module Twitch exposing ( Playlist , Video , fetchPlaylists , playlistMiniatureUrl , videoId , videoMiniatureUrl , videoName ) import Html.Parser import Http import Iso8601 import Json.Decode as Decode import Task exposing (Task) import Time type alias Playlist = { url : String , name : String , videos : List Video } type alias Video = { name : String , url : String , duration : Int , date : Maybe Time.Posix } videoName : Video -> String videoName video = String.join "/" (List.drop 3 (String.split "/" video.url)) videoId : Video -> String videoId video = String.dropLeft 1 video.url |> String.replace "/" "-" get : { url : String, resolver : Http.Resolver x a } -> Task x a get { url, resolver } = Http.task { body = Http.emptyBody , headers = [] , method = "GET" , resolver = resolver , timeout = Nothing , url = url } playlistMiniatureUrl : Playlist -> String playlistMiniatureUrl playlist = case List.head playlist.videos of Just v -> v.url ++ "miniature-050.png" _ -> "" videoMiniatureUrl : Video -> String videoMiniatureUrl video = video.url ++ "miniature-050.png" sortPlaylist : Playlist -> Playlist sortPlaylist playlist = { playlist | videos = List.sortBy .url playlist.videos |> List.reverse } sortPlaylists : List Playlist -> List Playlist sortPlaylists playlists = List.sortBy .url (List.map sortPlaylist playlists) |> List.reverse fetchPlaylists : Task x ( List Playlist, Time.Zone ) fetchPlaylists = fetchPlaylistPath |> Task.andThen fetchPlaylistsMapper |> Task.map sortPlaylists |> Task.andThen fetchTimezone fetchTimezone : List Playlist -> Task x ( List Playlist, Time.Zone ) fetchTimezone playlists = Task.map (\zone -> ( playlists, zone )) Time.here fetchPlaylistPath : Task x (List String) fetchPlaylistPath = get { url = "/videos" , resolver = Http.stringResolver parseHrefs } fetchPlaylist : String -> Task x Playlist fetchPlaylist name = fetchPlaylistName name |> Task.andThen (\a -> fetchPlaylistVideoPaths name |> Task.andThen (\c -> fetchVideos name c) |> Task.map (Playlist name a) ) fetchVideo : String -> String -> Task x Video fetchVideo playlist video = let url = "/videos/" ++ playlist ++ video in get { url = url ++ "description.json" , resolver = Http.stringResolver (parseVideo url) } fetchVideos : String -> List String -> Task x (List Video) fetchVideos playlist videos = Task.sequence (List.map (fetchVideo playlist) videos) fetchPlaylistName : String -> Task x String fetchPlaylistName path = get { url = "/videos/" ++ path ++ "description.json" , resolver = Http.stringResolver parsePlaylistName } fetchPlaylistVideoPaths : String -> Task x (List String) fetchPlaylistVideoPaths name = get { url = "/videos/" ++ name , resolver = Http.stringResolver parseHrefs } parseVideo : String -> Http.Response String -> Result x Video parseVideo url result = case result of Http.GoodStatus_ _ content -> case Decode.decodeString (decodeVideo url) content of Ok v -> Ok v _ -> Ok { name = "", url = url, duration = 0, date = Nothing } _ -> Ok { name = "", url = url, duration = 0, date = Nothing } fetchPlaylistsMapper : List String -> Task x (List Playlist) fetchPlaylistsMapper names = Task.sequence (List.map fetchPlaylist names) parsePlaylistName : Http.Response String -> Result x String parsePlaylistName result = case result of Http.GoodStatus_ _ content -> case Decode.decodeString decodePlaylistName content of Ok p -> Ok p _ -> Ok "" _ -> Ok "" parseHrefs : Http.Response String -> Result x (List String) parseHrefs result = case result of Http.GoodStatus_ _ content -> let withoutDoctype = if String.startsWith " List.drop 1 |> String.join "\n" else content decoded = Html.Parser.run withoutDoctype hrefs = Result.map findHrefs decoded in case hrefs of Ok h -> Ok h _ -> Ok [] _ -> Ok [] findHrefsAux : List String -> Html.Parser.Node -> List String findHrefsAux acc node = case node of Html.Parser.Element string (( key, value ) :: t) nodes -> let newAcc = if key == "href" && String.endsWith "/" value && value /= "../" then value :: acc else acc in findHrefsAux newAcc (Html.Parser.Element string t nodes) Html.Parser.Element string [] (h :: t) -> let attrs = findHrefsAux [] h in findHrefsAux (acc ++ attrs) (Html.Parser.Element string [] t) _ -> acc findHrefs : List Html.Parser.Node -> List String findHrefs x = findHrefsAux [] (Html.Parser.Element "" [] x) decodePlaylistName : Decode.Decoder String decodePlaylistName = Decode.field "title" Decode.string decodeVideo : String -> Decode.Decoder Video decodeVideo url = Decode.map3 (\x y -> Video x url y) (Decode.field "title" Decode.string) (Decode.map Basics.round (Decode.field "duration" Decode.float)) (Decode.maybe (Decode.field "date" Iso8601.decoder))