diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,4 @@
+CHANGELOG
+
+1.20130928 -> 1.20131115
+  - Added support to http://radioreddit.com
diff --git a/Radio.hs b/Radio.hs
new file mode 100644
--- /dev/null
+++ b/Radio.hs
@@ -0,0 +1,104 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+-- | A generic interface to online radio services
+module Radio
+  ( SongMeta(..)
+  , Radio(..)
+  , getLordDir
+  , getPidFile
+  , getLogFile
+  , getStateFile
+  , writeLog
+  ) where
+
+import           Codec.Binary.UTF8.String (encodeString)
+import           Control.Applicative ((<$>))
+import           Control.Concurrent.MVar
+import           Control.Monad (when)
+import           Data.Aeson (FromJSON, Value)
+import qualified Data.ByteString.Char8 as C
+import           Network.MPD hiding (play, Value)
+import qualified Network.MPD as MPD
+import           System.Directory (getHomeDirectory)
+import           System.IO.Unsafe (unsafePerformIO)
+import           System.Log.FastLogger
+
+eof = unsafePerformIO newEmptyMVar
+
+data SongMeta = SongMeta 
+    { artist    :: String
+    , album     :: String
+    , title     :: String
+    }
+
+class FromJSON a => Radio a where
+    data Param a :: *
+
+    parsePlaylist :: Value -> [a]
+
+    getPlaylist :: Param a -> IO [a]
+
+    songUrl :: Param a -> a -> IO String
+
+    songMeta :: a -> SongMeta
+
+    tagged :: a -> Bool
+
+    play :: Logger -> Param a -> [a] -> IO ()
+    play logger reqData [] = getPlaylist reqData >>= play logger reqData
+    play logger reqData (x:xs) = do
+        surl <- songUrl reqData x
+        print surl
+        when (surl /= "") $ do
+            let song = artist (songMeta x) ++ " - " ++ title (songMeta x)
+            writeLog logger song 
+            getStateFile >>= flip writeFile song
+            mpdLoad $ Path $ C.pack surl
+            takeMVar eof                     -- Finished
+        play logger reqData xs
+
+mpdLoad :: Path -> IO ()
+mpdLoad path = do
+    withMPD $ do
+        clear
+        add path
+    withMPD $ MPD.play Nothing
+    mpdPlay
+
+mpdPlay :: IO ()
+mpdPlay = do
+    withMPD $ idle [PlayerS]   
+    -- This will block until paused/finished.
+
+    st <- withMPD status
+    let st' = fmap stState st
+    print st'
+    if st' == Right Stopped 
+        then putMVar eof ()
+        else mpdPlay
+
+getLordDir :: IO FilePath
+getLordDir = (++ "/.lord") <$> getHomeDirectory
+
+getPidFile :: IO FilePath
+getPidFile = (++ "/lord.pid") <$> getLordDir
+
+getLogFile :: IO FilePath
+getLogFile = (++ "/lord.log") <$> getLordDir
+
+getStateFile :: IO FilePath
+getStateFile = (++ "/lordstate") <$> getLordDir
+
+formatLogMessage :: IO ZonedDate -> String -> IO [LogStr]
+formatLogMessage getdate msg = do
+    now <- getdate
+    return 
+        [ LB now
+        , LB " : "
+        , LS $ encodeString msg
+        , LB "\n"
+        ]
+
+writeLog :: Logger -> String -> IO ()
+writeLog l msg = formatLogMessage (loggerDate l) msg >>= loggerPutStr l
diff --git a/Radio/Cmd.hs b/Radio/Cmd.hs
new file mode 100644
--- /dev/null
+++ b/Radio/Cmd.hs
@@ -0,0 +1,119 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+-- | Module of http://cmd.fm
+module Radio.Cmd where
+
+import qualified Control.Exception as E
+import           Control.Monad (liftM)
+import           Data.Aeson
+import qualified Data.ByteString.Char8 as C
+import           Data.Conduit (($$+-))
+import           Data.Conduit.Attoparsec (sinkParser)
+import qualified Data.HashMap.Strict as HM
+import           GHC.Generics (Generic)
+import           Network.HTTP.Conduit
+import           Network.HTTP.Types (renderQuery)
+import           Network.HTTP.Types.Header (hLocation)
+import           Network.HTTP.Types.Status (status302)
+
+import qualified Radio
+
+type Param a = Radio.Param Cmd
+
+data Cmd = Cmd
+    { sc_id :: Int
+    , title :: String
+    , artwork_url :: String
+    , description   :: String
+    , duration      :: Int
+    , genre         :: String
+    , tag_list      :: String
+    , waveform_url  :: String
+    , stream_url    :: String
+    --, last_listened :: Int
+    , main_type     :: String
+    --, created_at    :: String
+    --, updated_at    :: String
+    } deriving (Show, Generic)
+
+instance FromJSON Cmd
+
+instance Radio.Radio Cmd where
+    data Param Cmd = Genre String
+
+    parsePlaylist val = do
+        case fromJSON val of
+            Success s -> s
+            Error err -> error $ "Parse playlist failed: " ++ show err
+
+    getPlaylist (Genre g) = do
+        let url = "http://cmd.fm/api/tracks/search"
+            query = [ ("genre", Just $ C.pack g)
+                    , ("limit", Just $ C.pack "10") ]
+        initReq <- parseUrl url
+        let req = initReq { method = "GET"
+                          , queryString = renderQuery False query
+                          }
+        withManager $ \manager -> do
+            res <- http req manager
+            liftM Radio.parsePlaylist (responseBody res $$+- sinkParser json)
+
+    songUrl _ x = do
+        initReq <- parseUrl url
+        let req = initReq { method = "GET"
+                          , queryString = renderQuery False query
+                          }
+        E.catch (do withManager $ \manager -> 
+                        httpLbs req { redirectCount = 0 } manager >> return "")
+                (\e -> case e of
+                    (StatusCodeException s hdr _) ->
+                        if s == status302 then redirect hdr 
+                        else return ""
+                    otherException -> print otherException >> return ""
+                )
+      where
+        url = stream_url x
+        query = [("client_id", Just $ C.pack "2b659ea66970555922d89ce9c07b2d0d")]
+        redirect hdr = case HM.lookup hLocation $ HM.fromList hdr of 
+                            Just u   -> return $ C.unpack u
+                            Nothing  -> return ""
+
+    songMeta x = Radio.SongMeta "" "" (title x)
+
+    tagged _ = False
+
+-- Currently, no api is provided to retrieve genre list.
+genres :: IO [String]
+genres = return 
+    ["80s","Abstract","Acid Jazz","Acoustic"
+    ,"Acoustic Rock","Alternative","Ambient","Avantgarde"
+    ,"Ballads","Blues","Blues Rock","Breakbeats"
+    ,"Chillout","Chiptunes","Choir","Classic Rock"
+    ,"Classical","Classical Guitar","Contemporary","Dance"
+    ,"Dancehall","Death Metal","Dirty South","Disco"
+    ,"Dream Pop","Drum & Bass","Dub","Dubstep"
+    ,"Easy Listening","Electro House","Electronic","Electronic Pop"
+    ,"Electronic Rock","Folk Rock","Funk","Glitch"
+    ,"Grime","Grindcore","Grunge","Hard Rock",
+    "Hardcore","Heavy Metal","Hip-Hop","House"
+    ,"Indie","Indie Pop","Industrial Metal","Instrumental"
+    ,"Instrumental Rock","J-Pop","Jazz","Jazz Funk"
+    ,"Jazz Fusion","K-Pop","Latin Jazz","Metalcore"
+    ,"Minimal","Modern Jazz","Moombahton","New Wave"
+    ,"Nu Jazz","Opera","Orchestral","Piano"
+    ,"Pop","Post Hardcore","Post Rock","Progressive House"
+    ,"Progressive Metal","Progressive Rock","Punk"
+    ,"R&B","Rap","Reggae","Reggaeton","Riddim"
+    ,"Rock","Rock 'n' Roll","Shoegaze","Singer / Songwriter"
+    ,"Smooth Jazz","Soul","Synth Pop","Tech House"
+    ,"Techno","Thrash Metal","Trance","Trap"
+    ,"Trip-hop","Turntablism","Underground"]
+
+pprGenres :: [String] -> IO ()
+pprGenres [] = return ()
+pprGenres gs = do
+    putStrLn $ foldr1 f (take 4 gs)
+    pprGenres $ drop 4 gs
+  where
+    f a b = a ++ (concat $ take (20 - length(a)) $ repeat " ") ++ b
diff --git a/Radio/Douban.hs b/Radio/Douban.hs
new file mode 100644
--- /dev/null
+++ b/Radio/Douban.hs
@@ -0,0 +1,192 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+-- | Module of http://douban.fm
+module Radio.Douban where
+
+import           Codec.Binary.UTF8.String (encodeString)
+import           Control.Applicative ((<$>), (<*>))
+import           Control.Monad
+import           Data.Aeson
+import qualified Data.ByteString.Char8 as C
+import           Data.Char (isDigit)
+import           Data.Conduit (($$+-))
+import           Data.Conduit.Attoparsec (sinkParser)
+import qualified Data.HashMap.Strict as HM
+import           Data.Maybe (fromJust, fromMaybe)
+import qualified Data.Text as T
+import           Network.HTTP.Conduit
+import           Network.HTTP.Types (urlEncode, renderQuery, Query)
+import           Prelude hiding (id)
+import           System.Console.ANSI
+import           Text.HTML.DOM (parseLBS)
+import           Text.XML.Cursor
+
+import qualified Radio
+
+type Param a = Radio.Param Douban
+
+data Douban = Douban 
+    { picture :: String
+    , albumtitle :: String
+    -- , company :: String
+    -- , rating_avg :: Float
+    -- , public_time :: String
+    , ssid :: Maybe String
+    , album :: String
+    -- , like :: Int
+    , artist :: String
+    , url :: String
+    , title :: String
+    , subtype :: String
+    -- , length :: Int
+    , sid :: String
+    , aid :: String
+    } deriving Show
+
+instance FromJSON Douban where
+    parseJSON (Object v) = Douban <$>
+                           v .: "picture" <*>
+                           v .: "albumtitle" <*>
+                           v .:? "ssid" <*>
+                           v .: "album" <*>
+                           --v .: "like" <*>
+                           v .: "artist" <*>
+                           v .: "url" <*>
+                           v .: "title" <*>
+                           v .: "subtype" <*>
+                           v .: "sid" <*>
+                           v .: "aid"
+    parseJSON _          = mzero
+
+
+getPlaylist' :: Query -> IO [Douban]
+getPlaylist' query = do
+    let url = "http://douban.fm/j/mine/playlist"
+    initReq <- parseUrl url
+    let req = initReq { method = "GET"
+                      , queryString = renderQuery False query
+                      }
+    withManager $ \manager -> do
+        res <- http req manager
+        liftM Radio.parsePlaylist (responseBody res $$+- sinkParser json)
+
+musicianID :: String -> IO (Maybe String)
+musicianID name = do
+    let url = "http://music.douban.com/search/" ++ 
+              (C.unpack $ urlEncode True (C.pack $ encodeString name))
+    rsp <- simpleHttp url
+    let cursor = fromDocument $ parseLBS rsp
+        href = cursor $// element "a" 
+                      >=> attributeIs "class" "ll musician_title "
+                      &|  attribute "href"
+    return $ Just $ filter isDigit $ T.unpack $ head $ head href
+
+instance Radio.Radio Douban  where
+    data Param Douban = Cid Int | Musician String
+
+    -- TODO: those without ssid filed are ads, filter them out!
+    parsePlaylist (Object hm) = do
+        let songs = HM.lookup "song" hm
+        case fromJSON $ fromMaybe Null songs of
+             Success s -> s
+             Error _   -> []
+    parsePlaylist _ = error "Unrecognized playlist format."
+
+    getPlaylist (Cid cid) = do
+        let query = [ ("type", Just "n")
+                    , ("channel", Just $ C.pack $ show cid)
+                    , ("from", Just "lord")
+                    ]
+        getPlaylist' query
+    getPlaylist (Musician name) = do
+        mId <- musicianID name
+        let query = [ ("type", Just "n")
+                    , ("channel", Just "0")
+                    , ("context", Just $ C.pack ("channel:0|musician_id:" ++ fromJust mId))
+                    , ("from", Just "lord")
+                    ]
+        getPlaylist' query
+
+    songUrl _ x = return $ url x
+
+    songMeta x = Radio.SongMeta (artist x) (albumtitle x) (title x)
+
+    -- Songs from douban.fm comes with no tags!
+    tagged _ = False
+
+data Channel = Channel 
+    { intro :: String
+    , name :: String
+    , song_num :: Int
+    -- , creator :: Creator
+    , banner :: String
+    , cover :: String
+    , id :: Int
+    , hot_songs :: [String]
+    } deriving (Eq, Show)
+
+instance FromJSON Channel where
+    parseJSON (Object v) = Channel <$>
+                           v .: "intro" <*>
+                           v .: "name" <*>
+                           v .: "song_num" <*>
+                           v .: "banner" <*>
+                           v .: "cover" <*>
+                           v .: "id" <*>
+                           v .: "hot_songs"
+    parseJSON _          = mzero
+
+
+pprChannels :: [Channel] -> IO ()
+pprChannels chs =
+    forM_ chs (\c -> do
+        setSGR [SetConsoleIntensity BoldIntensity]
+        putStr $ "* " ++ name c 
+        setSGR [SetColor Foreground Vivid Green]
+        putStrLn $ " cid=" ++ show (id c)
+        setSGR [Reset]
+        let folding = foldr (\x acc -> 
+                      if x `elem` "\r\n" then ' ':acc else x:acc) []
+        putStrLn $ "    Intro: " ++ folding (intro c) 
+        putStr "    Hot songs: " 
+        forM_ (hot_songs c) (\s -> putStr $ s ++ ", ")
+        putStrLn ""
+        )
+
+-- | Return a list of hot channels.
+hot :: IO [Channel]
+hot = search' url
+  where
+    url = "http://douban.fm/j/explore/hot_channels"
+
+
+-- | Return a list of up trending channels.
+trending :: IO [Channel]
+trending = search' url
+  where
+    url = "http://douban.fm/j/explore/up_trending_channels"
+
+
+-- | Return a list of channels matching provided keywords.
+search :: String -> IO [Channel]
+search [] = return []
+search key = search' url
+  where 
+    url = "http://douban.fm/j/explore/search?query=" ++ 
+          -- encodeString: encode chinese characters
+          (C.unpack $ urlEncode True (C.pack $ encodeString key))
+
+
+search' :: String -> IO [Channel]
+search' url = do
+    req <- parseUrl url
+    (Object hm) <- withManager $ \manager -> do
+        res <- http req manager
+        responseBody res $$+- sinkParser json
+
+    let (Object hm') = fromJust $ HM.lookup "data" hm
+        resData = fromJust $ HM.lookup "channels" hm'
+        channels = fromJSON resData :: Result [Channel]
+    case channels of
+        Success c -> return c
+        Error err -> putStrLn err >> print resData >> return []
diff --git a/Radio/Jing.hs b/Radio/Jing.hs
new file mode 100644
--- /dev/null
+++ b/Radio/Jing.hs
@@ -0,0 +1,272 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+-- | Module of http://jing.fm
+-- It's a bit tricky to play jing.fm
+-- Notice the `play` function of **instance Radio**
+module Radio.Jing where
+
+import           Codec.Binary.UTF8.String (encodeString)
+import           Control.Applicative ((<$>), (<*>))
+import           Control.Concurrent (forkIO, threadDelay)
+import           Control.Concurrent.MVar
+import qualified Control.Exception as E
+import           Control.Monad (liftM, mzero)
+import           Data.Aeson
+import           Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as C
+import qualified Data.HashMap.Strict as HM
+import           Data.Maybe (fromJust, fromMaybe)
+import qualified Data.Text as T
+import           Data.Yaml
+import           Data.CaseInsensitive (mk)
+import           Data.Conduit (runResourceT, ($$+-))
+import           Data.Conduit.Binary (sinkFile)
+import           Data.Conduit.Attoparsec (sinkParser)
+import           GHC.Generics (Generic)
+import           Network.HTTP.Types 
+import           Network.HTTP.Conduit
+import           Network.MPD hiding (play, Value, Query)
+import qualified Network.MPD as MPD
+import           System.IO
+import           System.IO.Unsafe (unsafePerformIO)
+import           System.Directory (doesFileExist)
+
+import Radio
+
+type Param a = Radio.Param Jing
+
+downloaded = unsafePerformIO newEmptyMVar
+
+data Jing = Jing 
+    { abid :: Int       -- album id
+    , aid  :: Int       -- artist id
+    , an   :: String    -- album name
+    , atn  :: String    -- artist name
+    , d    :: String
+    , fid  :: String
+    , fs   :: Int       -- file size
+    , mid  :: ByteString
+    , n    :: String    -- song name
+    , tid  :: Int
+    --, y    :: Bool
+    } deriving (Show, Generic)
+
+instance FromJSON Jing
+
+data Usr = Usr
+    { userid :: Int
+    , usernick :: ByteString
+    } deriving Show
+
+instance FromJSON Usr where
+    parseJSON (Object v) = Usr <$>
+                           v .: "id" <*>
+                           v .: "nick"
+    parseJSON _          = mzero
+
+instance Radio.Radio Jing where
+    data Param Jing = Token
+        { aToken        :: ByteString
+        , rToken        :: ByteString
+        , uid           :: Int
+        , nick          :: ByteString 
+        , cmbt          :: String
+        , highquality   :: Bool
+        } deriving (Show, Generic)
+
+    parsePlaylist (Object hm) = do
+        let songs = HM.lookup "result" hm >>= 
+                    \(Object hm') -> HM.lookup "items" hm'
+        case fromJSON $ fromMaybe Null songs of
+            Success s -> s
+            Error err -> error $ "Parse playlist failed: " ++ show err
+    parsePlaylist _ = error "Unrecognized playlist format."
+
+    getPlaylist tok = do
+        let url = "http://jing.fm/api/v1/search/jing/fetch_pls"
+            query = [ ("q", C.pack $ encodeString $ cmbt tok)
+                    , ("ps", "10")
+                    , ("st", "0")
+                    , ("u", C.pack $ show $ uid tok)
+                    , ("tid", "0")
+                    , ("mt", "")
+                    , ("ss", "true")
+                    ]
+            aHdr = (mk "Jing-A-Token-Header", aToken tok) :: Header
+            rHdr = (mk "Jing-R-Token-Header", rToken tok) :: Header
+
+        initReq <- parseUrl url
+        let req = initReq { requestHeaders = [aHdr, rHdr] }
+
+        -- urlEncodeBody adds a content-type request header and
+        -- changes the method to POST.
+        let req' = urlEncodedBody query req
+        withManager $ \manager -> do
+            res <- http req' manager
+            liftM Radio.parsePlaylist (responseBody res $$+- sinkParser json)
+
+    songUrl tok x = E.catch 
+        (do
+            let url = "http://jing.fm/api/v1/media/song/surl"
+                type_ = if highquality tok then "NO" else "MM"
+                query = [ ("type", Just type_)
+                        , ("mid", Just $ mid x)
+                        ] :: Query
+                aHdr = (mk "Jing-A-Token-Header", aToken tok) :: Header
+                rHdr = (mk "Jing-R-Token-Header", rToken tok) :: Header
+
+            initReq <- parseUrl url
+            let req = initReq { method = "POST"
+                              , requestHeaders = [aHdr, rHdr]
+                              , queryString = renderQuery False query
+                              }
+            (Object hm) <- withManager $ \manager -> do
+                res <- http req manager
+                responseBody res $$+- sinkParser json
+            let (String surl) = fromJust $ HM.lookup "result" hm
+            return $ T.unpack surl)
+        (\e -> print (e :: E.SomeException) >> songUrl tok x)
+
+    songMeta x = Radio.SongMeta (atn x) (an x) (n x)
+
+    -- Songs from jing.fm comes with tags!
+    tagged _ = True
+
+    -- The media file jing.fm provides is of m4a type.
+    -- MPD is unable to stream m4a file, as a result, lord streams it to 
+    -- ~/.lord/lord.m4a
+    play logger reqData [] = Radio.getPlaylist reqData >>= Radio.play logger reqData
+    play logger reqData (x:xs) = do
+        surl <- Radio.songUrl reqData x
+        print surl
+        req <- parseUrl surl
+        home <- Radio.getLordDir
+        manager <- newManager def
+        threadId <- forkIO $ E.catch 
+            (do
+                let song = artist (songMeta x) ++ " - " ++ title (songMeta x)
+                getStateFile >>= flip writeFile song
+
+                runResourceT $ do 
+                    res <- http req manager
+                    responseBody res $$+- sinkFile (home ++ "/lord.m4a")
+                -- This will block until downloaded.
+
+                writeLog logger song 
+                putMVar downloaded ())
+            (\e -> do
+                print (e :: E.SomeException)
+                Radio.writeLog logger $ show e
+                Radio.play logger reqData xs
+                )
+        --mtid <- newMVar threadId
+        threadDelay 3000000
+        mpdLoad
+        Radio.play logger reqData xs
+
+mpdLoad :: IO ()
+mpdLoad = do
+    --m4a <- (++ "/lord.m4a") <$> Radio.getLordDir
+    let m4a = "lord/lord.m4a"
+    s <- withMPD $ do
+            clear
+            update [Path "lord"]
+            add m4a
+    case s of
+        Right _ -> do
+            withMPD $ MPD.play Nothing
+            mpdPlay
+        _                  -> mpdLoad
+
+mpdPlay :: IO ()
+mpdPlay = do
+    withMPD $ idle [PlayerS]   
+    -- This will block until paused/finished.
+
+    st <- withMPD status
+    let st' = fmap stState st
+    print st'
+    bd <- isEmptyMVar downloaded
+    if st' == Right Stopped 
+        then if bd 
+                then do                                     -- Slow Network
+                    withMPD $ MPD.play Nothing
+                    mpdPlay
+                else do
+                    withMPD clear
+                    takeMVar downloaded                     -- Finished
+        else mpdPlay                                        -- Pause
+
+instance FromJSON (Radio.Param Jing)
+instance ToJSON (Radio.Param Jing)
+
+login :: String -> IO (Radio.Param Jing)
+login keywords = do
+    hSetBuffering stdout NoBuffering
+    hSetEcho stdin True 
+    putStrLn "Please Log in"
+    putStr "Email: "
+    email <- getLine
+    putStr "Password: "
+    hSetEcho stdin False 
+    pwd <- getLine
+    hSetEcho stdin True 
+    putStrLn ""
+    mtoken <- createSession keywords email pwd
+    case mtoken of
+         Just tok -> do
+             saveToken tok
+             return tok
+         Nothing  -> do
+             putStrLn "ERROR: Invalid email or password!"
+             login keywords
+
+createSession :: String -> String -> String -> IO (Maybe (Radio.Param Jing))
+createSession keywords email pwd = do
+    let url = "http://jing.fm/api/v1/sessions/create"
+        query = [ ("email", C.pack email) , ("pwd", C.pack pwd) ]
+    req <- parseUrl url
+    let req' = urlEncodedBody query req
+    res <- withManager $ \manager -> http req' manager
+    let hmap = HM.fromList $ responseHeaders res
+        atoken = HM.lookup "Jing-A-Token-Header" hmap
+        rtoken = HM.lookup "Jing-R-Token-Header" hmap
+        parseToken :: Value -> Maybe (Radio.Param Jing)
+        parseToken (Object hm) = do
+            let user = HM.lookup "result" hm >>= 
+                       \(Object hm') -> HM.lookup "usr" hm'
+            case fromJSON $ fromMaybe Null user of
+                Success u -> Token <$> atoken
+                                   <*> rtoken
+                                   <*> (Just $ userid u)
+                                   <*> (Just $ usernick u)
+                                   <*> Just keywords
+                                   <*> Just True
+                Error err -> error $ "Retrieve token failed: " ++ show err
+        parseToken _ = error "Unrecognized token format."
+    liftM parseToken (runResourceT $ responseBody res $$+- sinkParser json)
+
+saveToken :: Radio.Param Jing -> IO ()
+saveToken tok = do
+    home <- Radio.getLordDir
+    let yml = home ++ "/lord.yml"
+    encodeFile yml tok
+    putStrLn "Your token has been saved to ~/lord.yml"
+
+readToken :: String -> IO (Maybe (Radio.Param Jing))
+readToken keywords = do
+    home <- Radio.getLordDir
+    let yml = home ++ "/lord.yml"
+    exist <- doesFileExist yml
+    if exist
+       then do
+            conf <- decodeFile yml
+            case conf of
+                Nothing -> error $ "Invalid YAML file: " ++ show conf
+                Just c -> 
+                    case fromJSON c of
+                        Success tok -> return $ Just $ tok { cmbt = keywords }
+                        Error err -> error $ "Parse token failed: " ++ show err
+       else return Nothing
diff --git a/Radio/Reddit.hs b/Radio/Reddit.hs
new file mode 100644
--- /dev/null
+++ b/Radio/Reddit.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+-- | Module of http://radioreddit.com
+module Radio.Reddit where
+
+import           Control.Monad (liftM)
+import           Data.Aeson
+import           Data.Conduit (($$+-))
+import           Data.Conduit.Attoparsec (sinkParser)
+import qualified Data.HashMap.Strict as HM
+import           Data.Maybe (fromMaybe)
+import           GHC.Generics (Generic)
+import           Network.HTTP.Conduit
+
+import qualified Radio
+
+type Param a = Radio.Param Reddit
+
+data Reddit = Reddit
+    { title         :: String
+    , artist        :: String
+    , preview_url   :: String
+    , download_url  :: Maybe String
+    } deriving (Show, Generic)
+
+instance FromJSON Reddit
+
+instance Radio.Radio Reddit where
+    data Param Reddit = Genre String
+
+    parsePlaylist (Object hm) = do
+        let songs = HM.lookup "songs" hm >>=
+                    \(Object hm') -> HM.lookup "song" hm'
+        case fromJSON $ fromMaybe Null songs of
+            Success s -> s
+            Error err -> error $ "Parse playlist failed: " ++ show err
+    parsePlaylist _ = error "Unrecognized playlist format."
+
+    getPlaylist (Genre g) = do
+        let url = "http://radioreddit.com/api/" ++ g ++ "/status.json"
+        req <- parseUrl url
+        withManager $ \manager -> do
+            res <- http req manager
+            liftM Radio.parsePlaylist (responseBody res $$+- sinkParser json)
+
+    songUrl _ x = return $ fromMaybe (preview_url x) (download_url x)
+
+    songMeta x = Radio.SongMeta (artist x) "" (title x)
+
+    tagged _ = False
+
+genres :: IO [String]
+genres = return 
+    [ "main", "electronic", "rock", "metal"
+    , "indie", "hiphop", "random", "talk" ]
+
+pprGenres :: [String] -> IO ()
+pprGenres [] = return ()
+pprGenres gs = do
+    putStrLn $ foldr1 f (take 4 gs)
+    pprGenres $ drop 4 gs
+  where
+    f a b = a ++ (concat $ take (20 - length(a)) $ repeat " ") ++ b
diff --git a/lord.cabal b/lord.cabal
--- a/lord.cabal
+++ b/lord.cabal
@@ -1,5 +1,5 @@
 name:                lord
-version:             1.20131115
+version:             1.20131124
 synopsis:            A command line interface to online radios.
 description:         
     A unified interface to online radio service providers, use mpd as backend.
@@ -19,6 +19,7 @@
 build-type:          Simple
 cabal-version:       >=1.10
 tested-with:         GHC == 7.6.3
+Extra-Source-Files:  CHANGELOG
 
 source-repository   head
   type:             git
@@ -26,7 +27,11 @@
  
 executable lord
   main-is:              main.hs         
-  -- other-modules:       
+  other-modules:        Radio,
+                        Radio.Cmd,
+                        Radio.Douban,
+                        Radio.Jing,
+                        Radio.Reddit
   ghc-options:          -Wall -fno-warn-unused-do-bind
   build-depends:        base >= 4 && < 5, 
                         aeson >= 0.6, 
