/src/Seeqsuqr.hs

http://haskwho.googlecode.com/ · Haskell · 215 lines · 164 code · 30 blank · 21 comment · 10 complexity · a92a534e6e2833b95f63abbbb45a9094 MD5 · raw file

  1. module Main where
  2. import List
  3. import Char
  4. import Control.Monad
  5. import Text.HTML.TagSoup
  6. import Text.HTML.Download
  7. import Maybe
  8. import Network.HTTP
  9. import Network.URI
  10. import System.Directory
  11. import System.FilePath.Posix
  12. import System.Environment (getArgs)
  13. import System.Exit (exitFailure)
  14. import System.IO
  15. {-
  16. Seeqsuqr - A Seeqpod.com scraping tool in Haskell
  17. Seeqsuqr provides a commandline interface to scraping
  18. Seeqpod for music. Usage:
  19. Seeqsuqr.exe <username>
  20. List playlists for username.
  21. Seeqsuqr.exe <username> <playlist>
  22. Download all MP3 files in playlist. Files will be stored in
  23. relative path <username/<playlist>/*.mp3
  24. TODO:
  25. Read ID3 tags and rename MP3 files according to rules.
  26. Only download files needed (by timestamp? filesize?).
  27. More command line options?
  28. -}
  29. data PlayList = PlayList {
  30. playListPid :: String,
  31. playListName :: String,
  32. playListUrl :: String
  33. } deriving (Show)
  34. split :: Char -> String -> [String]
  35. split = unfoldr . split'
  36. split' :: Char -> String -> Maybe (String, String)
  37. split' c l
  38. | null l = Nothing
  39. | otherwise = Just (h, drop 1 t)
  40. where (h, t) = span (/=c) l
  41. href :: Tag -> Maybe String
  42. href (TagOpen _ attrs) = lookup "href" attrs
  43. href _ = Nothing -- We aren't interested in other stuff.
  44. urlParams url =
  45. let params = split '&' $ (split '?' url)!!1
  46. kv param =
  47. let (k:v:_) = split '=' param
  48. in (k,v)
  49. in map kv params
  50. linkTags tags = filter isTagOpen $ head $ sections (~== ("<a>")) tags
  51. links tags = catMaybes $ map href $ linkTags tags
  52. userPrefixRange url =
  53. let pmap = urlParams url
  54. in (lookup "umin" pmap, lookup "umax" pmap)
  55. getUserRange :: String -> IO String
  56. getUserRange userName =
  57. let userInitial = userNameInitial userName
  58. url = "http://www.seeqpod.com/api/iphone/ukeylist?key=" ++ userInitial
  59. isMatchingRange :: String -> Bool
  60. isMatchingRange url' =
  61. let userPre = take 3 userName
  62. in case userPrefixRange url' of
  63. (Just a, Just b) -> (a <= userPre) && (b >= userPre)
  64. otherwise -> False
  65. in do
  66. src <- getURL url;
  67. tags <- return $ parseTags src;
  68. rangeUrl <- return $ head $
  69. filter isMatchingRange $ links tags;
  70. return rangeUrl;
  71. userNameInitial userName = (toUpper $ head userName) : ""
  72. userRangeUrl (umin,umax) =
  73. concat [
  74. "http://www.seeqpod.com/api/iphone/ukeylist?umin=",
  75. umin, "&umax=", umax ]
  76. getUserPage :: String -> IO String
  77. getUserPage userName = do
  78. usersUrlFrag <- getUserRange userName
  79. usersUrl <- return $ "http://www.seeqpod.com" ++ usersUrlFrag
  80. usersSrc <- getURL usersUrl
  81. tags <- return $ parseTags usersSrc
  82. userUrl <- return $
  83. "http://www.seeqpod.com" ++ (head $ filter isUserLink $ links tags)
  84. getURL userUrl
  85. where
  86. isUserLink link =
  87. let params = urlParams link
  88. linkUser = lookup "uname" params
  89. in case linkUser of
  90. Just user -> user == userName
  91. Nothing -> False
  92. getPlayLists userName = do
  93. userSrc <- getUserPage userName
  94. tags <- return $ parseTags userSrc
  95. playlists <- return $ catMaybes $ map getPlayList $ links tags
  96. return playlists
  97. where
  98. getPlayList :: String -> Maybe PlayList
  99. getPlayList link =
  100. let params = urlParams link
  101. name = lookup "name" params
  102. pid = lookup "pid" params
  103. in case (name,pid) of
  104. (Just name', Just pid') ->
  105. Just $ PlayList pid' name' ("http://www.seeqpod.com" ++ link)
  106. otherwise -> Nothing
  107. getPlayListPage playList = do
  108. getURL $ playListUrl playList
  109. getMp3Links :: PlayList -> IO [String]
  110. getMp3Links playList = do
  111. src <- getPlayListPage playList
  112. tags <- return $ parseTags src
  113. liftM catMaybes $ mapM followRedirectLink $ links tags
  114. where
  115. followRedirectLink :: String -> IO (Maybe String)
  116. followRedirectLink url' = do
  117. src' <- getURL ("http://www.seeqpod.com" ++ url')
  118. tags' <- return $ parseTags src'
  119. case links tags' of
  120. (x:xs) -> return $ Just x
  121. _ -> return Nothing
  122. err :: String -> IO a
  123. err msg = do
  124. hPutStrLn stderr msg
  125. exitFailure
  126. getURL url = get $ fromJust $ parseURI url
  127. get :: URI -> IO String
  128. get uri = do
  129. eresp <- simpleHTTP (request uri)
  130. resp <- handleE (err . show) eresp
  131. case rspCode resp of
  132. (2,0,0) -> return (rspBody resp)
  133. (3,_,_) -> return (rspBody resp) -- TODO: follow redir
  134. _ -> err (httpError resp)
  135. where
  136. showRspCode (a,b,c) = map intToDigit [a,b,c]
  137. httpError resp = showRspCode (rspCode resp) ++ " " ++ rspReason resp
  138. request :: URI -> Request
  139. request uri = Request{ rqURI = uri,
  140. rqMethod = GET,
  141. rqHeaders = [],
  142. rqBody = "" }
  143. handleE :: Monad m => (ConnError -> m a) -> Either ConnError a -> m a
  144. handleE h (Left e) = h e
  145. handleE _ (Right v) = return v
  146. main :: IO ()
  147. main = do
  148. args <- getArgs
  149. case args of
  150. (userName:[]) -> printPlayLists userName
  151. (userName:playList:[]) -> downloadPlayList userName playList
  152. _ -> printHelp
  153. printHelp = do
  154. putStrLn "Coming Soon..."
  155. printPlayLists userName = do
  156. playLists <- getPlayLists userName
  157. mapM putStrLn $ map playListName playLists
  158. return ()
  159. downloadPlayList userName playListName' = let
  160. dlPath = joinPath [userName, playListName']
  161. in do
  162. playLists <- getPlayLists userName
  163. createDirectoryIfMissing True dlPath
  164. case filter (\x -> playListName x == playListName') playLists of
  165. (playList:xs) -> do
  166. mp3Links <- getMp3Links playList
  167. mapM_ (downloadFile dlPath) $ mp3Links
  168. _ -> return ()
  169. downloadFile dir url = let
  170. basefile = head $ reverse $ splitPath url
  171. file = joinPath [dir, basefile]
  172. in do
  173. putStr $ "Downloading " ++ url ++ " to " ++ file ++ "..."
  174. outFile <- openBinaryFile file WriteMode
  175. contents <- getURL url
  176. hPutStr outFile contents
  177. hClose outFile
  178. putStrLn "Done"