glirc 2.24 → 2.25
raw patch · 21 files changed
+273/−67 lines, 21 filesdep ~asyncdep ~freedep ~hookup
Dependency ranges changed: async, free, hookup, kan-extensions, lens, time, vty
Files
- ChangeLog.md +7/−0
- README.md +2/−12
- glirc.cabal +9/−7
- include/glirc-api.h +1/−2
- src/Client/CApi.hs +7/−8
- src/Client/CApi/Types.hsc +3/−7
- src/Client/Commands.hs +44/−10
- src/Client/Configuration.hs +2/−2
- src/Client/Configuration/ServerSettings.hs +14/−1
- src/Client/Image/PackedImage.hs +3/−2
- src/Client/Image/StatusLine.hs +1/−0
- src/Client/Image/Textbox.hs +3/−1
- src/Client/Mask.hs +102/−0
- src/Client/Message.hs +5/−5
- src/Client/Network/Connect.hs +7/−1
- src/Client/State.hs +12/−7
- src/Client/State/Focus.hs +1/−0
- src/Client/View.hs +2/−0
- src/Client/View/IgnoreList.hs +44/−0
- src/Client/View/Messages.hs +2/−1
- src/Client/View/UrlSelection.hs +2/−1
ChangeLog.md view
@@ -1,5 +1,12 @@ # Revision history for glirc2 +## 2.25+* `/ignore` can list ignores and supports full wildcard masks+* Updated C extension API+* Improved OTR extension to avoid interacting with ZNC replays+* Added `protocol-family` configuration option. Set to `inet` or+ `inet6` to force a particular IP protocol.+ ## 2.24 * `/query` now takes a message parameter and tab-completes
README.md view
@@ -16,8 +16,7 @@ up-to-date: ```-$ cabal update # if you're using cabal-$ stack update # if you're using stack+$ cabal update ``` To install the latest version from Hackage using cabal-install:@@ -33,16 +32,6 @@ $ cabal build ``` -Building with stack--```-$ stack init --resolver=lts-8 --solver-$ stack build-```--Note that if you're using a *nightly* resolver that you'll need to work around a-bug in stack by manually installing `alex` and `happy`.- Client Features =============== @@ -119,6 +108,7 @@ sasl-password: "somepass" socks-host: "socks5.example.com" socks-port: 8080 -- defaults to 1080+ log-dir: "/home/myuser/ircLogs" * name: "example" hostname: "example.com"
glirc.cabal view
@@ -1,5 +1,5 @@ name: glirc-version: 2.24+version: 2.25 synopsis: Console IRC client description: Console IRC client .@@ -89,6 +89,7 @@ Client.Image.StatusLine Client.Image.Textbox Client.Log+ Client.Mask Client.Message Client.Network.Async Client.Network.Connect@@ -104,6 +105,7 @@ Client.View.ChannelInfo Client.View.Digraphs Client.View.Help+ Client.View.IgnoreList Client.View.KeyMap Client.View.MaskList Client.View.Mentions@@ -133,13 +135,13 @@ containers >=0.5.7 && <0.6, directory >=1.2.6 && <1.4, filepath >=1.4.1 && <1.5,- free >=4.12 && <4.13,+ free >=4.12 && <5.1, gitrev >=1.2 && <1.4, hashable >=1.2.4 && <1.3,- hookup >=0.1 && <0.2,+ hookup >=0.2 && <0.3, irc-core >=2.3 && <2.4,- kan-extensions >=5.0 && <5.1,- lens >=4.14 && <4.16,+ kan-extensions >=5.0 && <5.2,+ lens >=4.14 && <4.17, network >=2.6.2 && <2.7, process >=1.4.2 && <1.7, regex-tdfa >=1.2 && <1.3,@@ -149,12 +151,12 @@ stm >=2.4 && <2.5, template-haskell >=2.11 && <2.13, text >=1.2.2 && <1.3,- time >=1.6 && <1.9,+ time >=1.6 && <1.10, transformers >=0.5.2 && <0.6, unix >=2.7 && <2.8, unordered-containers >=0.2.7 && <0.3, vector >=0.11 && <0.13,- vty >=5.11.1 && <5.18+ vty >=5.11.1 && <5.20 test-suite test type: exitcode-stdio-1.0
include/glirc-api.h view
@@ -40,8 +40,7 @@ }; struct glirc_command {- const struct glirc_string *params;- size_t params_n;+ struct glirc_string command; }; typedef void *start_type (struct glirc *G, const char *path);
src/Client/CApi.hs view
@@ -167,11 +167,11 @@ -- | Notify an extension of a client command with the given parameters. commandExtension :: Ptr () {- ^ client state stableptr -} ->- [Text] {- ^ parameters -} ->+ Text {- ^ command -} -> ActiveExtension {- ^ extension to command -} -> IO ()-commandExtension stab params ae = evalNestedIO $- do cmd <- withCommand params+commandExtension stab command ae = evalNestedIO $+ do cmd <- withCommand command let f = fgnCommand (aeFgn ae) liftIO $ unless (f == nullFunPtr) $ runProcessCommand f stab (aeSession ae) cmd@@ -209,12 +209,11 @@ nest1 $ with $ FgnChat net' tgt' msg' withCommand ::- [Text] {- ^ parameters -} ->+ Text {- ^ command -} -> NestedIO (Ptr FgnCmd)-withCommand params =- do prms <- traverse withText params- (prmN,prmPtr) <- nest2 $ withArrayLen prms- nest1 $ with $ FgnCmd prmPtr (fromIntegral prmN)+withCommand command =+ do cmd <- withText command+ nest1 $ with $ FgnCmd cmd withTag :: TagEntry -> NestedIO (FgnStringLen, FgnStringLen) withTag (TagEntry k v) =
src/Client/CApi/Types.hsc view
@@ -223,20 +223,16 @@ -- | @struct glirc_command@ data FgnCmd = FgnCmd- { fcParams :: Ptr FgnStringLen -- ^ array- , fcParamN :: CSize -- ^ array length+ { fcCommand :: FgnStringLen } instance Storable FgnCmd where alignment _ = #alignment struct glirc_command sizeOf _ = #size struct glirc_command peek p = FgnCmd- <$> (#peek struct glirc_command, params ) p- <*> (#peek struct glirc_command, params_n) p+ <$> (#peek struct glirc_command, command) p - poke p FgnCmd{..} =- do (#poke struct glirc_command, params ) p fcParams- (#poke struct glirc_command, params_n) p fcParamN+ poke p FgnCmd{..} = (#poke struct glirc_command, command) p fcCommand ------------------------------------------------------------------------
src/Client/Commands.hs view
@@ -33,6 +33,7 @@ import Client.Commands.Recognizer import Client.Commands.WordCompletion import Client.Configuration+import Client.Mask import Client.Message import Client.State import Client.State.Channel@@ -627,9 +628,29 @@ , Command (pure "ignore")- (remainingArg "nicks")- "Toggle the soft-ignore on each of the space-delimited given nicknames.\n"- $ ClientCommand cmdIgnore simpleClientTab+ (remainingArg "masks")+ "\^BParameters:\^B\n\+ \\n\+ \ masks: List of masks\n\+ \\n\+ \\^BDescription:\^B\n\+ \\n\+ \ Toggle the soft-ignore on each of the space-delimited given\n\+ \ nicknames. Ignores can use \^B*\^B (many) and \^B?\^B (one) wildcards.\n\+ \ Masks can be of the form: nick[[!user]@host]\n\+ \ Masks use a case-insensitive comparison.\n\+ \\n\+ \ If no masks are specified the current ignore list is displayed.\n\+ \\n\+ \\^BExamples:\^B\n\+ \\n\+ \ /ignore\n\+ \ /ignore nick1 nick2 nick3\n\+ \ /ignore nick@host\n\+ \ /ignore nick!user@host\n\+ \ /ignore *@host\n\+ \ /ignore *!baduser@*\n"+ $ ClientCommand cmdIgnore tabIgnore , Command (pure "grep")@@ -1890,15 +1911,28 @@ cmdIgnore :: ClientCommand String cmdIgnore st rest =- case mkId . Text.pack <$> words rest of- [] -> commandFailureMsg "bad arguments" st- xs -> commandSuccess- $ over clientIgnores updateIgnores st+ case mkId <$> Text.words (Text.pack rest) of+ [] -> commandSuccess (changeSubfocus FocusIgnoreList st)+ xs -> commandSuccess st2 where+ (newIgnores, st1) = (clientIgnores <%~ updateIgnores) st+ st2 = set clientIgnoreMask (buildMask (toList newIgnores)) st1+ updateIgnores :: HashSet Identifier -> HashSet Identifier updateIgnores s = foldl' updateIgnore s xs+ updateIgnore s x = over (contains x) not s +-- | Complete the nickname at the current cursor position using the+-- userlist for the currently focused channel (if any)+tabIgnore :: Bool {- ^ reversed -} -> ClientCommand String+tabIgnore isReversed st _ =+ simpleTabCompletion mode hint completions isReversed st+ where+ hint = activeNicks st+ completions = currentCompletionList st ++ views clientIgnores toList st+ mode = currentNickCompletionMode st+ -- | Implementation of @/reload@ -- -- Attempt to reload the configuration file@@ -2054,7 +2088,7 @@ -- the window line when that action was significant enough to -- be considered a hint for tab completion. summaryActor :: IrcSummary -> Maybe Identifier- summaryActor (ChatSummary who) = Just who+ summaryActor (ChatSummary who) = Just $! userNick who summaryActor _ = Nothing -- | Use the *!*@host masks of users for channel lists when setting list modes@@ -2114,13 +2148,13 @@ mode = currentNickCompletionMode st cmdExtension :: ClientCommand (String, String)-cmdExtension st (name,params) =+cmdExtension st (name,command) = case find (\ae -> aeName ae == Text.pack name) (view (clientExtensions . esActive) st) of Nothing -> commandFailureMsg "unknown extension" st Just ae -> do (st',_) <- clientPark st $ \ptr ->- commandExtension ptr (Text.pack <$> words params) ae+ commandExtension ptr (Text.pack command) ae commandSuccess st' -- | Implementation of @/exec@ command.
src/Client/Configuration.hs view
@@ -93,7 +93,7 @@ , _configMacros :: Recognizer Macro -- ^ command macros , _configExtensions :: [FilePath] -- ^ paths to shared library , _configUrlOpener :: Maybe FilePath -- ^ paths to url opening executable- , _configIgnores :: HashSet Identifier -- ^ initial ignore list+ , _configIgnores :: [Text] -- ^ initial ignore mask list , _configActivityBar :: Bool -- ^ initially visibility of the activity bar , _configBellOnMention :: Bool -- ^ notify terminal on mention , _configHideMeta :: Bool -- ^ default setting for hidemeta on new windows@@ -273,7 +273,7 @@ "Extra words to highlight in chat messages" _configNickPadding <- sec' NoPadding "nick-padding" nickPaddingSpec "Amount of space to reserve for nicknames in chat messages"- _configIgnores <- sec' mempty "ignores" identifierSetSpec+ _configIgnores <- sec' [] "ignores" valuesSpec "Set of nicknames to ignore on startup" _configActivityBar <- sec' False "activity-bar" yesOrNoSpec "Show channel names and message counts for activity on\
src/Client/Configuration/ServerSettings.hs view
@@ -47,6 +47,7 @@ , ssAutoconnect , ssNickCompletion , ssLogDir+ , ssProtocolFamily -- * Load function , loadDefaultServerSettings@@ -68,7 +69,7 @@ import Data.Text (Text) import qualified Data.Text as Text import Irc.Identifier (Identifier, mkId)-import Network.Socket (HostName, PortNumber)+import Network.Socket (HostName, PortNumber, Family(..)) import System.Environment -- | Static server-level settings@@ -100,6 +101,7 @@ , _ssAutoconnect :: Bool -- ^ Connect to this network on server startup , _ssNickCompletion :: WordCompletionMode -- ^ Nick completion mode for this server , _ssLogDir :: Maybe FilePath -- ^ Directory to save logs of chat+ , _ssProtocolFamily :: Maybe Family -- ^ Protocol family to connect with } deriving Show @@ -148,6 +150,7 @@ , _ssAutoconnect = False , _ssNickCompletion = defaultNickWordCompleteMode , _ssLogDir = Nothing+ , _ssProtocolFamily = Nothing } serverSpec :: ValueSpecs (ServerSettings -> ServerSettings)@@ -249,7 +252,17 @@ , opt "log-dir" ssLogDir stringSpec "Path to log file directory for this server"++ , opt "protocol-family" ssProtocolFamily protocolFamilySpec+ "IP protocol family to use for this connection" ]+++-- | Specification for IP protocol family.+protocolFamilySpec :: ValueSpecs Family+protocolFamilySpec =+ AF_INET <$ atomSpec "inet"+ <!> AF_INET6 <$ atomSpec "inet6" nicksSpec :: ValueSpecs (NonEmpty Text)
src/Client/Image/PackedImage.hs view
@@ -45,8 +45,8 @@ = HorizText' !Attr -- don't unpack, these get reused from the palette {-# UNPACK #-} !S.Text- {-# UNPACK #-} !Int- {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int -- terminal width+ {-# UNPACK #-} !Int -- codepoint count !Image' | EmptyImage' deriving (Show)@@ -91,6 +91,7 @@ Nothing -> 0 Just ix -> ix +-- | Width in terms of terminal columns imageWidth :: Image' -> Int imageWidth = go 0 where
src/Client/Image/StatusLine.hs view
@@ -338,6 +338,7 @@ FocusKeyMap -> Just $ string (view palLabel pal) "keymap" FocusHelp mb -> Just $ string (view palLabel pal) "help" <> opt mb+ FocusIgnoreList -> Just $ string (view palLabel pal) "ignores" FocusRtsStats -> Just $ string (view palLabel pal) "rtsstats" FocusMasks m -> Just $ mconcat [ string (view palLabel pal) "masks"
src/Client/Image/Textbox.hs view
@@ -144,7 +144,7 @@ String {- ^ input text -} -> Image' {- ^ output image -} renderLine st pal myNick nicks macros focused ('/':xs) =- char defAttr '/' <> string attr cmd <> continue rest+ char defAttr '/' <> string attr cleanCmd <> continue rest where specAttr spec = case parse st spec rest of@@ -152,6 +152,8 @@ Just{} -> view palCommandReady pal (cmd, rest) = break isSpace xs+ cleanCmd = map cleanChar cmd+ allCommands = (Left <$> macros) <> (Right <$> commands) (attr, continue) = case recognize (Text.pack cmd) allCommands of
+ src/Client/Mask.hs view
@@ -0,0 +1,102 @@+{-# Language OverloadedStrings #-}+module Client.Mask+ ( Mask+ , matchMask+ , buildMask+ ) where++import Irc.UserInfo+import Irc.Identifier+import Data.Text (Text)+import qualified Data.Text as Text+import Text.Regex.TDFA+import Text.Regex.TDFA.String (compile)+import Data.List++newtype Mask = Mask Regex++-- | Compile a list of masks down to a single, reuseable 'Mask' value+-- suitable for being used with 'matchMask'.+--+-- Masks can match zero-to-many arbitrary characters with @*@.+--+-- Masks can match one arbitrary character with @?@.+--+-- Literal @*@ @?@ and @|@ can be matched with a preceding @\@.+--+-- Missing host or username components of a mask will automatically+-- be treated as wildcards.+buildMask ::+ [Identifier] {- ^ masks -} ->+ Mask+buildMask patterns =+ case componentsToMask (map (translate . parseMaskComponents . idTextNorm) patterns) of+ Left e -> error e+ Right m -> m++-- | Determine if a given 'Mask' matches a given 'UserInfo'+matchMask :: Mask -> UserInfo -> Bool+matchMask (Mask re) userInfo =+ matchTest re (Text.unpack (normalized (renderUserInfo userInfo)))++normalized :: Text -> Text+normalized = idTextNorm . mkId++-- | Parse a mask into the nick, user, and hostname components+-- while replacing omitted components with @"*"@.+parseMaskComponents :: Text -> String+parseMaskComponents str = Text.unpack nick ++ "!" ++ user ++ "@" ++ host+ where+ (nickuser,rawhost) = Text.break (=='@') str+ (nick ,rawuser) = Text.break (=='!') nickuser++ user = defaultWild rawuser+ host = defaultWild rawhost++ defaultWild x =+ case Text.uncons x of+ Nothing -> "*"+ Just (_, y) -> Text.unpack y++componentsToMask :: [String] -> Either String Mask+componentsToMask xs =+ Mask <$> compile defaultCompOpt { multiline = False }+ defaultExecOpt { captureGroups = False }+ ("^(" ++ intercalate "|" xs ++ ")$")++-- | Translate from the languge of masks to the language of+-- regular expressions.+--+-- Masks support the @*@ (many) and @?@ (one) wildcards. Wildcards+-- and @\@ can be escaped by preceding them with a @\@. All other+-- uses of @\@ are treated as matching the literal backslash.+translate :: String -> String+translate [] = []+translate ('\\' : '*' : xs) = '\\' : '*' : translate xs+translate ('\\' : '?' : xs) = '\\' : '?' : translate xs+translate ('\\' : '\\' : xs) = '\\' : '\\' : translate xs+translate ('*' : xs) = '.' : '*' : translate xs+translate ('?' : xs) = '.' : '?' : translate xs+translate (x : xs)+ | isMetaChar x = '\\' : x : translate xs+ | otherwise = x : translate xs++-- | returns True iff the charactr is a regular expression meta character:+-- @^$\\.|*?+()[]{}@+isMetaChar :: Char -> Bool+isMetaChar c = case c of+ '^' -> True+ '\\' -> True+ '.' -> True+ '|' -> True+ '*' -> True+ '?' -> True+ '+' -> True+ '(' -> True+ ')' -> True+ '[' -> True+ ']' -> True+ '{' -> True+ '}' -> True+ '$' -> True+ _ -> False
src/Client/Message.hs view
@@ -61,7 +61,7 @@ | PartSummary {-# UNPACK #-} !Identifier | NickSummary {-# UNPACK #-} !Identifier {-# UNPACK #-} !Identifier | ReplySummary {-# UNPACK #-} !ReplyCode- | ChatSummary {-# UNPACK #-} !Identifier+ | ChatSummary {-# UNPACK #-} !UserInfo | CtcpSummary {-# UNPACK #-} !Identifier | NoSummary deriving (Eq, Show)@@ -87,10 +87,10 @@ Part who _ _ -> PartSummary (userNick who) Quit who _ -> QuitSummary (userNick who) Nick who who' -> NickSummary (userNick who) who'- Privmsg who _ _ -> ChatSummary (userNick who)- Notice who _ _ -> ChatSummary (userNick who)- Ctcp who _ "ACTION" _ -> ChatSummary (userNick who)+ Privmsg who _ _ -> ChatSummary who+ Notice who _ _ -> ChatSummary who+ Ctcp who _ "ACTION" _ -> ChatSummary who Ctcp who _ _ _ -> CtcpSummary (userNick who)- CtcpNotice who _ _ _ -> ChatSummary (userNick who)+ CtcpNotice who _ _ _ -> ChatSummary who Reply code _ -> ReplySummary code _ -> NoSummary
src/Client/Network/Connect.hs view
@@ -33,6 +33,11 @@ (view ssTlsServerCert args) (view ssTlsCiphers args) + family =+ case view ssProtocolFamily args of+ Nothing -> defaultFamily+ Just pf -> pf+ useSecure = case view ssTls args of UseInsecure -> Nothing@@ -45,7 +50,8 @@ (view ssSocksPort args) in ConnectionParams- { cpHost = view ssHostName args+ { cpFamily = family+ , cpHost = view ssHostName args , cpPort = ircPort args , cpTls = useSecure , cpSocks = proxySettings
src/Client/State.hs view
@@ -33,6 +33,7 @@ , clientSubfocus , clientNetworkMap , clientIgnores+ , clientIgnoreMask , clientConnection , clientBell , clientExtensions@@ -111,6 +112,7 @@ import Client.Image.Message import Client.Image.Palette import Client.Log+import Client.Mask import Client.Message import Client.Network.Async import Client.State.Channel@@ -181,7 +183,8 @@ , _clientBell :: !Bool -- ^ sound a bell next draw - , _clientIgnores :: !(HashSet Identifier) -- ^ ignored nicknames+ , _clientIgnores :: !(HashSet Identifier) -- ^ ignored masks+ , _clientIgnoreMask :: Mask -- ^ precomputed ignore regular expression (lazy) , _clientExtensions :: !ExtensionState -- ^ state of loaded extensions , _clientLogQueue :: ![LogLine] -- ^ log lines ready to write@@ -240,11 +243,13 @@ withExtensionState $ \exts -> - do events <- atomically newTQueue+ do events <- atomically newTQueue+ let ignoreIds = map mkId (view configIgnores cfg) k ClientState { _clientWindows = _Empty # () , _clientNetworkMap = _Empty # ()- , _clientIgnores = view configIgnores cfg+ , _clientIgnores = HashSet.fromList ignoreIds+ , _clientIgnoreMask = buildMask ignoreIds , _clientConnections = _Empty # () , _clientTextBox = Edit.defaultEditBox , _clientTextBoxOffset = 0@@ -404,17 +409,17 @@ _ -> Nothing where checkUser !who- | identIgnored (userNick who) st = Just (userNick who)- | otherwise = Nothing+ | identIgnored who st = Just (userNick who)+ | otherwise = Nothing -- | Predicate for nicknames to determine if messages should be ignored. identIgnored ::- Identifier {- ^ nickname -} ->+ UserInfo {- ^ target user -} -> ClientState {- ^ client state -} -> Bool {- ^ is ignored -}-identIgnored who st = HashSet.member who (view clientIgnores st)+identIgnored who st = matchMask (view clientIgnoreMask st) who -- | Record a message in the windows corresponding to the given target
src/Client/State/Focus.hs view
@@ -54,6 +54,7 @@ | FocusKeyMap -- ^ Show key bindings | FocusHelp (Maybe Text) -- ^ Show help window with optional command | FocusRtsStats -- ^ Show GHC RTS statistics+ | FocusIgnoreList -- ^ Show ignored masks deriving (Eq,Show) -- | Unfocused first, followed by focuses sorted by network.
src/Client/View.hs view
@@ -19,6 +19,7 @@ import Client.View.ChannelInfo import Client.View.Digraphs import Client.View.Help+import Client.View.IgnoreList import Client.View.KeyMap import Client.View.MaskList import Client.View.Mentions@@ -49,6 +50,7 @@ (_, FocusKeyMap) -> keyMapLines st (_, FocusHelp mb) -> helpImageLines st mb pal (_, FocusRtsStats) -> rtsStatsLines (view clientRtsStats st) pal+ (_, FocusIgnoreList) -> ignoreListLines (view clientIgnores st) pal _ -> chatMessageImages focus w st where pal = clientPalette st
+ src/Client/View/IgnoreList.hs view
@@ -0,0 +1,44 @@+{-# Language OverloadedStrings #-}+{-|+Module : Client.View.IgnoreList+Description : Line renderers for ignore mask list view+Copyright : (c) Eric Mertens, 2016+License : ISC+Maintainer : emertens@gmail.com++This module renders the lines used to list the ignore masks.+-}+module Client.View.IgnoreList+ ( ignoreListLines+ ) where++import Client.Image.PackedImage+import Client.Image.Palette+import Client.Image.Message+import Graphics.Vty.Attributes+import Irc.Identifier+import Data.HashSet (HashSet)+import Data.Foldable+import Data.Semigroup+import Control.Lens++-- | Render the lines used in a channel mask list.+ignoreListLines ::+ HashSet Identifier {- ^ ignore masks -} ->+ Palette {- ^ palette -} ->+ [Image']+ignoreListLines ignores pal =+ summaryLine ignores pal :+ [ text' defAttr (cleanText (idText mask))+ | mask <- toList ignores ]+++-- | Render a summary describing the number of ignore masks.+summaryLine ::+ HashSet Identifier {- ^ ignore masks -} ->+ Palette {- ^ palette -} ->+ Image'+summaryLine ignores pal+ | null ignores = text' (view palError pal) "Ignore list empty"+ | otherwise = text' (view palLabel pal) "Ignore entries: "+ <> string defAttr (show (length ignores))
src/Client/View/Messages.hs view
@@ -31,6 +31,7 @@ import Data.Semigroup import Irc.Identifier import Irc.Message+import Irc.UserInfo chatMessageImages :: Focus -> Int -> ClientState -> [Image']@@ -169,5 +170,5 @@ {- ^ Image, incoming identifier, outgoing identifier if changed -} metadataWindowLine st wl = case view wlSummary wl of- ChatSummary who -> (ignoreImage, who, Nothing) <$ guard (identIgnored who st)+ ChatSummary who -> (ignoreImage, userNick who, Nothing) <$ guard (identIgnored who st) summary -> metadataImg summary
src/Client/View/UrlSelection.hs view
@@ -31,6 +31,7 @@ import Data.Text (Text) import Graphics.Vty.Attributes import Irc.Identifier+import Irc.UserInfo (userNick) import Text.Read (readMaybe) @@ -76,7 +77,7 @@ QuitSummary who -> Just who PartSummary who -> Just who NickSummary who _ -> Just who- ChatSummary who -> Just who+ ChatSummary who -> Just (userNick who) CtcpSummary who -> Just who ReplySummary {} -> Nothing NoSummary -> Nothing