irc-ctcp (empty) → 0.1.0.0
raw patch · 4 files changed
+248/−0 lines, 4 filesdep +basedep +bytestringdep +textsetup-changed
Dependencies added: base, bytestring, text
Files
- LICENSE +13/−0
- Network/IRC/CTCP.hs +124/−0
- Setup.hs +2/−0
- irc-ctcp.cabal +109/−0
+ LICENSE view
@@ -0,0 +1,13 @@+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 ++ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> ++ Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. ++ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION ++ 0. You just DO WHAT THE FUCK YOU WANT TO.
+ Network/IRC/CTCP.hs view
@@ -0,0 +1,124 @@+-- |Functions for encoding and decoding CTCPs.+module Network.IRC.CTCP+ ( -- *Types+ CTCPByteString+ , getUnderlyingByteString++ -- *Encoding and decoding+ , toCTCP+ , fromCTCP+ , encodeCTCP+ , decodeCTCP++ -- *Utilities+ , isCTCP+ , asCTCP+ , orCTCP+ ) where++import Data.ByteString (ByteString, pack, singleton, unpack)+import Data.List (mapAccumL)+import Data.Maybe (catMaybes, fromMaybe)+import Data.Text (Text, splitOn)+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Data.Tuple (swap)++import qualified Data.ByteString as B+import qualified Data.Text as T++-- |Type representing a CTCP-encoded bytestring.+newtype CTCPByteString = CBS+ { getUnderlyingByteString :: ByteString+ -- ^Get the underlying (encoded) bytestring from a CTCP+ -- bytestring.+ }++-- |Turn a command name and arguments into a CTCP-encoded bytestring.+--+-- This encodes the text with UTF-8. If another encoding is desired,+-- 'encodeCTCP' should be used directly.+toCTCP :: Text -> [Text] -> CTCPByteString+toCTCP cmd args = encodeCTCP . encodeUtf8 . T.unwords $ cmd : args++-- |Encode a bytestring according to the CTCP spec.+encodeCTCP :: ByteString -> CTCPByteString+encodeCTCP bs = CBS $ B.concat [ singleton soh+ , escape bs+ , singleton soh+ ]++ where escape = B.concatMap escape'+ escape' x = case lookup x encodings of+ -- If there is an encoding, escape it and use+ -- that.+ Just x' -> pack [esc, x']++ -- Otherwise, just return the original+ -- character.+ Nothing -> singleton x++-- |Decode a CTCP-encoded bytestring and turn it into a command name+-- and arguments.+--+-- This decodes the text with UTF-8. If another encoding is desired,+-- 'decodeCTCP' should be used directly.+fromCTCP :: CTCPByteString -> (Text, [Text])+fromCTCP bs = case splitOn (T.pack " ") . decodeUtf8 . decodeCTCP $ bs of+ (cmd : args) -> (cmd, args)+ _ -> (T.pack "", [])++-- |Decode a CTCP bytestring. Extraeneous escapes are dropped.+decodeCTCP :: CTCPByteString -> ByteString+decodeCTCP (CBS bs) = unescape . B.tail . B.init $ bs++ where unescape = pack . catMaybes . snd . mapAccumL step False . unpack++ -- If we fail to find a decoding, ignore the escape.+ step True x = (False, Just . fromMaybe x $ lookup x decodings)++ -- Enter escape mode, this doesn't add a character to the+ -- output.+ step False 0o020 = (True, Nothing)++ step _ x = (False, Just x)++soh :: Integral i => i+soh = 0o001++esc :: Integral i => i+esc = 0o020++encodings :: Integral i => [(i, i)]+encodings = [ (0o000, 0o060)+ , (0o012, 0o156)+ , (0o015, 0o162)+ , (0o020, 0o020)+ ]++decodings :: Integral i => [(i, i)]+decodings = map swap encodings++-- |Check if a bytestring represents a CTCP.+--+-- This is intentionally very lenient, in particular it doesn't check+-- that there are no extra escape characters. This is because the spec+-- states that misplaced escape characters should be discarded by the+-- decoding process.+isCTCP :: ByteString -> Bool+isCTCP bs = and $ (B.length bs >= 2) : (B.head bs == soh) : (B.last bs == soh) : map (flip B.notElem bs . fst) encodings++-- |Check if a bytestring looks like a CTCP, and if so, wrap it up in+-- the 'CTCPByteString' type.+--+-- This uses 'isCTCP', and so is lenient with escapes.+asCTCP :: ByteString -> Maybe CTCPByteString+asCTCP bs = if isCTCP bs+ then Just $ CBS bs+ else Nothing++-- |Apply one of two functions depending on whether the bytestring+-- looks like a CTCP or not.+--+-- This uses 'asCTCP', and so is lenient with escapes.+orCTCP :: (ByteString -> a) -> (CTCPByteString -> a) -> ByteString -> a+orCTCP f g bs = maybe (f bs) g (asCTCP bs)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ irc-ctcp.cabal view
@@ -0,0 +1,109 @@+-- Initial irc-ctcp.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/++-- The name of the package.+name: irc-ctcp++-- The package version. See the Haskell package versioning policy (PVP) +-- for standards guiding when and how versions should be incremented.+-- http://www.haskell.org/haskellwiki/Package_versioning_policy+-- PVP summary: +-+------- breaking API changes+-- | | +----- non-breaking API additions+-- | | | +--- code changes with no API change+version: 0.1.0.0++-- A short (one-line) description of the package.+synopsis: A CTCP encoding and decoding library for IRC clients.++-- A longer description of the package.+description:+ CTCP (Client To Client Protocol) is a way of sending arbitrary data+ over an IRC network, which may include bytes not allowed in standard+ IRC messages. CTCPs are sent as a PRIVMSG or NOTICE, where the first+ and last characters as @\\001@ (SOH), and special bytes are escaped+ by encoding them into a two-byte sequence beginning with @\\020@+ (DLE). CTCPs consist of command name (typically in upper-case)+ followed by list of space-separated arguments, which may be empty.+ .+ One use of CTCPs supported by the vast majority of IRC clients today+ is the ACTION command, typically invoked with /me. For example, if+ the user @foo@ in the channel @#bar@ were to issue+ .+ > /me dances+ .+ everyone in the channel would receive the message+ .+ > :foo PRIVMSG #bar :\001ACTION dances\001+ .+ Other common uses of CTCP include requesting the name and version of+ a user's IRC client, their local time, determining ping times, and+ initiating file transfers (DCC).+ .+ Characters are escaped as follows:+ .+ [@\\000@ (NUL)] @\\020 \\060@ ("0")+ .+ [@\\012@ (NL)] @\\020 \\156@ ("n")+ .+ [@\\015@ (CR)] @\\020 \\162@ ("r")+ .+ [@\\020@ (DLE)] @\\020 \\020@+ .+ All other appearences of the escape character are errors, and are+ dropped.+ .+ See <http://www.irchelp.org/irchelp/rfc/ctcpspec.html> for more+ details.++-- URL for the project homepage or repository.+homepage: https://github.com/barrucadu/yukibot++-- The license under which the package is released.+license: OtherLicense++-- The file containing the license text.+license-file: LICENSE++-- The package author(s).+author: Michael Walker++-- An email address to which users can send suggestions, bug reports, and +-- patches.+maintainer: mike@barrucadu.co.uk++-- 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: ++-- Constraint on the version of Cabal needed to build this package.+cabal-version: >=1.10+++library+ -- Modules exported by the library.+ exposed-modules: Network.IRC.CTCP+ + -- 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.7 && <5+ , bytestring >=0.10+ , text >=1.1+ + -- Directories containing source files.+ -- hs-source-dirs: + + -- Base language which the package is written in.+ default-language: Haskell2010+