packages feed

discord-haskell-voice 2.2.2 → 2.3.0

raw patch · 12 files changed

+84/−56 lines, 12 filesdep +microlensdep +microlens-thdep −lensdep ~conduitdep ~discord-haskellPVP ok

version bump matches the API change (PVP)

Dependencies added: microlens, microlens-th

Dependencies removed: lens

Dependency ranges changed: conduit, discord-haskell

API changes (from Hackage documentation)

- Discord.Internal.Types.VoiceUDP: _MalformedPacket :: Prism' VoiceUDPPacket ByteString
- Discord.Internal.Types.VoiceUDP: _SpeakingData :: Prism' VoiceUDPPacket ByteString
- Discord.Internal.Types.VoiceUDP: _SpeakingDataEncrypted :: Prism' VoiceUDPPacket (ByteString, ByteString)
- Discord.Internal.Types.VoiceUDP: _SpeakingDataEncryptedExtra :: Prism' VoiceUDPPacket (ByteString, ByteString)
- Discord.Internal.Types.VoiceUDP: _UnknownPacket :: Prism' VoiceUDPPacket ByteString
- Discord.Internal.Types.VoiceWebsocket: _ClientDisconnect :: Prism' VoiceWebsocketReceivable UserId
- Discord.Internal.Types.VoiceWebsocket: _HeartbeatAck :: Prism' VoiceWebsocketReceivable Int
- Discord.Internal.Types.VoiceWebsocket: _ParseError :: Prism' VoiceWebsocketReceivable Text
- Discord.Internal.Types.VoiceWebsocket: _Reconnect :: Prism' VoiceWebsocketReceivable ()
- Discord.Internal.Types.VoiceWebsocket: _Resumed :: Prism' VoiceWebsocketReceivable ()
- Discord.Internal.Types.VoiceWebsocket: _SpeakingR :: Prism' VoiceWebsocketReceivable SpeakingPayload
- Discord.Internal.Types.VoiceWebsocket: _UnknownOPCode :: Prism' VoiceWebsocketReceivable (Integer, Object)
+ Discord.Voice: playYouTubeWith :: String -> (String -> [String]) -> String -> String -> Voice ()
+ Discord.Voice: playYouTubeWith' :: String -> (String -> [String]) -> String -> String -> ConduitT ByteString ByteString (ResourceT DiscordHandler) () -> Voice ()
- Discord.Internal.Types.VoiceUDP: _IPDiscovery :: Prism' VoiceUDPPacket (Integer, Text, Integer)
+ Discord.Internal.Types.VoiceUDP: _IPDiscovery :: Traversal' VoiceUDPPacket (Integer, Text, Integer)
- Discord.Internal.Types.VoiceWebsocket: _Hello :: Prism' VoiceWebsocketReceivable Int
+ Discord.Internal.Types.VoiceWebsocket: _Hello :: Traversal' VoiceWebsocketReceivable Int
- Discord.Internal.Types.VoiceWebsocket: _Ready :: Prism' VoiceWebsocketReceivable ReadyPayload
+ Discord.Internal.Types.VoiceWebsocket: _Ready :: Traversal' VoiceWebsocketReceivable ReadyPayload
- Discord.Internal.Types.VoiceWebsocket: _SessionDescription :: Prism' VoiceWebsocketReceivable (Text, [Word8])
+ Discord.Internal.Types.VoiceWebsocket: _SessionDescription :: Traversal' VoiceWebsocketReceivable (Text, [Word8])

Files

ChangeLog.md view
@@ -2,6 +2,13 @@  ## Unreleased changes ++## 2.3.0++- Export `playYouTubeWith` and `playYoUTubeWith'` from `Discord.Voice`.+- Update `discord-haskell` dependency bounds to `>= 1.12.0 && <= 1.13.0`.+- Migrate from `lens` to `microlens`, following the `opus` package doing the same.+ ## 2.2.2  - Update `discord-haskell` dependency to 1.12.0
README.md view
@@ -1,7 +1,7 @@ # discord-haskell-voice -![hackage version](https://img.shields.io/hackage/v/discord-haskell-voice?color=%235e5184)-![discord-haskell version dependency](https://img.shields.io/badge/discord--haskell%20ver.-1.12.0-lightblue)+[![hackage version](https://img.shields.io/hackage/v/discord-haskell-voice?color=%235e5184)](https://hackage.haskell.org/package/discord-haskell-voice)+[![discord-haskell version dependency](https://img.shields.io/badge/discord--haskell-%3E=1.12.0%20%26%26%20%3C=1.13.0-lightblue)](https://hackage.haskell.org/package/discord-haskell)  Welcome to `discord-haskell-voice`! This library provides you with a high-level interface for interacting with Discord's Voice API, building on top of the@@ -26,7 +26,7 @@ rickrollHalfVolume :: Channel -> DiscordHandler () rickrollHalfVolume c@(ChannelVoice {}) = do     void $ runVoice $ do-        join (channelGuild c) (channelId c)        +        join (channelGuild c) (channelId c)         let halfAmplitude = awaitForever $ \current ->                 yield $ round $ fromIntegral current * 0.5         playYouTube' "rickroll" $ packInt16C .| halfAmplitude .| unpackInt16C@@ -36,17 +36,18 @@ ## Requirements  - The library uses [`saltine`](https://github.com/tel/saltine) for encryption-and decryption of audio packets. This requires the appropriate libraries to be-installed on your system. See their README for information.+and decryption of audio packets. This requires libsodium to be installed on+your system. See their README for information. - The library requires Opus libraries to be installed on your system. The `libopus-dev` package available on package repositories should be sufficient-on most \*nix systems. Windows is unexplored yet (WSL works).+on most \*nix systems. The `opus` brew package suffices on Mac. Windows+is unexplored yet (WSL works). - If you are to use any variants of `playFile`, `playYouTube`, you will need FFmpeg installed. To specify a custom executable name, see the `-With` function variants. - If you are to use any variants of `playYouTube`, you will additionally need youtube-dl installed. This is used to get the stream URL to pass to FFmpeg. To-specify a custom executable name, use `playYouTubeWith`.+specify a custom executable name (such as yt-dlp), use `playYouTubeWith`.  ## Features @@ -54,8 +55,8 @@  - Can join/leave Discord voice channels. It is possible to join multiple of them simultaneously (one per sever) and stream different contents to each.-- It is also possible for many voice channels (across many servers) and play the-same content, radio/subscriber-style.+- It is also possible to join many voice channels (across many servers) and play+the same content, radio/subscriber-style. - You can play arbitrary PCM audio, arbitrary audio (with FFmpeg), and arbitrary internet audio (with youtube-dl). - You can transform audio arbitrarily using Conduit.@@ -84,8 +85,8 @@ # --- myproject.cabal <truncated>  build-depends:       base >=4.7 && <5-    , discord-haskell ==1.12.0-    , discord-haskell-voice ==2.2.2+    , discord-haskell ==1.13.0+    , discord-haskell-voice ==2.3.0 ```  ### Stack@@ -95,16 +96,16 @@ ``` # --- stack.yaml <truncated> extra-deps:-- discord-haskell-1.12.0-- discord-haskell-voice-2.2.2+- discord-haskell-1.13.0+- discord-haskell-voice-2.3.0 ```  ``` # --- package.yaml <truncated> dependencies: - base >= 4.7 && < 5-- discord-haskell == 1.12.0-- discord-haskell-voice == 2.2.2+- discord-haskell == 1.13.0+- discord-haskell-voice == 2.3.0 ```  ## Documentation
discord-haskell-voice.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           discord-haskell-voice-version:        2.2.2+version:        2.3.0 synopsis:       Voice support for discord-haskell. description:    Supplementary library to discord-haskell. See the project README on GitHub for more information. <https://github.com/yutotakano/discord-haskell-voice> category:       Network@@ -49,9 +49,10 @@     , base >=4.7 && <5     , binary ==0.8.*     , bytestring >=0.10.12.0 && <0.11-    , conduit ==1.3.4.1-    , discord-haskell ==1.12.0-    , lens >=4.19.2 && <5+    , conduit ==1.3.4.2+    , discord-haskell >=1.12.0 && <=1.13.0+    , microlens >=0.4.13.0+    , microlens-th >=0.4.3.10     , mtl ==2.2.2     , network >=3.1.1.1 && <3.2     , opus ==0.1.0.0@@ -79,10 +80,11 @@     , base >=4.7 && <5     , binary ==0.8.*     , bytestring >=0.10.12.0 && <0.11-    , conduit ==1.3.4.1-    , discord-haskell ==1.12.0+    , conduit ==1.3.4.2+    , discord-haskell >=1.12.0 && <=1.13.0     , discord-haskell-voice-    , lens >=4.19.2 && <5+    , microlens >=0.4.13.0+    , microlens-th >=0.4.3.10     , mtl ==2.2.2     , network >=3.1.1.1 && <3.2     , optparse-applicative >=0.15.1.0 && <0.17@@ -113,10 +115,11 @@     , base >=4.7 && <5     , binary ==0.8.*     , bytestring >=0.10.12.0 && <0.11-    , conduit ==1.3.4.1-    , discord-haskell ==1.12.0+    , conduit ==1.3.4.2+    , discord-haskell >=1.12.0 && <=1.13.0     , discord-haskell-voice-    , lens >=4.19.2 && <5+    , microlens >=0.4.13.0+    , microlens-th >=0.4.3.10     , mtl ==2.2.2     , network >=3.1.1.1 && <3.2     , opus ==0.1.0.0
examples/BasicMusicBot.hs view
@@ -106,7 +106,8 @@                     let adjustVolume = awaitForever $ \current -> do                             v' <- liftIO $ readTVarIO volume                             yield $ round $ fromIntegral current * (fromIntegral v' / 100)-                    playYouTube' x $ packInt16C .| adjustVolume .| unpackInt16C+                    playYouTubeWith' "ffmpeg" defaultFFmpegArgs "yt-dlp" x $+                        packInt16C .| adjustVolume .| unpackInt16C      case result of         Left e -> liftIO $ print e >> pure ()
src/Discord/Internal/Types/VoiceCommon.hs view
@@ -32,7 +32,7 @@ import Control.Concurrent ( Chan, MVar, ThreadId ) import Control.Concurrent.BoundedChan qualified as Bounded import Control.Exception.Safe ( Exception, MonadMask, MonadCatch, MonadThrow )-import Control.Lens ( makeFields )+import Lens.Micro.TH ( makeFields ) import Control.Monad.Except import Control.Monad.Reader import Data.ByteString qualified as B
src/Discord/Internal/Types/VoiceUDP.hs view
@@ -26,7 +26,7 @@ -} module Discord.Internal.Types.VoiceUDP where -import Control.Lens ( makePrisms )+import Lens.Micro import Data.Binary.Get import Data.Binary.Put import Data.Binary@@ -47,6 +47,10 @@     | MalformedPacket BL.ByteString     deriving (Show, Eq) +_IPDiscovery :: Traversal' VoiceUDPPacket (Integer, T.Text, Integer)+_IPDiscovery f (IPDiscovery ssrc ip port) = (\(a, b, c) -> IPDiscovery a b c) <$> f (ssrc, ip, port)+_IPDiscovery f packet = pure packet+ data VoiceUDPPacketHeader     = Header Word8 Word8 Word16 Word32 Word32  @@ -107,4 +111,4 @@         putLazyByteString a     put (MalformedPacket a) = putLazyByteString a -$(makePrisms ''VoiceUDPPacket)+-- $(makePrisms ''VoiceUDPPacket)
src/Discord/Internal/Types/VoiceWebsocket.hs view
@@ -27,7 +27,7 @@ module Discord.Internal.Types.VoiceWebsocket where  import Control.Applicative ( (<|>) )-import Control.Lens ( makePrisms )+import Lens.Micro import Data.Aeson import Data.Aeson.Types import Data.Text qualified as T@@ -50,6 +50,19 @@     | Reconnect                                     -- Internal use     deriving (Show, Eq) +_Ready :: Traversal' VoiceWebsocketReceivable ReadyPayload+_Ready f (Ready rp) = Ready <$> f rp+_Ready f rp = pure rp++_SessionDescription :: Traversal' VoiceWebsocketReceivable (T.Text, [Word8])+_SessionDescription f (SessionDescription t bytes) = uncurry SessionDescription <$> f (t, bytes)+_SessionDescription f sd = pure sd++_Hello :: Traversal' VoiceWebsocketReceivable Int+_Hello f (Hello a) = Hello <$> f a+_Hello f a = pure a++ data VoiceWebsocketSendable     = Identify IdentifyPayload                      -- Opcode 0     | SelectProtocol SelectProtocolPayload          -- Opcode 1@@ -196,4 +209,4 @@             ]         ] -$(makePrisms ''VoiceWebsocketReceivable)+-- $(makePrisms ''VoiceWebsocketReceivable)
src/Discord/Internal/Voice.hs view
@@ -50,7 +50,8 @@     ) import Control.Concurrent.BoundedChan qualified as Bounded import Control.Exception.Safe ( finally, bracket, throwTo, catch, throwIO )-import Control.Lens+import Lens.Micro+import Lens.Micro.Extras (view) import Control.Monad.Reader ( ask, liftIO, runReaderT ) import Control.Monad.Except ( runExceptT, throwError ) import Control.Monad.Trans ( lift )@@ -149,8 +150,8 @@         -- There is no way to prevent it, so as a consequence, the bot may         -- linger in the voice call for a few minutes after the bot program is         -- killed.-        mapMOf_ (traverse . guildId) (\x -> updateStatusVoice x Nothing False False) finalState-        mapMOf_ (traverse . websocket . _1) (liftIO . killWkThread) finalState+        traverseOf_ (traverse . guildId) (\x -> updateStatusVoice x Nothing False False) finalState+        traverseOf_ (traverse . websocket . _1) (liftIO . killWkThread) finalState      pure result @@ -303,7 +304,7 @@ updateSpeakingStatus micStatus = do     h <- (^. voiceHandles) <$> ask     handles <- liftIO $ readMVar h-    flip (mapMOf_ traverse) handles $ \handle ->+    flip (traverseOf_ traverse) handles $ \handle ->         liftIO $ writeChan (handle ^. websocket . _2 . _2) $ Speaking $ SpeakingPayload             { speakingPayloadMicrophone = micStatus             , speakingPayloadSoundshare = False@@ -383,9 +384,9 @@     encoder <- liftIO $ opusEncoderCreate enCfg     loop encoder   where-    enCfg = _EncoderConfig # (opusSR48k, True, app_audio)+    enCfg = mkEncoderConfig opusSR48k True app_audio     -- 1275 is the max bytes an opus 20ms frame can have-    streamCfg = _StreamConfig # (enCfg, 48*20, 1276)+    streamCfg = mkStreamConfig enCfg (48*20) 1276     loop encoder = await >>= \case         Nothing -> do             -- Send at least 5 blank frames (20ms * 5 = 100 ms)
src/Discord/Internal/Voice/CommonUtils.hs view
@@ -24,7 +24,7 @@  import Control.Concurrent import Control.Concurrent.Async ( race )-import Control.Lens+import Lens.Micro import Data.Text qualified as T import Data.Time.Clock.POSIX import Data.Time
src/Discord/Internal/Voice/UDPLoop.hs view
@@ -47,7 +47,7 @@     ) import Control.Concurrent.BoundedChan qualified as Bounded import Control.Exception.Safe ( handle, SomeException, finally, try, bracket )-import Control.Lens+import Lens.Micro import Control.Monad.IO.Class ( MonadIO ) import Data.Binary ( encode, decode ) import Data.ByteString.Lazy qualified as BL@@ -288,8 +288,8 @@  decodeOpusData :: B.ByteString -> IO B.ByteString decodeOpusData bytes = do-    let deCfg = _DecoderConfig # (opusSR48k, True)-    let deStreamCfg = _DecoderStreamConfig # (deCfg, 48*20, 0)+    let deCfg = mkDecoderConfig opusSR48k True+    let deStreamCfg = mkDecoderStreamConfig deCfg (48*20) 0     decoder <- opusDecoderCreate deCfg     decoded <- opusDecode decoder deStreamCfg bytes     pure decoded
src/Discord/Internal/Voice/WebsocketLoop.hs view
@@ -48,7 +48,7 @@     , readMVar     ) import Control.Exception.Safe ( try, tryAsync, SomeException, finally, handle )-import Control.Lens+import Lens.Micro import Control.Monad ( forever, guard ) import Control.Monad.Except ( runExceptT, ExceptT (ExceptT), lift ) import Control.Monad.IO.Class ( liftIO )@@ -125,9 +125,7 @@     -- @opts@ to report back to runVoice, so it can be killed in the     -- future.     websocketFsm WSStart retries udpInfo = do-        -- Use of tryAsync (unsafe, as it catches asynchronous exceptions) is-        -- justified here, since it will only log, then go to WSClosed.-        next <- tryAsync $ connect (opts ^. endpoint) $ \conn -> do+        next <- try $ connect (opts ^. endpoint) $ \conn -> do             (libSends, sendTid) <- flip (setupSendLoop conn) log $ opts ^. wsHandle . _2              result <- flip finally (killThread sendTid) $ runExceptT $ do@@ -216,18 +214,16 @@         -- Connection is now closed.         case next :: Either SomeException WSState of             Left e -> do-                (✍!) log $ "could not connect due to an exception: " <>+                (✍!) log $ "connection terminated due to a synchronous exception: " <>                     (tshow e)                 writeChan (opts ^. wsHandle . _1) $ Left $                     VoiceWebsocketCouldNotConnect-                        "could not connect due to an exception"+                        "connection terminated due to a synchronous exception"                 websocketFsm WSClosed 0 udpInfo             Right n -> websocketFsm n 0 udpInfo      websocketFsm WSResume retries udpInfo = do-        -- Use of tryAsync (unsafe, as it catches asynchronous exceptions) is-        -- justified here, since it will only log, then go to WSClosed.-        next <- tryAsync $ connect (opts ^. endpoint) $ \conn -> do+        next <- try $ connect (opts ^. endpoint) $ \conn -> do             (libSends, sendTid) <- flip (setupSendLoop conn) log $ opts ^. wsHandle . _2             helloPacket <- getPayload conn             case helloPacket of@@ -247,26 +243,26 @@                         Right (Discord.Internal.Types.VoiceWebsocket.Resumed) -> do                             -- use the previous UDP launch options since it's not resent                             udpLaunchOpts <- readMVar udpInfo-                            +                             -- Pass not the MVar but the raw options, since                             -- there's no writing to be done.                             finally (eventStream conn opts interval udpLaunchOpts libSends log) $                                 (killThread heartGenTid >> killThread sendTid)                         Right p -> do-                            (✍!) log $ "First packet after Resume not " <> +                            (✍!) log $ "First packet after Resume not " <>                                 "Opcode 9 Resumed: " <> (tshow p)                             pure WSClosed                 Right p -> do                     (✍!) log $ "First packet not Opcode 8 Hello: " <> (tshow p)                     pure WSClosed -        -- Connection is now closed.         case next :: Either SomeException WSState of-            Left _ -> do-                (✍!) log $ "could not resume, retrying after 5 seconds"+            Left e -> do+                (✍!) log $ "could not resume due to a synchronous exception: " <>+                    (tshow e) <> ", retrying after 5 seconds"                 threadDelay $ 5 * (10^(6 :: Int))                 websocketFsm WSResume (retries + 1) udpInfo-            Right n -> websocketFsm n 1 udpInfo+            Right n -> websocketFsm n retries udpInfo  -- | Create the library-specific sending packets Chan, and then create the -- thread for eternally sending contents in the said Chan, as well as the
src/Discord/Voice.hs view
@@ -56,6 +56,8 @@     , playFileWith'     , playYouTube     , playYouTube'+    , playYouTubeWith+    , playYouTubeWith'     , defaultFFmpegArgs     ) where