-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathCustomCommand.hs
More file actions
231 lines (216 loc) · 8.31 KB
/
CustomCommand.hs
File metadata and controls
231 lines (216 loc) · 8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Bot.CustomCommand
( addCustomCommand
, deleteCustomCommand
, dispatchCustomCommand
, updateCustomCommand
, showCustomCommand
, timesCustomCommand
) where
import Bot.CustomCommandType
import Bot.Expr
import Bot.Flip
import Bot.Help
import Bot.Replies
import Command
import Control.Monad
import Control.Monad.Trans.Maybe
import Data.Functor.Compose
import qualified Data.Map as M
import Data.Maybe
import Data.Proxy
import qualified Data.Text as T
import Data.Time
import Effect
import Entity
import HyperNerd.Parser
import qualified Network.URI.Encode as URI
import Property
import Reaction
import Text.InterpolatedString.QM
import Transport
customCommandByName :: T.Text -> MaybeT Effect (Entity CustomCommand)
customCommandByName name =
MaybeT $
fmap listToMaybe $
selectEntities Proxy $ Filter (PropertyEquals "name" $ PropertyText name) All
addCustomCommand :: CommandTable -> Reaction Message (T.Text, T.Text)
addCustomCommand builtinCommands =
Reaction $ \mesg@Message { messageSender = sender
, messageContent = (name, message)
} -> do
res <- refreshHelpAndUnpack builtinCommands (fst <$> mesg)
case res of
(Just _, Nothing) ->
replyToSender sender [qms|Command '{name}' already exists|]
(Nothing, Just _) ->
replyToSender
sender
[qms|There is already a builtin command with name '{name}'|]
(Just _, Just _) ->
errorEff [qms|Custom command '{name}' collide with a built in command|]
(Nothing, Nothing) -> do
void $
createEntity
Proxy
CustomCommand
{ customCommandName = name
, customCommandMessage = message
, customCommandTimes = 0
}
replyToSender sender [qms|Added command '{name}'|]
refreshHelpAndUnpack ::
CommandTable
-> Message T.Text
-> Effect (Maybe (Entity CustomCommand), Maybe BuiltinCommand)
refreshHelpAndUnpack builtinCommands mesg@Message {messageContent = name} = do
runReaction refreshHelpGistId mesg
customCommand <- runMaybeT $ customCommandByName name
let builtinCommand = M.lookup name builtinCommands
pure (customCommand, builtinCommand)
deleteCustomCommand :: CommandTable -> Reaction Message T.Text
deleteCustomCommand builtinCommands =
Reaction $ \mesg@Message {messageSender = sender, messageContent = name} -> do
res <- refreshHelpAndUnpack builtinCommands mesg
case res of
(Just _, Nothing) -> do
void $
deleteEntities (Proxy :: Proxy CustomCommand) $
Filter (PropertyEquals "name" $ PropertyText name) All
replyToSender sender [qms|Command '{name}' has been removed|]
(Nothing, Just _) ->
replyToSender
sender
[qms|Command '{name}' is builtin and can't be removed like that|]
(Just _, Just _) ->
errorEff
[qms|Custom command '{name}'
collide with a built in command|]
(Nothing, Nothing) ->
replyToSender sender [qms|Command '{name}' does not exist|]
showCustomCommand :: CommandTable -> Reaction Message T.Text
showCustomCommand builtinCommands =
Reaction $ \Message {messageContent = name, messageSender = sender} -> do
customCommand <- runMaybeT $ customCommandByName name
let builtinCommand = M.lookup name builtinCommands
case (customCommand, builtinCommand) of
(Just cmd, Nothing) ->
replyToSender
sender
[qms|Command '{name}' defined as
'{customCommandMessage $ entityPayload cmd}'|]
(Nothing, Just bc) ->
replyToSender
sender
[qms|Command '{name}' is builtin. Look into the code
for the definition: {bcGitHubLocation bc}|]
(Just _, Just _) ->
errorEff
[qms|Custom command '{name}' collide with
a built in command|]
(Nothing, Nothing) ->
replyToSender sender [qms|Command '{name}' does not exist|]
timesCustomCommand :: CommandTable -> Reaction Message T.Text
timesCustomCommand builtinCommands =
Reaction $ \Message {messageSender = sender, messageContent = name} -> do
customCommand <- runMaybeT $ customCommandByName name
let builtinCommand = M.lookup name builtinCommands
case (customCommand, builtinCommand) of
(Just cmd, Nothing) ->
replyToSender
sender
[qms|Command '{name}' was invoked
{customCommandTimes $ entityPayload cmd} times.|]
(Nothing, Just _) ->
replyToSender
sender
[qms|Command '{name}' is builtin and
we don't track the frequency usage for builtin commands.
See https://github.com/tsoding/HyperNerd/issues/334
for more info.|]
(Just _, Just _) ->
errorEff
[qms|Custom command '{name}' collide with
a built in command|]
(Nothing, Nothing) ->
replyToSender sender [qms|Command '{name}' does not exist|]
updateCustomCommand :: CommandTable -> Reaction Message (T.Text, T.Text)
updateCustomCommand builtinCommands =
Reaction $ \mesg@Message { messageSender = sender
, messageContent = (name, message)
} -> do
res <- refreshHelpAndUnpack builtinCommands (fst <$> mesg)
case res of
(Just cmd, Nothing) -> do
void $ updateEntityById (replaceCustomCommandMessage message <$> cmd)
replyToSender sender [qms|Command '{name}' has been updated|]
(Nothing, Just _) ->
replyToSender
sender
[qms|Command '{name}' is builtin and
can't be updated like that|]
(Just _, Just _) ->
errorEff
[qms|Custom command '{name}' collide with
a built in command|]
(Nothing, Nothing) ->
replyToSender sender [qms|Command '{name}' does not exist|]
evalExpr :: M.Map T.Text T.Text -> Expr -> T.Text
evalExpr _ (TextExpr t) = t
evalExpr vars (FunCallExpr "or" args) =
fromMaybe "" $ listToMaybe $ dropWhile T.null $ map (evalExpr vars) args
evalExpr vars (FunCallExpr "urlencode" args) =
T.concat $ map (T.pack . URI.encode . T.unpack . evalExpr vars) args
evalExpr vars (FunCallExpr "flip" args) =
T.concat $ map (flipText . evalExpr vars) args
evalExpr vars (FunCallExpr funame _) = fromMaybe "" $ M.lookup funame vars
expandVars :: M.Map T.Text T.Text -> [Expr] -> T.Text
expandVars vars = T.concat . map (evalExpr vars)
-- TODO(#598): reimplement expandCustomCommandVars with Bot.Expr when it's ready
expandCustomCommandVars ::
Message (Command T.Text, Entity CustomCommand)
-> Effect (Either String CustomCommand)
expandCustomCommandVars Message { messageSender = sender
, messageContent = (Command {commandArgs = args}, Entity {entityPayload = customCommand})
} = do
timestamp <- now
let day = utctDay timestamp
let (yearNum, monthNum, dayNum) = toGregorian day
let code = runParser exprs $ customCommandMessage customCommand
let times = customCommandTimes customCommand
let vars =
M.fromList
[ ("times", [qms|{times}|])
, ("year", [qms|{yearNum}|])
, ("month", [qms|{monthNum}|])
, ("day", [qms|{dayNum}|])
, ("date", [qms|{showGregorian day}|])
, ("sender", mentionSender sender)
, ("1", args)
]
case code of
Left msg -> return $ Left (show msg)
Right (_, code') ->
return $
Right customCommand {customCommandMessage = expandVars vars code'}
bumpCustomCommandTimes :: CustomCommand -> CustomCommand
bumpCustomCommandTimes customCommand =
customCommand {customCommandTimes = customCommandTimes customCommand + 1}
replaceCustomCommandMessage :: T.Text -> CustomCommand -> CustomCommand
replaceCustomCommandMessage message customCommand =
customCommand {customCommandMessage = message}
dispatchCustomCommand :: Reaction Message (Command T.Text)
dispatchCustomCommand =
liftFst (runMaybeT . customCommandByName . commandName) $
cmapR f $
ignoreNothing $
transR Compose $
liftR (updateEntityById . fmap bumpCustomCommandTimes) $
ignoreNothing $
transR getCompose $
dupLiftR expandCustomCommandVars $
replyLeft $ cmapR customCommandMessage sayMessage
where
f :: Functor m => (a, m b) -> m (a, b)
f = uncurry $ fmap . (,)