Supporting a New Service
From Mugshot Developer Wiki
Mugshot is "aware" of many different online services, and knows how to incorporate events on those services into the Mugshot view of the world. This page documents how you'd extend Mugshot to support a new service. A list of possible new services can be found at Where I'm At Locations.
If you're familiar with how to work on Mugshot, adding a new service will probably take only a few hours. It's not hard. If you're not familiar, you'll probably spend more time in order to ramp up, but that time will be useful as you go on to make other changes in the future.
Contents |
Background
You may find it helpful to look over Server Code Overview and Client Code Overview as well. You won't necessarily need to hack on the client to support a new service, but you'll need to hack on the server; see Server Development Setup to learn how to get a debug instance of the server running.
External Accounts
Mugshot users can tell Mugshot "where they're at" online - this is done on the account page. In the Mugshot code (but please, not in the user interface) these are called "external accounts," for example see the Java object com.dumbhippo.persistence.ExternalAccount.
A user's external accounts are shown in a "ribbon" of small icons on the user's /person page.
Blocks
If you look at a user or group page on Mugshot, you'll see a series of items relevant to that user or group. Each item is called a "block" and the list is called a "stack." On a person's page there are two "stacks" - one is the "what I'm watching" stack, and the other is the "what I did" stack. Here is an example.
Many blocks originate from external accounts. For example, you may see a Flickr block go by. Other blocks are Mugshot-internal, such as the block that appears when someone joins or leaves a group.
Adding A New External Account
To support a new kind of external account, you'll need to change the following places in the code. Read on below for more details on each change.
- Modify the ExternalAccountType enumeration by appending a new value for your account type.
- Modify HttpMethodsBean to have the Ajax methods used by the account page to set and unset the account information.
- Modify account.js and account.jsp to add UI for entering the account to the account page. (As part of this, you will also change the AccountPage class.)
The simplest way to find everywhere that needs changing is to have Eclipse do a text search for one of the existing account names, and add corresponding code for your new kind of account in all the same places.
In addition to the boilerplate all external accounts require, you'll want to make Mugshot do something with the external account. This is less mechanical, because it depends on what you want Mugshot to do. The most common thing is to create or update blocks based on the external account, more details on that below.
ExternalAccount Persistence Bean
External accounts are stored in the database using an EJB3 persistence bean called ExternalAccount. Its fields are:
- account
- the Mugshot account this external account is associated with
- accountType
- from the ExternalAccountType enum
- sentiment
- LOVE, HATE, or INDIFFERENT - just look at the account page to understand this one
- handle
- a free-form string field interpreted differently according to the accountType; for example, may be the username on the external site
- extra
- another free-form string field for external accounts that need a second piece of account-type-specific information
- quip
- a snarky comment that can be filled in when the user chooses sentiment=HATE
- feeds
- a set of RSS (or Atom, etc.) feed objects associated with the account
To add a new account type, you probably don't need to modify the ExternalAccount object. However, you will need to decide how to use the "handle" and "extra" fields.
ExternalAccountSystemBean is in charge of manipulating ExternalAccount objects.
ExternalAccountType Enumeration
The basic behavior of each external account type is encoded in the ExternalAccountType enum. Each enum value is a full-blown class that implements or overrides methods in the generic ExternalAccountType base class.
Some of the most important methods are:
- getSiteLink()
- gets a link to the external site, not specialized for any particular user account; e.g. "http://myspace.com"
- getLink()
- using the "handle" and "extra", build and return a link to a user's particular page on the site; e.g. "http://myspace.com/rhpennin"
- getLinkText()
- return the text of the link from getLink(), that is, <a href="getLink()">getLinkText()</a>
- canonicalizeHandle()
- if the external account uses the "handle" field, this method validates it and ensures it's in canonical format
- canonicalizeExtra()
- if the external account uses the "extra" field, this method validates it and ensures it's in canonical format
In addition, the constructor for each enum value will specify the human-readable name of the site, such as "MySpace". This is returned by the getSiteName() method.
For more details, look for comments in the java file (and ask us to add them when they are missing).
HttpMethodsBean
After defining a new external account type, you need a way to edit it for a given user from JavaScript. If you look at the HttpMethods interface you'll notice that each method has annotations indicating how the method would be invoked over http. These annotated methods are automatically made available (you can ask a running Mugshot server which methods it offers; see /api-docs).
External accounts can be removed via the generic method doRemoveExternalAccount() and "hated" via the generic method doHateExternalAccount(). However, to "love" an external account, users need to enter their account information, which varies by account type.
You'll see a bunch of existing methods such as doSetFlickrAccount(), doSetYouTubeName(), doSetTwitterName(), and so forth. You'll want to add a new method analogous to these, but with arguments appropriate to the external account type you're working on.
The implementation of HttpMethods is called HttpMethodsBean. An implementation of a "love" method will do the following (at least - look at some of the existing examples):
- validate and parse the account info entered; this is human-provided info, so try to be "smart" and figure out what was intended
- validation may include trying to fetch associated pages or feeds to be sure they work
- create an ExternalAccount object by invoking externalAccountSystem.getOrCreateExternalAccount()
- set the "handle" and/or "extra"
- create any associated Feed objects and add them to the ExternalAccount object
- set the sentiment on the ExternalAccount object to LOVE
- return the new value of the account info text field to the caller of the method
Account Page
account.js and account.jsp make up the account page.
The handling of external accounts in this code could use some refactoring; for now it's something of a cut-and-paste fest. Your best approach is to search for an existing account name such as Rhapsody and then add equivalent code in both the .js and .jsp file for your new account type. It should be straightforward, though due to the cut-and-pasting, perhaps harder than it should be.
Tips
There's a tag called randomTip.tag that displays a rotating set of hints about things people can do with Mugshot; you should add a tip mentioning your new supported service.
Adding a New Block
Your new external account may have zero, one, or more "block types" associated with it. For example, if you're adding support for a full-blown social networking site, you might have blocks for new comments from friends, new blog posts, friend adds/removes, new posts to groups, and so forth.
For each block type, you'll need to decide on:
- the "key" for the block - what is it "per-", that is, per-user, per-rss-feed-entry, per-whatever. For example, the "played a song" block is per-user (there isn't a separate block per-song), the Flickr photoset block is per-photoset, and blocks for new blog posts are generally per-post.
- when does it update or "stack" - this will bubble the block up to the top so people can see it. Generally, blocks should update when an interesting event happens regarding that block.
Most of the logic for handling your block type will be in the com.dumbhippo.server.blocks package, in particular you'll need to implement BlockHandler and BlockView.
Block Persistence Bean
The Block object represents a block in the database. Its fields include:
- blockType
- from the BlockType enum
- timestamp
- when the block was last updated, determines the sort order of a stack of blocks; newer blocks are higher in the stack
- data1, data2, data3
- these are used differently depending on the block type
- clickedCount
- some block types track how many times the block was clicked on by a user
- publicBlock
- a flag for whether the block is visible to anyone, this is a denormalization for efficiency
- inclusion
- StackInclusion, used to allow two different versions of a block depending on who is logged in
The blockType, data1, data2, data3, and inclusion fields make up the BlockKey or what the block is "per-"; for example, if the block is per-user, you would expect one of the data1/data2/data3 fields to be the id of the user the block is for.
BlockType Enumeration
There isn't much complexity in the BlockType enum, but you do need to add a value for your new block type.
BlockHandler Session Bean
For each kind of block, there's a corresponding session bean implementing the BlockHandler interface. The block handler determines which users and groups the block will be visible to, and can create a BlockView object used to display a block.
Block handlers are also responsible for creating and updating blocks when certain events occur. This is done by having NotifierBean call methods when those events happen; see the javadoc for Notifier for details on this subsystem.
There are a couple of convenience base classes, AbstractBlockPerFeedEntryHandlerBean is for block handlers that create one block for each entry in a feed, and AbstractSingleBlockForFeedBlockHandlerBean is for block handlers that keep one block for an entire feed and just update the timestamp on the block when new feed entries appear.
Block handlers generally implement a getKey() method, which creates the right block key for the block type; note that a single block type always has the same key components, that is, you can't have one kind of block that is sometimes per-user and sometimes per-feed-entry.
The best way to understand block handlers is to look at existing examples.
BlockView Object
A BlockHandler can create a BlockView object for blocks it handles. A BlockView represents the block as viewed by a particular logged-in user, or an anonymous (not logged in) visitor to the web site. It "packages up" all the details needed by the web tier (html/javascript) and the client application when they display the block.
BlockView is the abstract base class for all block views, and there are derived abstract base classes for block views with particular properties. For example, AbstractFeedEntryBlockView can be used for views of blocks that represent single feed entries.
Most of the BlockView interface should be self-explanatory or have inline javadocs.
StackerBean Changes
StackerBean has a switch over BlockType in a couple of places. When you add a new block type, Eclipse should warn you about not handling it in these switch statements; you need to add handling for your block type. The most important change in StackerBean.java is to point the StackerBean to your new BlockHandler implementation.
Block Display
Blocks are displayed in three major places:
- On the web, for example on person or group pages
- In the Linux or Windows client application stacker
- In "widgets" such as the Google Gadget or Mugshot Mini badge
Web Display (JSP Tag)
The <dht3:block/> tag displays a block in web-based stacks.
If your BlockView implements the TitleDescriptionBlockView interface, you don't need to add a special tag to display your block. However, you can add one (copying existing examples) if you want to display your block in a special way. It's usually better to do a custom tag and incorporate elements such as quipping on the block, thumbnails, or whatever is relevant.
Client-Side Display
The client application is written in C, so if you lack C skillz your best bet is to implement TitleDescriptionBlockView to give the client a basic way to display your block, and then beg a C programmer on the list to implement a display for your specific block type.
If you do have C skillz, you need to implement a "model" and a "view" object for your block type.
The "model" is a subclass of HippoBlock, and most of the small amount of work is implementing an update_from_xml() method which parses XML received from the server. This XML is created by the Java BlockView object you coded earlier.
The display engine on the client side is called HippoCanvas and you need to implement a HippoCanvasBlock subclass to display it.
The way to approach this: cut-and-paste the most similar-looking existing HippoBlock and HippoCanvasBlock subclasses, then modify those.
You'll need to add your new files to the Makefile and you'll need to add your new block type to the switch in hippo_block_new(), the array in hippo_block_type_from_attributes(), and the switch in hippo_canvas_block_new().
Badge and Gadget
The badge and Google gadget don't require custom coding for each new block type. They display a "summary" of each block using methods on the BlockView object. You should look at your block in the badge and gadget, though, to be sure you return sensible text from these methods.

