I've been working for a little while on a haskell wrapper to the photo hosting site Smugmug's api.
As of now, the library is pretty simple - it is 'read-only' - ie, you can't use it to upload to or create galleries, but I still think it is potentially useful. I created it primarily so I could integrate my photos at smugmug into my website, which you can see in the 'photos' section - a very primitive but proof of concept-esque demonstration.
As a sample piece of code, I was trying today to get all my photos off of smugmug, just to see if I could. I checked around, and played with a windows program named AlbumFetcher, by Greg Wellman, but I use linux, and for whatever reason it kept erroring after downloading a bunch of photos when I used it through wine. Perhaps it was due to network problems, but I figured it wasn't worth the effort to try to troubleshoot it. So I concocted a short script using my library to get all the original sized urls to my photos, which I could then use something like wget -i ./myfile to download. Here it is:
module Main where
import Data.Maybe (fromJust, isJust)
import Network.Libraries.HSmugAPI
onlyJust m = map fromJust (filter isJust m)
mkIdKey (i, k) = makeIdKey i k
ses s = fromJust $ getSessId $ fromJust s
iGet s alb = do ims <- imagesGet (ses s) alb True
sequence $ map putStrLn $ onlyJust $ map getOriginalURL (fromJust ims)
main = do s <- loginWithPassword "mylogin" "mypassword"
h <- albumsGet (ses s) (Just "dbpatterson") False
sequence $ map (iGet s) (fromJust h)
logout (ses s)
To briefly explain how the code is working or why it looks like it does - first, I am hooking into the JSON api, and to do it I'm using a JSON library maintained by Paul Brown and you can download it from his repository (to fetch it, darcs get http://datapr0n.com/repos/perpubplat). The internal representation is not vitally important (if it starts to be, then my library is lacking, email me!), but everything that is returned is a sort of object. This means that when you loginWithPassword you are given back a session object (well, a Maybe session object), which you then pull the session id out of (getSessId). This is similar to what you do with results from imagesGet, or albumsGet, or anything really.
The second this is that the Maybe monad is used throughout, and is passed totally onto the user of the library. I did this because I believe that errors should be caught and handled by the end user (of the library) - the library should not try to trap them and silently error or recover. This is why you see a few 'fromJust' (this is an unsafe assumption that the request succeeded. In a short script like this, it is a perfect thing to do, because if it didn't, the script should fail. In a large program, you would want to check for this type of thing). You also see them when getting attributes out of the objects - this is because of the nature of how the api returns types, and a little paranoia - for things like the Original URL, if you are anonymous, it may or may not be visible, so there is no way for the library to know if this is the case - you simply have to check it.
To download HSmugAPI 0.1, Click Here.
Installing:
A simple
runghc Setup.hs configure
runghc Setup.hs build
runghc Setup.hs install
should work, provided you have curl and json (the library linked to above, from Paul Brown's site).
In the tarball, the top level Main.hs is a rough test/shows what is available.
If something isn't working, or it won't build, email me!
API
here is a (very) short description of the public api:
-- for the following, the Bools are for values that can be 'Heavy',
-- the session is always first, where needed (the String).
loginAnonymously :: IO (Maybe J.Value)
loginWithPassword :: String -> String -> IO (Maybe J.Value)
logout:: String -> IO ()
usersGetTree :: String -> Maybe String -> Bool -> IO (Maybe J.Value)
albumsGet :: String -> Maybe String -> Bool -> IO (Maybe [J.Value])
albumsGetInfo :: String -> J.Value -> IO (Maybe J.Value)
imagesGet :: String -> J.Value -> Bool -> IO (Maybe [J.Value])
imagesGetInfo :: String -> J.Value -> IO (Maybe J.Value)
imagesGetURLs :: String -> J.Value -> IO (Maybe J.Value)
imagesGetEXIF :: String -> J.Value -> IO (Maybe J.Value)
-- all of the following are of type J.Value -> Maybe String except:
-- getId, getPosition, getSerial, getSize, getWidth, getHeight, and getImageCount
-- which are of type J.Value -> Maybe Double
-- and getHidden, which is of type J.Value -> Maybe Bool
-- and getCategory, getSubcategory, which are of type J.Value -> Maybe J.Value
getSessId
getId
getKey
getTitle
getAlbumURL
getLargeURL
getXLargeURL
getX2LargeURL
getX3LargeURL
getOriginalURL
getMediumURL
getSmallURL
getTinyURL
getThumbURL
getDate
getKeywords
getFormat
getCaption
getPosition
getSerial
getSize
getWidth
getHeight
getLastUpdated
getFileName
getMD5Sum
getWatermark
getHidden
getAlbum
getPublic
getName
getDescription
getImageCount
getCategory
getSubCategory
makeIdKey :: Double -> String -> J.Value
Feedback (positive, negative, neutral) is welcome, and if people want it, it would not be hard to extend this to add the rest of the smugmug api.
I have deliberately not included much glue between the actual api and the use of it, because I think that is something that can more cleanly evolve out of use - if people find themselves repeating certain patterns when using the api, those would logically become part of the api wrapper. So, let me know what you think!
