commit
12947ba2bd
3 changed files with 158 additions and 0 deletions
@ -0,0 +1,122 @@ |
|||||
|
import Control.Monad (unless) |
||||
|
import System.Info (os) |
||||
|
import System.Process (system, rawSystem) |
||||
|
import System.Exit (ExitCode(..)) |
||||
|
import System.Directory (doesFileExist) |
||||
|
import Network.Google.OAuth2 (formUrl, exchangeCode, refreshTokens, |
||||
|
OAuth2Client(..), OAuth2Tokens(..)) |
||||
|
import Network.Google (makeRequest, doRequest) |
||||
|
import Network.HTTP.Conduit (simpleHttp) |
||||
|
import Data.Aeson |
||||
|
import qualified Data.ByteString.Lazy.Char8 as BL |
||||
|
import qualified Data.Text as T |
||||
|
import qualified Data.Map as M |
||||
|
|
||||
|
data URL = URL { jurl :: T.Text } |
||||
|
deriving (Show, Eq) |
||||
|
|
||||
|
data VideoID = VideoID { videoID :: T.Text } |
||||
|
deriving (Show, Eq) |
||||
|
|
||||
|
data Thumbnail = Thumbnail { thumbnail :: URL } |
||||
|
deriving (Show, Eq) |
||||
|
|
||||
|
data JSearchResult = JSearchResult { |
||||
|
jvideoID :: VideoID, |
||||
|
snippet :: Snippet |
||||
|
} |
||||
|
deriving (Show, Eq) |
||||
|
|
||||
|
data Snippet = Snippet { |
||||
|
jtitle :: T.Text, |
||||
|
jdescription :: T.Text, |
||||
|
jthumbnails :: Thumbnail |
||||
|
} |
||||
|
deriving (Show, Eq) |
||||
|
|
||||
|
data JItems = JItems [JSearchResult] |
||||
|
deriving (Show) |
||||
|
|
||||
|
data SearchResult = SearchResult { |
||||
|
title :: T.Text, |
||||
|
description :: T.Text, |
||||
|
url :: T.Text, |
||||
|
thumb :: T.Text |
||||
|
} |
||||
|
deriving (Show, Eq) |
||||
|
|
||||
|
instance FromJSON URL where |
||||
|
parseJSON (Object v) = URL <$> v .: "url" |
||||
|
|
||||
|
instance FromJSON Thumbnail where |
||||
|
parseJSON (Object v) = Thumbnail <$> v .: "default" |
||||
|
|
||||
|
instance FromJSON VideoID where |
||||
|
parseJSON (Object v) = VideoID <$> |
||||
|
v .: "videoId" |
||||
|
|
||||
|
instance FromJSON Snippet where |
||||
|
parseJSON (Object v) = Snippet <$> |
||||
|
v .: "title" <*> |
||||
|
v .: "description" <*> |
||||
|
v .: "thumbnails" |
||||
|
|
||||
|
instance FromJSON JSearchResult where |
||||
|
parseJSON (Object v) = JSearchResult <$> |
||||
|
(v .: "id") <*> |
||||
|
(v .: "snippet") |
||||
|
|
||||
|
instance FromJSON JItems where |
||||
|
parseJSON (Object v) = JItems <$> v .: "items" |
||||
|
|
||||
|
makeURL :: T.Text -> T.Text |
||||
|
makeURL vid = "https://youtube.com/watch?v=" `T.append` vid |
||||
|
|
||||
|
getItems :: BL.ByteString -> [SearchResult] |
||||
|
getItems str = |
||||
|
let parsed = decode str :: Maybe JItems |
||||
|
in maybe [] getResults parsed |
||||
|
where |
||||
|
getResults (JItems xs) = map getResult xs |
||||
|
getResult x = SearchResult (jtitle . snippet $ x) |
||||
|
(jdescription . snippet $ x) |
||||
|
(makeURL . videoID . jvideoID $ x) |
||||
|
(jurl . thumbnail . jthumbnails . snippet $ x) |
||||
|
|
||||
|
cid = "571126085022-7ash7a48cdao0tesqe66ghtime34cfvo.apps.googleusercontent.com" |
||||
|
secret = "FjgeOtSZoJgU87FbuwJf2vwj" |
||||
|
file = "./tokens.txt" |
||||
|
baseURI = "https://www.googleapis.com/youtube/v3/" |
||||
|
|
||||
|
searchRequest :: String -> String -> String |
||||
|
searchRequest keyword accessTok = |
||||
|
baseURI ++ |
||||
|
"search?part=snippet&q=" ++ |
||||
|
keyword ++ |
||||
|
"&type=video&access_token=" ++ |
||||
|
accessTok |
||||
|
|
||||
|
|
||||
|
|
||||
|
main = do |
||||
|
let client = OAuth2Client { clientId = cid, clientSecret = secret } |
||||
|
permissionUrl = formUrl client ["https://www.googleapis.com/auth/youtube"] |
||||
|
b <- doesFileExist file |
||||
|
unless b $ do |
||||
|
putStrLn $ "Load this URL: " ++ show permissionUrl |
||||
|
putStrLn "Please paste the verification code: " |
||||
|
authcode <- getLine |
||||
|
tokens <- exchangeCode client authcode |
||||
|
putStrLn $ "Received access token: " ++ show (accessToken tokens) |
||||
|
tokens2 <- refreshTokens client tokens |
||||
|
putStrLn $ "As a test, refreshed token: " ++ show (accessToken tokens2) |
||||
|
writeFile file (show tokens2) |
||||
|
accessTok <- fmap (accessToken . read) (readFile file) |
||||
|
findTracks accessTok "Foo+Fighters" |
||||
|
|
||||
|
findTracks accessTok term = do |
||||
|
response <- simpleHttp $ searchRequest term accessTok |
||||
|
return $ getItems response |
||||
|
|
||||
|
search :: String -> [T.Text] |
||||
|
search term = undefined |
@ -0,0 +1,25 @@ |
|||||
|
-- This is a Cabal package environment file. |
||||
|
-- THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY. |
||||
|
-- Please create a 'cabal.config' file in the same directory |
||||
|
-- if you want to change the default settings for this sandbox. |
||||
|
|
||||
|
|
||||
|
local-repo: /home/wes/youtube-mpd/.cabal-sandbox/packages |
||||
|
logs-dir: /home/wes/youtube-mpd/.cabal-sandbox/logs |
||||
|
world-file: /home/wes/youtube-mpd/.cabal-sandbox/world |
||||
|
user-install: False |
||||
|
package-db: /home/wes/youtube-mpd/.cabal-sandbox/x86_64-linux-ghc-7.10.1-packages.conf.d |
||||
|
build-summary: /home/wes/youtube-mpd/.cabal-sandbox/logs/build.log |
||||
|
|
||||
|
install-dirs |
||||
|
prefix: /home/wes/youtube-mpd/.cabal-sandbox |
||||
|
bindir: $prefix/bin |
||||
|
libdir: $prefix/lib |
||||
|
libsubdir: $abi/$pkgkey |
||||
|
libexecdir: $prefix/libexec |
||||
|
datadir: $prefix/share |
||||
|
datasubdir: $abi/$pkgid |
||||
|
docdir: $datadir/doc/$abi/$pkgid |
||||
|
htmldir: $docdir/html |
||||
|
haddockdir: $htmldir |
||||
|
sysconfdir: $prefix/etc |
@ -0,0 +1,11 @@ |
|||||
|
Name: youtube-mpd |
||||
|
Version: 0.1 |
||||
|
Cabal-Version: >= 1.2 |
||||
|
License: GPL-3 |
||||
|
Author: Wesley Kerfoot |
||||
|
Synopsis: play youtube music easier |
||||
|
Build-Type: Simple |
||||
|
|
||||
|
Executable youtube-mpd |
||||
|
Build-Depends: base, handa-gdata, process, directory, http-conduit, bytestring, aeson, containers, text |
||||
|
Main-Is: Main.hs |
Reference in new issue