bronyradiogermany-streaming (empty) → 1.0.0.0
raw patch · 5 files changed
+291/−0 lines, 5 filesdep +aesondep +basedep +bronyradiogermany-commonsetup-changed
Dependencies added: aeson, base, bronyradiogermany-common, bytestring, case-insensitive, http-types, mtl, streaming, streaming-bytestring, streaming-utils, text, time, uuid
Files
- ChangeLog.md +5/−0
- LICENSE +30/−0
- Setup.hs +2/−0
- bronyradiogermany-streaming.cabal +79/−0
- src/Network/Radio/BronyRadioGermany/Streaming.hs +175/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for bronyradiogermany-streaming++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2018, Marvin Cohrs++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Marvin Cohrs nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bronyradiogermany-streaming.cabal view
@@ -0,0 +1,79 @@+-- Initial bronyradiogermany-streaming.cabal generated by cabal init. For +-- further documentation, see http://haskell.org/cabal/users-guide/++-- The name of the package.+name: bronyradiogermany-streaming++-- The package version. See the Haskell package versioning policy (PVP) +-- for standards guiding when and how versions should be incremented.+-- https://wiki.haskell.org/Package_versioning_policy+-- PVP summary: +-+------- breaking API changes+-- | | +----- non-breaking API additions+-- | | | +--- code changes with no API change+version: 1.0.0.0++-- A short (one-line) description of the package.+synopsis: Streaming interface for the BronyRadioGermany API.++-- A longer description of the package.+description: This is a streaming interface for the API of BronyRadioGermany. Features: get the currently played song, the entire song history, the AutoDj track list, vote statistics, and the raw audio streams; it can also post up- and downvotes, as well as song requests. If you prefer conduit over streaming, please have a look at bronyradiogermany-conduit.++-- The license under which the package is released.+license: BSD3++-- The file containing the license text.+license-file: LICENSE++-- The package author(s).+author: Marvin Cohrs++-- An email address to which users can send suggestions, bug reports, and +-- patches.+maintainer: darcs@m.doomanddarkness.eu++-- A copyright notice.+-- copyright: ++category: Network++build-type: Simple++-- Extra files to be distributed with the package, such as examples or a +-- README.+extra-source-files: ChangeLog.md++-- Constraint on the version of Cabal needed to build this package.+cabal-version: >=1.10+++library+ -- Modules exported by the library.+ exposed-modules: Network.Radio.BronyRadioGermany.Streaming+ + -- Modules included in this library but not exported.+ -- other-modules: + + -- LANGUAGE extensions used by modules in this package.+ -- other-extensions: + + -- Other library packages from which modules are imported.+ build-depends: base >=4.9 && <4.10,+ streaming,+ streaming-bytestring,+ streaming-utils,+ bronyradiogermany-common,+ uuid,+ text,+ aeson,+ time,+ mtl,+ bytestring,+ case-insensitive,+ http-types+ + -- Directories containing source files.+ hs-source-dirs: src+ + -- Base language which the package is written in.+ default-language: Haskell2010+
+ src/Network/Radio/BronyRadioGermany/Streaming.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.Radio.BronyRadioGermany.Streaming+ (-- * Simple information requests+ getStreamInfo,+ getTrackInfo,+ -- * Track streams+ getTrackList,+ getHistory,+ -- * Voting+ postUpVote,+ postUpVoteFresh,+ postDownVote,+ postDownVoteFresh,+ -- * AutoDJ requests+ postAutoDJRequest,+ postAutoDJTrackRequest,+ -- * Audio streams+ radioStream,+ -- * Errors+ AutoDjRequestError(..))+ where++import Control.Exception+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans+import Data.Aeson hiding (encode)+import Data.Time.Clock+import qualified Data.Text as T+import Data.Text.Encoding+import Streaming+import qualified Streaming.Prelude as S+import qualified Data.ByteString.Streaming.Char8 as Q+import Data.ByteString.Streaming.HTTP+import Data.ByteString.Streaming.Aeson+import Network.Radio.BronyRadioGermany.Types as BRG+import Network.Radio.BronyRadioGermany.URI as BRG+import Network.HTTP.Types.URI+import Data.UUID+import Data.UUID.V4+import qualified Data.ByteString as BS+import qualified Data.CaseInsensitive as CI++request :: (FromJSON a, MonadIO m) => T.Text -> m (Maybe a)+request uri = handleResponse <$> request' uri+ where handleResponse (Just (Successful a _ _ _)) = Just a+ handleResponse _ = Nothing++request' :: (FromJSON a, MonadIO m) => T.Text -> m (Maybe (BRG.Response a))+request' uri = liftIO $ do+ m <- newManager tlsManagerSettings+ req <- parseRequest (T.unpack uri)+ withHTTP req m $ \resp ->+ S.head_ $+ decoded $+ responseBody resp++multipageRequest :: (FromJSON a, MonadIO m) => Int -> (Int -> T.Text) -> Stream (Of a) m ()+multipageRequest page uri = do+ mr <- liftIO $ request' $ uri page+ case mr of+ Nothing -> return ()+ Just (Successful rs (Just next) _ _) -> do+ S.each rs `const` head rs -- head forces rs to be a list+ multipageRequest (page+1) uri+ Just (Successful rs Nothing _ _) -> S.each rs+ Just (Errorful msg) -> fail (T.unpack msg)++-- | Get information about the given channel+getStreamInfo :: MonadIO m => Mountpoint -> m (Maybe StreamInfo)+getStreamInfo = request . brgStreamInfoURI++-- | Get information about the given track+getTrackInfo :: (HasTrackId t, MonadIO m) => t -> m (Maybe TrackInfo)+getTrackInfo = request . brgTrackInfoURI++-- | Get the AutoDJ's track list+getTrackList :: MonadIO m => Maybe T.Text -- ^ Title mask (use % as a wildcard)+ -> Maybe T.Text -- ^ Artist mask (use % as a wildcard)+ -> Stream (Of Track) m ()+getTrackList mtitle martist = do+ mlist <- request $ brgTrackListURI mtitle martist+ case mlist of+ Just list -> S.each list `const` head list -- head forces list to be a list+ Nothing -> return ()++-- | Get the song history of the main channel+getHistory :: MonadIO m => Maybe UTCTime -- ^ start time+ -> Maybe UTCTime -- ^ end time+ -> Maybe T.Text -- ^ title mask (use % as a wildcard)+ -> Maybe T.Text -- ^ artist mask+ -> Stream (Of HistoryItem) m ()+getHistory mstart mend mtitle martist = do+ end' <- case mend of+ Just end -> return end+ Nothing -> liftIO getCurrentTime+ multipageRequest 1 $ \i -> brgHistoryURI i mstart (Just end') mtitle martist++-- | Check Vote status for a given UUID and mountpoint+getVoteStatus :: MonadIO m => UUID -> Mountpoint -> m (Maybe VoteCheckResult)+getVoteStatus uuid mp = request $ brgVoteCheckURI uuid mp++-- | Post an upvote using the given UUID (caution: even though this is logically a post, the HTTP method is GET)+postUpVote :: MonadIO m => UUID -> Mountpoint -> m (Maybe VoteResult)+postUpVote uuid mp = request $ brgUpVoteURI uuid mp++-- | Post a downvote using the given UUID (caution: even though this is logically a post, the HTTP method is GET)+postDownVote :: MonadIO m => UUID -> Mountpoint -> m (Maybe VoteResult)+postDownVote uuid mp = request $ brgDownVoteURI uuid mp++-- | Post an upvote using a fresh UUID+postUpVoteFresh :: MonadIO m => Mountpoint -> m (Maybe VoteResult)+postUpVoteFresh mp = do+ uuid <- liftIO nextRandom+ postUpVote uuid mp++-- | Post a downvote using a fresh UUID+postDownVoteFresh :: MonadIO m => Mountpoint -> m (Maybe VoteResult)+postDownVoteFresh mp = do+ uuid <- liftIO nextRandom+ postDownVote uuid mp++-- | Thrown if the AutoDJ song request has been rejected, i.e. due to quota violation+data AutoDjRequestError = AutoDjRequestRejected deriving (Eq,Show,Read)++instance Exception AutoDjRequestError++-- | Request a song from the AutoDJ. Throws AutoDjRequestRejected if the request is rejected by the server, i.e. the quota is violated+postAutoDJRequest :: (MonadIO m,MonadThrow m) => T.Text -- ^ your nickname+ -> T.Text -- ^ track title+ -> T.Text -- ^ track artist+ -> m ()+postAutoDJRequest nick title artist = do+ let body = renderQuery False+ [("title", Just (encodeUtf8 title)),+ ("artist", Just (encodeUtf8 artist)),+ ("nickname", Just (encodeUtf8 nick))]+ req <- parseRequest $ T.unpack brgAutoDJRequestURI+ m <- liftIO $ newManager tlsManagerSettings+ uuid <- liftIO nextRandom+ let req' = req{+ method = "POST",+ requestBody = RequestBodyBS body}+ req'' = addRequestHeader "cookie" ("brg-player-voting-uuid="<>toASCIIBytes uuid) $+ addRequestHeader "content-type" "application/x-www-form-urlencoded; charset=UTF-8" $+ addRequestHeader "referer" "https://www.bronyradiogermany.com/request-v2/include/request.php" $+ addRequestHeader "origin" "https://www.bronyradiogermany.com" $+ addRequestHeader "x-requested-with" "XMLHttpRequest" req'+ resp <- liftIO $ withHTTP req'' m $ \resp -> Q.toStrict_ $ responseBody resp+ if resp == "Request wurde eingereicht."+ then return ()+ else throwM AutoDjRequestRejected++-- | Request a song from the AutoDJ+postAutoDJTrackRequest :: (MonadIO m, MonadThrow m, HasTrack t)+ => T.Text -- ^ your nickname+ -> t -- ^ requested track+ -> m ()+postAutoDJTrackRequest nick track =+ postAutoDJRequest nick (trackTitle track) (trackArtist track)++-- | Raw radio stream. Use an audio library to extract frames, or pipe it into an audio device+radioStream :: MonadResource m => Mountpoint -> Q.ByteString m ()+radioStream mp = do+ req <- parseRequest $ T.unpack $ brgStreamURI mp+ m <- liftIO $ newManager tlsManagerSettings + resp <- lift $ http req m+ responseBody resp++addRequestHeader :: BS.ByteString -> BS.ByteString -> Request -> Request+addRequestHeader key val req =+ let key' = CI.mk key+ h = (key', val)+ in req{requestHeaders= h : requestHeaders req}