irc-fun-bot (empty) → 0.1.0.0
raw patch · 19 files changed
+1398/−0 lines, 19 filesdep +basedep +irc-fun-clientdep +irc-fun-messagessetup-changed
Dependencies added: base, irc-fun-client, irc-fun-messages, transformers
Files
- AUTHORS +1/−0
- COPYING +121/−0
- ChangeLog +17/−0
- INSTALL +13/−0
- NEWS +23/−0
- README.md +14/−0
- Setup.hs +2/−0
- irc-fun-bot.cabal +48/−0
- src/Network/IRC/Fun/Bot.hs +88/−0
- src/Network/IRC/Fun/Bot/Behavior.hs +134/−0
- src/Network/IRC/Fun/Bot/Chat.hs +45/−0
- src/Network/IRC/Fun/Bot/Internal/Chat.hs +205/−0
- src/Network/IRC/Fun/Bot/Internal/Event.hs +114/−0
- src/Network/IRC/Fun/Bot/Internal/Failure.hs +131/−0
- src/Network/IRC/Fun/Bot/Internal/State.hs +80/−0
- src/Network/IRC/Fun/Bot/Internal/Types.hs +248/−0
- src/Network/IRC/Fun/Bot/State.hs +32/−0
- src/Network/IRC/Fun/Bot/Types.hs +32/−0
- src/Network/IRC/Fun/Bot/Util.hs +50/−0
+ AUTHORS view
@@ -0,0 +1,1 @@+fr33domlover <fr33domlover@riseup.net>
+ COPYING view
@@ -0,0 +1,121 @@+Creative Commons Legal Code++CC0 1.0 Universal++ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED+ HEREUNDER.++Statement of Purpose++The laws of most jurisdictions throughout the world automatically confer+exclusive Copyright and Related Rights (defined below) upon the creator+and subsequent owner(s) (each and all, an "owner") of an original work of+authorship and/or a database (each, a "Work").++Certain owners wish to permanently relinquish those rights to a Work for+the purpose of contributing to a commons of creative, cultural and+scientific works ("Commons") that the public can reliably and without fear+of later claims of infringement build upon, modify, incorporate in other+works, reuse and redistribute as freely as possible in any form whatsoever+and for any purposes, including without limitation commercial purposes.+These owners may contribute to the Commons to promote the ideal of a free+culture and the further production of creative, cultural and scientific+works, or to gain reputation or greater distribution for their Work in+part through the use and efforts of others.++For these and/or other purposes and motivations, and without any+expectation of additional consideration or compensation, the person+associating CC0 with a Work (the "Affirmer"), to the extent that he or she+is an owner of Copyright and Related Rights in the Work, voluntarily+elects to apply CC0 to the Work and publicly distribute the Work under its+terms, with knowledge of his or her Copyright and Related Rights in the+Work and the meaning and intended legal effect of CC0 on those rights.++1. Copyright and Related Rights. A Work made available under CC0 may be+protected by copyright and related or neighboring rights ("Copyright and+Related Rights"). Copyright and Related Rights include, but are not+limited to, the following:++ i. the right to reproduce, adapt, distribute, perform, display,+ communicate, and translate a Work;+ ii. moral rights retained by the original author(s) and/or performer(s);+iii. publicity and privacy rights pertaining to a person's image or+ likeness depicted in a Work;+ iv. rights protecting against unfair competition in regards to a Work,+ subject to the limitations in paragraph 4(a), below;+ v. rights protecting the extraction, dissemination, use and reuse of data+ in a Work;+ vi. database rights (such as those arising under Directive 96/9/EC of the+ European Parliament and of the Council of 11 March 1996 on the legal+ protection of databases, and under any national implementation+ thereof, including any amended or successor version of such+ directive); and+vii. other similar, equivalent or corresponding rights throughout the+ world based on applicable law or treaty, and any national+ implementations thereof.++2. Waiver. To the greatest extent permitted by, but not in contravention+of, applicable law, Affirmer hereby overtly, fully, permanently,+irrevocably and unconditionally waives, abandons, and surrenders all of+Affirmer's Copyright and Related Rights and associated claims and causes+of action, whether now known or unknown (including existing as well as+future claims and causes of action), in the Work (i) in all territories+worldwide, (ii) for the maximum duration provided by applicable law or+treaty (including future time extensions), (iii) in any current or future+medium and for any number of copies, and (iv) for any purpose whatsoever,+including without limitation commercial, advertising or promotional+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each+member of the public at large and to the detriment of Affirmer's heirs and+successors, fully intending that such Waiver shall not be subject to+revocation, rescission, cancellation, termination, or any other legal or+equitable action to disrupt the quiet enjoyment of the Work by the public+as contemplated by Affirmer's express Statement of Purpose.++3. Public License Fallback. Should any part of the Waiver for any reason+be judged legally invalid or ineffective under applicable law, then the+Waiver shall be preserved to the maximum extent permitted taking into+account Affirmer's express Statement of Purpose. In addition, to the+extent the Waiver is so judged Affirmer hereby grants to each affected+person a royalty-free, non transferable, non sublicensable, non exclusive,+irrevocable and unconditional license to exercise Affirmer's Copyright and+Related Rights in the Work (i) in all territories worldwide, (ii) for the+maximum duration provided by applicable law or treaty (including future+time extensions), (iii) in any current or future medium and for any number+of copies, and (iv) for any purpose whatsoever, including without+limitation commercial, advertising or promotional purposes (the+"License"). The License shall be deemed effective as of the date CC0 was+applied by Affirmer to the Work. Should any part of the License for any+reason be judged legally invalid or ineffective under applicable law, such+partial invalidity or ineffectiveness shall not invalidate the remainder+of the License, and in such case Affirmer hereby affirms that he or she+will not (i) exercise any of his or her remaining Copyright and Related+Rights in the Work or (ii) assert any associated claims and causes of+action with respect to the Work, in either case contrary to Affirmer's+express Statement of Purpose.++4. Limitations and Disclaimers.++ a. No trademark or patent rights held by Affirmer are waived, abandoned,+ surrendered, licensed or otherwise affected by this document.+ b. Affirmer offers the Work as-is and makes no representations or+ warranties of any kind concerning the Work, express, implied,+ statutory or otherwise, including without limitation warranties of+ title, merchantability, fitness for a particular purpose, non+ infringement, or the absence of latent or other defects, accuracy, or+ the present or absence of errors, whether or not discoverable, all to+ the greatest extent permissible under applicable law.+ c. Affirmer disclaims responsibility for clearing rights of other persons+ that may apply to the Work or any use thereof, including without+ limitation any person's Copyright and Related Rights in the Work.+ Further, Affirmer disclaims responsibility for obtaining any necessary+ consents, permissions or other rights required for any use of the+ Work.+ d. Affirmer understands and acknowledges that Creative Commons is not a+ party to this document and has no duty or obligation with respect to+ this CC0 or use of the Work.
+ ChangeLog view
@@ -0,0 +1,17 @@+The changes are recorded by the version control system, Darcs. To see a log+quickly from the terminal, run:++ $ darcs changes --repo http://dev.rel4tion.org/fr33domlover/irc-fun-bot++There is also a web interface at <http://darcs.rel4tion.org> which, among other+things, can display the history log.++To see the log in a local clone, first get a copy of the repository if you+haven't yet:++ $ darcs get http://dev.rel4tion.org/fr33domlover/irc-fun-bot++Then move into the newly created directory and run darcs:++ $ cd irc-fun-bot+ $ darcs changes
+ INSTALL view
@@ -0,0 +1,13 @@+Install from Hackage:++ $ cabal install irc-fun-bot++Install from unpacked release tarball or source repo:++ $ cd irc-fun-bot+ $ cabal install++Just play with it without installing:++ $ cabal build+ $ cabal repl
+ NEWS view
@@ -0,0 +1,23 @@+This file lists the user-visible interesting changes between releases. For a+full list of changes to the source, see the ChangeLog.++++irc-fun-bot 0.1.0.0 -- 2015-08-09+=================================++General, build and documentation changes:++* (This is the first release, so everything is new)++New APIs, features and enhancements:++* (This is the first release, so everything is a new feature)++Bug fixes:++* (This is just the first release, many bugs haven't been discovered yet)++Dependency changes:++* (This is the first release)
+ README.md view
@@ -0,0 +1,14 @@+See the .cabal file for more info and link to project website the version+control.++The official download location is Hackage:++<http://hackage.haskell.org/package/irc-fun-bot>++This library is free software, and is committed to software freedom. It is+released to the public domain using the CC0 Public Domain Dedication. For the+boring "legal" details see the file 'COPYING'.++See the file 'INSTALL' for hints on installation. The file 'ChangeLog' explains+how to see the history log of the changes done in the code. 'NEWS' provides a+friendly overview of the changes for each release.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ irc-fun-bot.cabal view
@@ -0,0 +1,48 @@+name: irc-fun-bot+version: 0.1.0.0+synopsis: Very simple library for writing fun IRC bots.+description:+ One day an idea came up on the #freepost IRC channel: We didn't need much of+ the serious features IRC bots provide, but we could develop an IRC bot+ collaboratively, for fun and for learning new skills and languages! I also+ thought this is a great chance for people to quickly see their code in real+ use, which is motivating when learning programming, and it's a chance to+ introduce Haskell to the community.+ .+ And so, this library was started. The API is inspired by the @Irc@ package+ and some other IRC client related packages.+homepage: http://rel4tion.org/projects/irc-fun-bot/+bug-reports: http://rel4tion.org/projects/irc-fun-bot/tickets/+license: PublicDomain+license-file: COPYING+author: fr33domlover+maintainer: fr33domlover@riseup.net+copyright: ♡ Copying is an act of love. Please copy, reuse and share.+category: Network, IRC+build-type: Simple+extra-source-files: AUTHORS ChangeLog COPYING INSTALL NEWS README.md+cabal-version: >=1.10++source-repository head+ type: darcs+ location: http://dev.rel4tion.org/fr33domlover/irc-fun-bot++library+ exposed-modules: Network.IRC.Fun.Bot+ , Network.IRC.Fun.Bot.Behavior+ , Network.IRC.Fun.Bot.Chat+ , Network.IRC.Fun.Bot.State+ , Network.IRC.Fun.Bot.Types+ , Network.IRC.Fun.Bot.Util+ other-modules: Network.IRC.Fun.Bot.Internal.Chat+ , Network.IRC.Fun.Bot.Internal.Event+ , Network.IRC.Fun.Bot.Internal.Failure+ , Network.IRC.Fun.Bot.Internal.State+ , Network.IRC.Fun.Bot.Internal.Types+ -- other-extensions: + build-depends: base >=4.7 && <5+ , irc-fun-client+ , irc-fun-messages+ , transformers >=0.4.3+ hs-source-dirs: src+ default-language: Haskell2010
+ src/Network/IRC/Fun/Bot.hs view
@@ -0,0 +1,88 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++-- | This module allows you to define bot event handlers and commands, and then+-- just run event source and sink threads in your @main@ function and let them+-- handle all the details.+module Network.IRC.Fun.Bot+ ( runBot+ )+where++import Control.Concurrent (forkIO)+import Control.Concurrent.Chan+import Control.Monad (forever)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.RWS (ask)+import Network.IRC.Fun.Bot.Internal.Event (matchEvent, handleEvent)+import Network.IRC.Fun.Bot.Internal.Types+import Network.IRC.Fun.Bot.Chat (login, joinConfig, run)+import Network.IRC.Fun.Client.Events (hGetIrcEvents)++-- Get the bot ready for listening to IRC messages.+startBot :: Session e s ()+startBot = do+ liftIO $ putStrLn "Bot: Logging in as IRC user with nickname"+ login+ liftIO $ putStrLn "Bot: Joining IRC channels"+ joinConfig++-- Wait for an IRC event, then handle it according to bot behavior definition.+listenToEvent :: Chan (Either Event a) -> EventHandler e s a -> Session e s ()+listenToEvent chan handler = do+ event <- liftIO $ readChan chan+ either handleEvent handler event++-- Collect IRC events from the server and push into a 'Chan' for the main+-- thread to handle.+listenToIrc :: BotEnv e s -> Chan (Either Event a) -> IO ()+listenToIrc bot chan = do+ putStrLn "Bot: IRC event source listening to IRC events"+ forever $ do+ ircEvents <- hGetIrcEvents $ handle bot+ let match e = matchEvent e (config bot) (commandSets $ behavior bot)+ botEvents = map match ircEvents+ mapM_ print botEvents+ writeList2Chan chan $ map Left botEvents++-- Connect, login, join. Then listen to events and handle them, forever.+botSession :: [EventSource e s a] -> EventHandler e s a -> Session e s ()+botSession sources handler = do+ chan <- liftIO newChan+ bot <- ask+ liftIO $ forkIO $ listenToIrc bot chan+ let launch s = forkIO $ s (config bot)+ (custom bot)+ (writeChan chan . Right)+ (writeList2Chan chan . map Right)+ liftIO $ mapM_ launch sources+ startBot+ liftIO $ putStrLn "Bot: Event sink listening to events"+ forever $ listenToEvent chan handler++-- | Start the bot and run its event loop. The bot will listen to messages from+-- the IRC server and other provided sources, and will respond according to the+-- behavior definitions.+runBot :: Config -- ^ IRC connection configuration+ -> Behavior e s -- ^ Behavior definition for IRC events+ -> [EventSource e s a] -- ^ Additional event source threads to run+ -> EventHandler e s a -- ^ Handler for events coming from those sources+ -> e -- ^ Custom bot environment (read-only state)+ -> s -- ^ Initial state to hold in the background+ -> IO ()+runBot conf behavior sources handler env state = do+ putStrLn "Bot: Starting"+ run conf behavior env state $ botSession sources handler+ putStrLn "Bot: Disconnected"
+ src/Network/IRC/Fun/Bot/Behavior.hs view
@@ -0,0 +1,134 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++-- | This module provides utilities for working with the bot's behavior+-- definition. While the library handles event dispatch by itself, custom+-- commands and bot features may benefit from having this module available to+-- them (e.g. a bot's help system).+module Network.IRC.Fun.Bot.Behavior+ ( -- * Defining+ defaultBehavior+ -- * Searching+ , findCmdInSet+ , findCmdInSets+ , findCmd+ , findSet+ , searchCmds+ -- * Showing+ , listNames+ , listPrimaryNames+ )+where++import Data.Char (toLower)+import Data.List (find, isInfixOf)+import Network.IRC.Fun.Bot.Types+import Network.IRC.Fun.Bot.Util (maybeQuote, showNames)++-------------------------------------------------------------------------------+-- Defining+-------------------------------------------------------------------------------++-- | A default behavior definition which be convenienty overridden and extended+-- using record syntax. It currently simply doesn't do anything (pings do get+-- handled behind the scenes), but things like default responses and logging+-- could be added in the future.+defaultBehavior :: Behavior e s+defaultBehavior = Behavior+ { handleJoin = \ _ _ -> return ()+ , handlePart = \ _ _ _ -> return ()+ , handleQuit = \ _ _ -> return ()+ , handleMsg = \ _ _ _ _ -> return ()+ , handleBotMsg = \ _ _ _ -> return ()+ , commandSets = []+ , handlePersonalMsg = \ _ _ -> return ()+ , handleTopicChange = \ _ _ _ -> return ()+ , handleNames = \ _ _ _ -> return ()+ }++-------------------------------------------------------------------------------+-- Searching+-------------------------------------------------------------------------------++-- | Take a command name (without prefix) and a command set, and return the+-- (leftmost) command which has that name, or 'Nothing' if there is no such+-- command.+findCmdInSet :: String -> CommandSet e s -> Maybe (Command e s)+findCmdInSet name = find ((name `elem`) . names) . commands++-- | Find a command in a list of command sets, using the given prefix character+-- and command name. This is a shortcut for 'findCmd' which doesn't return the+-- matched command set (for the cases you only need to find the command).+findCmdInSets :: Char -> String -> [CommandSet e s] -> Maybe (Command e s)+findCmdInSets cpref cname sets =+ case findCmd cpref cname sets of+ Just (Right cmd) -> Just cmd+ _ -> Nothing++-- | Find a command in a list of command sets, using the given prefix character+-- and command name. If the prefix isn't matched, 'Nothing' is returned. If+-- the prefix is matched but the command isn't, 'Just' 'Left' the command set+-- is returned. Otherwise, 'Just' 'Right' the matched command is returned.+findCmd :: Char -- ^ Command prefix to search for+ -> String -- ^ Command name to search for+ -> [CommandSet e s] -- ^ Command set in which to search+ -> Maybe (Either (CommandSet e s) (Command e s))+findCmd cpref cname sets =+ case findSet cpref sets of+ Nothing -> Nothing+ Just cset ->+ case findCmdInSet cname cset of+ Nothing -> Just $ Left cset+ Just cmd -> Just $ Right cmd++-- | Take a command prefix and a list of command sets, and return the+-- (leftmost) set which has that prefex, or 'Nothing' if there is no such set.+findSet :: Char -> [CommandSet e s] -> Maybe (CommandSet e s)+findSet p = find ((== p) . prefix)++-- | Search for commands by testing a search string against their textual+-- fields: Names and help strings. Each returned pair is a command and the+-- prefix under which it was found.+searchCmds :: String -> [CommandSet e s] -> [(Char, Command e s)]+searchCmds search = concatMap (f $ lower search)+ where+ lower = map toLower+ match s cmd = any (s `isInfixOf`) (map lower $ names cmd)+ || s `isInfixOf` (lower $ help cmd)+ f s cset = [(prefix cset, cmd) | cmd <- commands cset, match s cmd]++-------------------------------------------------------------------------------+-- Showing+-------------------------------------------------------------------------------++-- | Format a list of command names.+listNames :: Maybe Char -- ^ Optionally prepend the prefix char to names+ -> Maybe Quotes -- ^ Optionally add quotes around the names+ -> Bool -- ^ Whether to separate with commas (otherwise+ -- spaces)+ -> [String] -- ^ List of command names+ -> String+listNames pref qs comma =+ showNames qs (if comma then ", " else " ") . maybe id (map . (:)) pref++-- | Format a list of the primary names of the given commands.+listPrimaryNames :: Maybe Char -- ^ Optionally prepend the prefix char to+ -- names+ -> Maybe Quotes -- ^ Optionally add quotes around the names+ -> Bool -- ^ Whether to separate with commas+ -- (otherwise spaces)+ -> [Command e s] -- ^ List of commands+ -> String+listPrimaryNames pref qs comma = listNames pref qs comma . map (head . names)
+ src/Network/IRC/Fun/Bot/Chat.hs view
@@ -0,0 +1,45 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++-- | This module provides IRC commands the bot can do within a bot session.+-- They conveniently wrap the details of IRC connections and messages.+module Network.IRC.Fun.Bot.Chat+ ( -- * Connection Management+ disconnect+ --, reconnect+ , quit+ -- * Session Management+ , run+ , login+ , pong+ -- * Channels+ , joinChannel+ , joinMulti+ , joinConfig+ , partChannel+ , partMulti+ , partAll+ -- * Sending Messages+ , sendToChannel+ , sendToUser+ , failToChannel+ , failToUser+ -- * Other Utilities+ , putIrc+ )+where++import Network.IRC.Fun.Bot.Internal.Chat+import Network.IRC.Fun.Bot.Internal.Failure (failToChannel, failToUser)
+ src/Network/IRC/Fun/Bot/Internal/Chat.hs view
@@ -0,0 +1,205 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++module Network.IRC.Fun.Bot.Internal.Chat+ ( disconnect+ --, reconnect+ , quit+ , run+ , login+ , pong+ , joinChannel+ , joinMulti+ , joinConfig+ , partChannel+ , partMulti+ , partAll+ , sendToUser+ , sendToChannel+ , putIrc+ )+where++import Control.Exception (bracket)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.RWS (runRWST)+import Network.IRC.Fun.Bot.Internal.State+import Network.IRC.Fun.Bot.Internal.Types+import Network.IRC.Fun.Client.Commands+import Network.IRC.Fun.Client.IO (ircConnect, ircDisconnect, hPutIrc)+import Network.IRC.Fun.Messages.Types (Message)++-------------------------------------------------------------------------------+-- Connection Management+-------------------------------------------------------------------------------++-- | Disconnect from IRC by closing the bot's side of the connection. This+-- function is mainly provided for completeness and cases of error. You should+-- probably use the QUIT command of IRC to quit the network in a manner+-- coordinated with the server.+--+-- After disconnection, make sure not to send more IRC commands.+disconnect :: Session e s ()+disconnect = askHandle >>= liftIO . ircDisconnect++-- Disconnect from the IRC server and connect again. This includes+-- identifying with the bot's nickname and joining IRC channels.+--+-- This operation closes the bot session. It opens a new one, and returns it.+--botReconnect :: Session s (Session e s ())+--botReconnect = do+-- disconnect++-- | Finish the IRC session, asking the server to close the connection.+quit :: Maybe String -- ^ Optional message, e.g. the reason for quitting+ -> Session e s ()+quit reason = do+ h <- askHandle+ liftIO $ ircQuit h reason++-------------------------------------------------------------------------------+-- Session Management+-------------------------------------------------------------------------------++-- Run a bot session computation+runSession :: BotEnv e s -> BotState s -> Session e s a -> IO a+runSession bot state session = do+ (a, _, _) <- runRWST session bot state+ return a++-- | Connect to an IRC server and run the bot session+run :: Config -- ^ IRC configuration+ -> Behavior e s -- ^ Bot behavior definition+ -> e -- ^ Custom bot environment (read-only state)+ -> s -- ^ Initial custom bot state+ -> Session e s a -- ^ Session definition+ -> IO a+run conf beh env state session = do+ putStrLn "Bot: Connecting to IRC server"+ bracket+ (ircConnect $ connection conf)+ ircDisconnect+ (\ h -> runSession (BotEnv conf beh h env) (BotState state) session)++-- | Log in as an IRC user and identify with the bot's nickname and password.+-- This is the first thing to do after 'botConnect'ing to the server.+login :: Session e s ()+login = do+ h <- askHandle+ conn <- askConfigS connection+ liftIO $ ircLogin h conn False False++-- | IRC servers send PING messages at regular intervals to test the presence+-- of an active client, at least if no other activity is detected on the+-- connection. The server closes the connection automatically if a PONG+-- response isn't sent from the client within a certain amount of time.+--+-- Therefore, an IRC client (both humans users and bots) usually listens to+-- these PINGs and sends back PONG messages. This function sends a PONG. The+-- parameters should simply be the ones received in the PING message.+pong :: String -- ^ Server name+ -> Maybe String -- ^ Optional server to forward to+ -> Session e s ()+pong server1 mserver2 = do+ h <- askHandle+ liftIO $ ircPong h server1 mserver2++-------------------------------------------------------------------------------+-- Channels+-------------------------------------------------------------------------------++-- | Join an IRC channel.+joinChannel :: String -- ^ Channel name+ -> Maybe String -- ^ Optional channel key (password)+ -> Session e s ()+joinChannel channel key = do+ h <- askHandle+ liftIO $ ircJoin h channel key++-- | Join one or more IRC channels.+joinMulti :: [(String, Maybe String)] -- ^ List of channels and optional keys+ -> Session e s ()+joinMulti channels = do+ h <- askHandle+ liftIO $ ircJoinMulti h channels++-- | Join the IRC channels listed in the configuration, without leaving any+-- other channels the bot already joined.+joinConfig :: Session e s ()+joinConfig = do+ chans <- askConfigS channels+ joinMulti $ map (flip (,) Nothing) chans --TODO avoid unnecessary JOINs?++-- | Leave an IRC channel.+partChannel :: String -- ^ Channel name+ -> Maybe String -- ^ Optional part message, e.g. the reason for+ -- leaving+ -> Session e s ()+partChannel channel reason = do+ h <- askHandle+ liftIO $ ircPart h channel reason++-- | Leave one or more IRC channels.+partMulti :: [String] -- ^ List of channel names+ -> Maybe String -- ^ Optional part message, e.g. the reason for+ -- leaving+ -> Session e s ()+partMulti chans reason = do+ h <- askHandle+ liftIO $ ircPartMulti h chans reason++-- | Leave all IRC channels the bot joined.+partAll :: Session e s ()+partAll = askHandle >>= liftIO . ircPartAll++-------------------------------------------------------------------------------+-- Sending Messages+-------------------------------------------------------------------------------++-- | Send a message to an IRC channel.+--+-- This usually requires that the bot joins the channel first, because many+-- channels have the +n flag set. This flag forbids sending a messages into+-- a channel from outside it.+sendToChannel :: String -- ^ The channel name+ -> String -- ^ The message to send. It may contain newlines, in+ -- which case it will be split into multiple messages+ -- and sent sequentially.+ -> Session e s ()+sendToChannel channel msg = do+ h <- askHandle+ liftIO $ mapM_ (ircSendToChannel h channel) $ lines msg++-- | Send a private message to an IRC user.+sendToUser :: String -- ^ The user's nickname+ -> String -- ^ The message to send. It may contain newlines, in+ -- which case it will be split into multiple messages+ -- and sent sequentially.+ -> Session e s ()+sendToUser nick msg = do+ h <- askHandle+ liftIO $ mapM_ (ircSendToUser h nick) $ lines msg++-------------------------------------------------------------------------------+-- Other Utilities+-------------------------------------------------------------------------------++-- | Send an IRC message to the server. This should only be used if the other+-- wrappers don't provide what you need. If that's the case, it may be a good+-- idea for reusability to add a new wrapper.+putIrc :: Message -> Session e s ()+putIrc msg = do+ h <- askHandle+ liftIO $ hPutIrc h msg
+ src/Network/IRC/Fun/Bot/Internal/Event.hs view
@@ -0,0 +1,114 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++module Network.IRC.Fun.Bot.Internal.Event+ ( matchEvent+ , handleEvent+ )+where++import Data.Char (isSpace)+import Data.List (find, isPrefixOf)+import Network.IRC.Fun.Bot.Internal.Chat (pong)+import Network.IRC.Fun.Bot.Internal.Failure (defaultRespondToChan)+import Network.IRC.Fun.Bot.Internal.State (askBehavior, askBehaviorS)+import Network.IRC.Fun.Bot.Internal.Types+import Network.IRC.Fun.Bot.Behavior (findCmd)+import qualified Network.IRC.Fun.Client.Events as C (Event (..))+import Network.IRC.Fun.Client.IO (nick)+import Network.IRC.Fun.Client.Util (mentions)++matchEvent :: C.Event -> Config -> [CommandSet e s] -> Event+matchEvent event conf csets =+ case event of+ C.Ping server1 server2 -> Ping server1 server2+ C.Kick channel nicks reason -> Kick channel nicks reason+ C.Join channel nick -> Join channel nick+ C.Part channel nick reason -> Part channel nick reason+ C.Quit nick reason -> Quit nick reason+ C.Mode -> other+ C.ChannelMessage channel nick msg False+ | not (null msg) && head msg `elem` map prefix csets ->+ let w = words $ tail msg+ in BotCommand channel nick (head msg)+ (if null w then "" else head w)+ (if null w then [] else tail w)+ | bnick `isPrefixOf` msg ->+ let msg' = drop (length bnick) msg+ msg'' =+ if null msg'+ then []+ else dropWhile isSpace $ tail msg'+ in if null msg' || head msg' `elem` ",:"+ then BotMessage channel nick msg''+ else Message channel nick msg $ msg `mentions` bnick+ | otherwise ->+ Message channel nick msg $ msg `mentions` bnick+ C.ChannelMessage channel nick msg True ->+ Notice (Just channel) nick msg+ C.PrivateMessage nick msg False -> PersonalMessage nick msg+ C.PrivateMessage nick msg True -> Notice Nothing nick msg+ C.Topic channel nick topic ->+ TopicChange channel nick topic+ C.Invite channel nick -> other+ C.Names priv chan pnicks -> Names chan priv pnicks+ C.OtherEvent s -> other+ where+ bnick = nick (connection conf)+ other = OtherEvent $ show event++-- Run the command with the given prefix character, command name and list of+-- parameters. If a command with the given prefix and name isn't found, the bot+-- sends a default friendly response.+runCommand :: Char -- Command prefix+ -> String -- Command name+ -> [String] -- List of parameters+ -> String -- Channel in which the command was triggered+ -> String -- Nickname of user who triggered the command+ -> Session e s ()+runCommand cpref cname cparams channel sender = do+ csets <- askBehaviorS commandSets+ case findCmd cpref cname csets of+ Nothing ->+ defaultRespondToChan channel cpref cname Nothing+ Just (Left cset) ->+ defaultRespondToChan channel cpref cname (Just cset)+ Just (Right cmd) ->+ respond cmd channel sender cparams++-- | React to an IRC event.+handleEvent :: Event -> Session e s ()+handleEvent event = do+ b <- askBehavior+ case event of+ Ping s1 s2 -> pong s1 s2+ Kick chan users why -> return ()+ Join chan nick -> handleJoin b chan nick+ Part chan nick why -> handlePart b chan nick why+ Quit nick why -> handleQuit b nick why+ Message chan sender msg mentioned ->+ handleMsg b chan sender msg mentioned+ Notice mchan sender msg -> return ()+ BotMessage chan sender msg ->+ handleBotMsg b chan sender msg+ BotCommand chan sender cpref cname cargs ->+ runCommand cpref cname cargs chan sender+ PersonalMessage sender msg ->+ handlePersonalMsg b sender msg+ TopicChange chan nick topic ->+ handleTopicChange b chan nick topic+ Names chan priv pnicks ->+ handleNames b chan priv pnicks+ OtherEvent s -> return ()
+ src/Network/IRC/Fun/Bot/Internal/Failure.hs view
@@ -0,0 +1,131 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++module Network.IRC.Fun.Bot.Internal.Failure+ ( defaultRespondToChan+ , defaultRespondToUser+ , failToChannel+ , failToUser+ )+where++import Data.Char (toLower)+import Data.List (intercalate)+import Network.IRC.Fun.Bot.Internal.Chat (sendToChannel, sendToUser)+import Network.IRC.Fun.Bot.Internal.State (askBehaviorS)+import Network.IRC.Fun.Bot.Internal.Types+import Text.Printf (printf)++-- Get the default response string in case the command isn't found in the sets.+-- This assumes the same response is set both to channels and to users in+-- private conversations, which could change in the future. Right now only+-- channel commands are supported by the bot anyway.+defaultResponse :: Char -- Command prefix that was triggered+ -> String -- Command name that was triggered+ -> [CommandSet e s] -- Available command sets+ -> Maybe (CommandSet e s) -- The command set matching the+ -- prefix, if one was found+ -> String+defaultResponse cpref _cname csets Nothing =+ printf "Expected to find a command with prefix ‘%v’, but there’s none. \+ \Available prefixes are: ‘%v’."+ cpref $+ intercalate "’, ‘" $ map ((: []) . prefix) csets+defaultResponse cpref cname _csets (Just cset) =+ printf "The command ‘%v%v’ isn’t available. Available commands for prefix \+ \‘%v’ are: ‘%v’."+ cpref+ cname+ cpref $+ intercalate "’, ‘" $ map (head . names) $ commands cset++-- | Send the default response to an IRC channel+defaultRespondToChan+ :: String -- Target channel+ -> Char -- Command prefix that was triggered+ -> String -- Command name that was triggered+ -> Maybe (CommandSet e s) -- The command set matching the prefix, is one+ -- was found+ -> Session e s ()+defaultRespondToChan chan cpref cname cset = do+ csets <- askBehaviorS commandSets+ sendToChannel chan $ defaultResponse cpref cname csets cset++-- | Send the default response to an IRC user+defaultRespondToUser+ :: String -- Recipient nickname+ -> Char -- Command prefix that was triggered+ -> String -- Command name that was triggered+ -> Maybe (CommandSet e s) -- The command set matching the prefix, is one+ -- was found+ -> Session e s ()+defaultRespondToUser nick cpref cname cset = do+ csets <- askBehaviorS commandSets+ sendToUser nick $ defaultResponse cpref cname csets cset++-- Determine number suffix when counting things+suffix :: Integral a => a -> String+suffix 1 = "st"+suffix 2 = "nd"+suffix 3 = "rd"+suffix _ = "th"++-- Response message to send on failure+failureDescription :: Failure -> String+failureDescription failure =+ case failure of+ WrongNumArgs ->+ "Wrong number of arguments"+ WrongNumArgsN Nothing Nothing ->+ failureDescription WrongNumArgs+ WrongNumArgsN (Just given) Nothing ->+ printf "Wrong number of arguments: %v given" given+ WrongNumArgsN Nothing (Just expected) ->+ printf "Wrong number of arguments: %v expected" expected+ WrongNumArgsN (Just given) (Just expected) ->+ printf "Wrong number of arguments: %v given, %v expected"+ given+ expected+ InvalidArgs -> "Invalid arguments"+ InvalidArg Nothing Nothing -> "Invalid argument found"+ InvalidArg (Just pos) Nothing ->+ printf "The %v%v argument is invalid" pos (suffix pos)+ InvalidArg Nothing (Just param) ->+ printf "Invalid argument: ‘%v’" param+ InvalidArg (Just pos) (Just param) ->+ printf "The %v%v argument is invalid: ‘%v’" pos (suffix pos) param+ OtherFail s -> "Command failed: " ++ s++-- | Send message explaining a failure to an IRC channel+failToChannel :: String -- ^ Target channel+ -> String -- ^ User to whom to refer+-- -> Char -- ^ Command prefix that was triggered+-- -> String -- ^ Command name that was triggered+ -> Failure -- ^ Problem indication+ -> Session e s ()+failToChannel chan nick {-_cpref _cname-} failure =+ let lowerc [] = []+ lowerc (c:cs) = toLower c : cs+ in sendToChannel chan $+ nick ++ ", " ++ lowerc (failureDescription failure)++-- | Send message explaining a failure to an IRC user+failToUser :: String -- ^ Target user+-- -> Char -- ^ Command prefix that was triggered+-- -> String -- ^ Command name that was triggered+ -> Failure -- ^ Problem indication+ -> Session e s ()+failToUser nick {-_cpref _cname-} failure =+ sendToUser nick $ failureDescription failure
+ src/Network/IRC/Fun/Bot/Internal/State.hs view
@@ -0,0 +1,80 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++module Network.IRC.Fun.Bot.Internal.State+ ( askConfig+ , askConfigS+ , askBehavior+ , askBehaviorS+ , askEnv+ , askEnvS+ , askHandle+ , getState+ , getStateS+ , putState+ , modifyState+ )+where++import Control.Monad.Trans.RWS+import Network.IRC.Fun.Bot.Internal.Types+import Network.IRC.Fun.Client.IO (Handle)++-- | Fetch the bot configuration.+askConfig :: Session e s Config+askConfig = asks config++-- | Retrieve a function of the bot configuration.+askConfigS :: (Config -> a) -> Session e s a+askConfigS f = asks $ f . config++-- | Fetch the bot behavior definition.+askBehavior :: Session e s (Behavior e s)+askBehavior = asks behavior++-- | Retrieve a function of the bot behavior definition.+askBehaviorS :: (Behavior e s -> a) -> Session e s a+askBehaviorS f = asks $ f . behavior++-- | Fetch the bot environment, i.e. read-only state.+askEnv :: Session e s e+askEnv = asks custom++-- | Retrieve a function of the bot environment.+askEnvS :: (e -> a) -> Session e s a+askEnvS f = asks $ f . custom++-- | Fetch the bot session socket handle.+askHandle :: Session e s Handle+askHandle = asks handle++-- | Fetch the current value of the state within the session.+getState :: Session e s s+getState = gets public++-- | Get a specific component of the state, using a projection function+-- supplied.+getStateS :: (s -> a) -> Session e s a+getStateS f = gets $ f . public++-- | Set the state within the session.+putState :: s -> Session e s ()+putState st = modify $ \ old -> old { public = st }++-- | Update the state to the result of applying a function to the current+-- state.+modifyState :: (s -> s) -> Session e s ()+modifyState f =+ modify $ \ old@(BotState { public = st }) -> old { public = f st }
+ src/Network/IRC/Fun/Bot/Internal/Types.hs view
@@ -0,0 +1,248 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++module Network.IRC.Fun.Bot.Internal.Types+ ( Config (..)+ , Failure (..)+ , Command (..)+ , CommandSet (..)+ , BotEnv (..)+ , BotState (..)+ , Session (..)+ , ChannelPrivacy (..)+ , Privilege (..)+ , Event (..)+ , Behavior (..)+ , EventSource+ , EventHandler+ , Quotes (..)+ )+where++import Control.Monad.Trans.RWS (RWST)+import Network.IRC.Fun.Client.IO (Connection, Handle)+import Network.IRC.Fun.Client.Events (ChannelPrivacy (..), Privilege (..))++-- | Configuration for the bot connection to IRC.+data Config = Config+ { -- | Connection details, including nickname and optional password+ connection :: Connection+ -- | List of channels to join, e.g. @["#freepost", "#rel4tion"]@+ , channels :: [String]+ }+ deriving (Eq, Show)++-- | Describes wrong usage of a bot command.+data Failure+ -- | The number of arguments given is wrong+ = WrongNumArgs+ -- | \<first-param\> arguments were given, instead of \<second-param\>+ | WrongNumArgsN (Maybe Int) (Maybe Int)+ -- | At least one of the arguments is invalid+ | InvalidArgs+ -- | Argument in position \<first-param\> and value \<second-param\>+ -- invalid+ | InvalidArg (Maybe Int) (Maybe String)+ -- | Some other error, with a given description+ | OtherFail String+ deriving (Eq, Show)++-- | A bot command, triggered by a message sent to an IRC channel the bot has+-- joined.+--+-- The type parameter @e@ is the type of environment, i.e. real-only state. The+-- type parameter @s@ is the type of the writable state the bot holds. For+-- example, maybe you want your bot to keep a list of to-do items and manage it+-- using bot commands. The bot state is where you store the to-do list.+data Command e s = Command+ { -- | The command's name string, e.g. @"echo"@. More than one name is+ -- allowed per command, so that localized names and shortcuts can be made+ -- available.+ --+ -- The first name in the list is considered the /primary name/. When one+ -- name is shown, e.g. in help messages, it will be the primary name.+ names :: [String]++ -- | What to do in response to the command being triggered by an IRC+ -- user. The bot can send an IRC message back to the channel, or modify+ -- its state, or both.+ --+ -- The returned value is in the 'Session' monad (it is really 'RWST'+ -- behind the scenes), which allows the bot to access the configuration+ -- and bot state, store and load data from files, communicate through the+ -- network (e.g. download RSS feeds) and more.+ --+ -- Parameters:+ --+ -- (1) Channel in which the command was triggered+ -- (2) Nickname of user who triggered the command+ -- (3) Command parameters given+ , respond :: String -> String -> [String] -> Session e s ()++ -- | Help string for the command, explaining it purpose, its parameters+ -- and its usage, possibly giving an example. May contain newlines.+ , help :: String+ }++-- | The bot recognizes commands by picking IRC channel messages which begin+-- with a special character, the command prefix. It's possible to have several+-- prefixes, maybe give each its own role or meaning. A command set is a set of+-- commands which share a prefix.+--+-- A command can be in more than one set.+--+-- Common prefixes are @'!', '\@', '>', ':'@.+data CommandSet e s = CommandSet+ { prefix :: Char+ , commands :: [Command e s]+ }++-- Read-only bot environment.+--+-- The full environment is exposed to commands, which is both power and+-- responsibility. For example, uncareful use of the handle (this is a handle+-- to the TCP socket) could cause parsing issues or disconnection. The 'Config'+-- contains the bot's nickname password, which shouldn't be shared publicly.+--+-- At the same time, you can send any custom message you wish to the IRC+-- server, even if the libraries don't support it directly, or you could use+-- the password to automatically re-identify in case of any problem with+-- NickServ. And so on.+data BotEnv e s = BotEnv+ { config :: Config+ , behavior :: Behavior e s+ , handle :: Handle+ , custom :: e+ }++-- Readable and writable bot state. Contains internal state used by this+-- library, and public state managed by the bot's behavior logic.+newtype BotState s = BotState { public :: s }++-- | Bot monad. It provides read-only bot environment (e.g. the configuration),+-- read-writable bot state (for use by bot commands) and IO.+type Session e s = RWST (BotEnv e s) () (BotState s) IO++-- An event triggered by an IRC message sent from the server.+data Event+ -- | A ping was sent to the bot. The parameters contain the arguments+ -- passed, which should be passed in the Pong.+ = Ping String (Maybe String)+ -- | Users (2nd parameter) have been kicked from a channel (1st parameter)+ -- for a given reason (3rd parameter).+ | Kick String [String] (Maybe String)+ -- | A user (2nd parameter) joined a channel (1st parameter).+ | Join String String+ -- | A user (2nd parameter) left a channel (1st parameter), 3rd=reason.+ | Part String String (Maybe String)+ -- | User, reason.+ | Quit String (Maybe String)+ -- | Message (3rd parameter) sent in a channel (1st parameter) by a user+ -- with the given nickname (2nd parameter). The last parameter says whether+ -- the bot's nick is mentioned in the message.+ | Message String String String Bool+ -- | Like 'Message', but this is a notice. The bot shouldn't send a+ -- response (but it can modify its state etc.). First parameter: Just+ -- channel, or Nothing. The latter means a private message.+ | Notice (Maybe String) String String+ -- | Message (3rd parameter) referring to the bot sent by nick (2nd+ -- parameter) in a channel (1st parameter). The message begins with the+ -- bot's nick, followed by a colon or a comma. That part is however removed+ -- from the 3rd parameter. For example, if the message was+ -- \"funbot, hello!\" then the 3rd parameter will be \"hello!\".+ | BotMessage String String String+ -- | A bot command, which is a message with a special prefix, was sent to+ -- a channel. Parameters: Channel, sender nickname, prefix character,+ -- command name, command arguments.+ | BotCommand String String Char String [String]+ -- | A private message sent specifically to the bot, from a user with the+ -- given nickname (1st parameter) and with the given content (2nd+ -- parameter).+ | PersonalMessage String String+ -- | Channel, nickname, topic+ | TopicChange String String String+ -- | Unrecognized or unimplemented event. The parameter contains (possibly+ -- empty) input.+ | OtherEvent String+ -- | The server sent a list of nicknames present in a channel. Parameters:+ -- Channel privacy mode, channel name, list of users. Each list item is a+ -- pair of a user privilege level in the channel, and the user's nickname.+ | Names String ChannelPrivacy [(Privilege, String)]+ deriving Show++-- | Bot behavior definition.+data Behavior e s = Behavior+ { handleJoin :: String -> String -> Session e s ()+ , handlePart :: String -> String -> Maybe String -> Session e s ()+ , handleQuit :: String -> Maybe String -> Session e s ()+ , handleMsg :: String -> String -> String -> Bool -> Session e s ()+ , handleBotMsg :: String -> String -> String -> Session e s ()+ , commandSets :: [CommandSet e s]+ , handlePersonalMsg :: String -> String -> Session e s ()+ , handleTopicChange :: String -> String -> String -> Session e s ()+ -- | Handle a channel member list received. Parameters:+ --+ -- (1) Channel name+ -- (2) Channel privacy+ -- (3) List of channel members: their privilege level in the channel and+ -- their nicknames+ , handleNames :: String+ -> ChannelPrivacy+ -> [(Privilege, String)]+ -> Session e s ()+ }++-- | An 'IO' action, possibly running forever, which produces events for the+-- bot to process. These event sources are run by the bot in dedicated threads.+--+-- Type parameters:+--+-- * @e@ - custom bot environment type+-- * @s@ - custom bot state type+-- * @a@ - event type+type EventSource e s a+ = Config+ -- ^ The bot configuration. The event source may find this useful.+ -> e+ -- ^ The bot's custom environment. The event source may find this useful.+ -> (a -> IO ())+ -- ^ A non-blocking 'IO' action which sends a given event to the+ -- processing queue.+ -> ([a] -> IO ())+ -- ^ A non-blocking 'IO' action which sends a given list of events to the+ -- processing queue.+ -> IO ()++-- | A bot session action which reacts to a single event which came from an+-- 'EventSource'.+--+-- Type parameters:+--+-- * @e@ - custom bot environment type+-- * @s@ - custom bot state type+-- * @a@ - event type+type EventHandler e s a = a -> Session e s ()++-- | Quote types for string formatting.+data Quotes+ -- | Single quote on each side of the formatted string. The parameter sets+ -- whether to use Unicode (otherwise use ASCII fallback).+ = SingleQuotes Bool+ -- | Double quote on each side of the formatted string. The parameter sets+ -- whether to use Unicode (otherwise use ASCII fallback).+ | DoubleQuotes Bool+ -- | Backtick on each side of the formatted string.+ | BackTicks+ deriving (Eq, Show)
+ src/Network/IRC/Fun/Bot/State.hs view
@@ -0,0 +1,32 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++-- | This module provides access functions for working with the state stored+-- within a bot session.+module Network.IRC.Fun.Bot.State+ ( askConfig+ , askConfigS+ , askBehavior+ , askBehaviorS+ , askEnv+ , askEnvS+ , getState+ , getStateS+ , putState+ , modifyState+ )+where++import Network.IRC.Fun.Bot.Internal.State
+ src/Network/IRC/Fun/Bot/Types.hs view
@@ -0,0 +1,32 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++-- | This module provides the datatypes.+module Network.IRC.Fun.Bot.Types+ ( Connection (..)+ , Config (..)+ , Failure (..)+ , Command (..)+ , CommandSet (..)+ , Session+ , Behavior (..)+ , EventSource+ , EventHandler+ , Quotes (..)+ )+where++import Network.IRC.Fun.Bot.Internal.Types+import Network.IRC.Fun.Client.IO (Connection (..))
+ src/Network/IRC/Fun/Bot/Util.hs view
@@ -0,0 +1,50 @@+{- This file is part of irc-fun-bot.+ -+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.+ -+ - ♡ Copying is an act of love. Please copy, reuse and share.+ -+ - The author(s) have dedicated all copyright and related and neighboring+ - rights to this software to the public domain worldwide. This software is+ - distributed without any warranty.+ -+ - You should have received a copy of the CC0 Public Domain Dedication along+ - with this software. If not, see+ - <http://creativecommons.org/publicdomain/zero/1.0/>.+ -}++-- | This module collects smaller general purpose utilities which may be useful+-- to bot authors.+module Network.IRC.Fun.Bot.Util+ ( quote+ , maybeQuote+ , showNames+ )+where++import Data.List (intercalate)+import Network.IRC.Fun.Bot.Types (Quotes (..))++-- | Add quotes to a string.+quote :: Quotes -> String -> String+quote (SingleQuotes True) s = '‘' : s ++ "’"+quote (SingleQuotes False) s = '\'' : s ++ "'"+quote (DoubleQuotes True) s = '“' : s ++ "”"+quote (DoubleQuotes False) s = '"' : s ++ "\""+quote BackTicks s = '`' : s ++ "`"++-- | Optionally add quotes to a string.+maybeQuote :: Maybe Quotes -> String -> String+maybeQuote = maybe id quote++-- | Format a list of labels.+--+-- >>> putStrLn $ showNames (Just $ SingleQuotes False)+-- >>> ", "+-- >>> ["one", "two", "three"]+-- 'one', 'two', 'three'+showNames :: Maybe Quotes -- ^ Optionally wrap labels with quotes+ -> String -- ^ Separate between labels, e.g. spaces or newline+ -> [String] -- ^ List of labels+ -> String+showNames qs sep = intercalate sep . map (maybeQuote qs)