1
+ using System . Reflection ;
2
+
3
+ namespace Cliptok . Commands
4
+ {
5
+ public class GlobalCmds
6
+ {
7
+ // These commands will be registered outside of the home server and can be used anywhere, even in DMs.
8
+
9
+ // Most of this is taken from DSharpPlus.CommandsNext and adapted to fit here.
10
+ // https://github.com/DSharpPlus/DSharpPlus/blob/1c1aa15/DSharpPlus.CommandsNext/CommandsNextExtension.cs#L829
11
+ [ Command ( "helptextcmd" ) , Description ( "Displays command help." ) ]
12
+ [ TextAlias ( "help" ) ]
13
+ [ AllowedProcessors ( typeof ( TextCommandProcessor ) ) ]
14
+ public async Task Help ( CommandContext ctx , [ Description ( "Command to provide help for." ) , RemainingText ] string command = "" )
15
+ {
16
+ var commandSplit = command . Split ( ' ' ) ;
17
+
18
+ DiscordEmbedBuilder helpEmbed = new ( )
19
+ {
20
+ Title = "Help" ,
21
+ Color = new DiscordColor ( "#0080ff" )
22
+ } ;
23
+
24
+ IEnumerable < Command > cmds = ctx . Extension . Commands . Values . Where ( cmd =>
25
+ cmd . Attributes . Any ( attr => attr is AllowedProcessorsAttribute apAttr
26
+ && apAttr . Processors . Contains ( typeof ( TextCommandProcessor ) ) ) ) ;
27
+
28
+ if ( commandSplit . Length != 0 && commandSplit [ 0 ] != "" )
29
+ {
30
+ commandSplit [ 0 ] += "textcmd" ;
31
+
32
+ Command ? cmd = null ;
33
+ IEnumerable < Command > ? searchIn = cmds ;
34
+ foreach ( string c in commandSplit )
35
+ {
36
+ if ( searchIn is null )
37
+ {
38
+ cmd = null ;
39
+ break ;
40
+ }
41
+
42
+ StringComparison comparison = StringComparison . InvariantCultureIgnoreCase ;
43
+ StringComparer comparer = StringComparer . InvariantCultureIgnoreCase ;
44
+ cmd = searchIn . FirstOrDefault ( xc => xc . Name . Equals ( c , comparison ) || ( ( xc . Attributes . FirstOrDefault ( x => x is TextAliasAttribute ) as TextAliasAttribute ) ? . Aliases . Contains ( c . Replace ( "textcmd" , "" ) , comparer ) ?? false ) ) ;
45
+
46
+ if ( cmd is null )
47
+ {
48
+ break ;
49
+ }
50
+
51
+ IEnumerable < ContextCheckAttribute > failedChecks = await CheckPermissionsAsync ( ctx , cmd ) ;
52
+ if ( failedChecks . Any ( ) )
53
+ {
54
+ return ;
55
+ }
56
+
57
+ searchIn = cmd . Subcommands . Any ( ) ? cmd . Subcommands : null ;
58
+ }
59
+
60
+ if ( cmd is null )
61
+ {
62
+ throw new CommandNotFoundException ( string . Join ( " " , commandSplit ) ) ;
63
+ }
64
+
65
+ helpEmbed . Description = $ "`{ cmd . Name . Replace ( "textcmd" , "" ) } `: { cmd . Description ?? "No description provided." } ";
66
+
67
+
68
+ if ( cmd . Subcommands . Count > 0 && cmd . Subcommands . Any ( subCommand => subCommand . Attributes . Any ( attr => attr is DefaultGroupCommandAttribute ) ) )
69
+ {
70
+ helpEmbed . Description += "\n \n This group can be executed as a standalone command." ;
71
+ }
72
+
73
+ var aliases = cmd . Method ? . GetCustomAttributes < TextAliasAttribute > ( ) . FirstOrDefault ( ) ? . Aliases ?? ( cmd . Attributes . FirstOrDefault ( x => x is TextAliasAttribute ) as TextAliasAttribute ) ? . Aliases ?? null ;
74
+ if ( aliases is not null && aliases . Length > 1 )
75
+ {
76
+ var aliasStr = "" ;
77
+ foreach ( var alias in aliases )
78
+ {
79
+ if ( alias == cmd . Name . Replace ( "textcmd" , "" ) )
80
+ continue ;
81
+
82
+ aliasStr += $ "`{ alias } `, ";
83
+ }
84
+ aliasStr = aliasStr . TrimEnd ( ',' , ' ' ) ;
85
+ helpEmbed . AddField ( "Aliases" , aliasStr ) ;
86
+ }
87
+
88
+ var arguments = cmd . Method ? . GetParameters ( ) ;
89
+ if ( arguments is not null && arguments . Length > 0 )
90
+ {
91
+ var argumentsStr = $ "`{ cmd . Name . Replace ( "textcmd" , "" ) } ";
92
+ foreach ( var arg in arguments )
93
+ {
94
+ if ( arg . ParameterType is CommandContext || arg . ParameterType . IsSubclassOf ( typeof ( CommandContext ) ) )
95
+ continue ;
96
+
97
+ bool isCatchAll = arg . GetCustomAttribute < RemainingTextAttribute > ( ) != null ;
98
+ argumentsStr += $ "{ ( arg . IsOptional || isCatchAll ? " [" : " <" ) } { arg . Name } { ( isCatchAll ? "..." : "" ) } { ( arg . IsOptional || isCatchAll ? "]" : ">" ) } ";
99
+ }
100
+
101
+ argumentsStr += "`\n " ;
102
+
103
+ foreach ( var arg in arguments )
104
+ {
105
+ if ( arg . ParameterType is CommandContext || arg . ParameterType . IsSubclassOf ( typeof ( CommandContext ) ) )
106
+ continue ;
107
+
108
+ argumentsStr += $ "`{ arg . Name } ({ arg . ParameterType . Name } )`: { arg . GetCustomAttribute < DescriptionAttribute > ( ) ? . Description ?? "No description provided." } \n ";
109
+ }
110
+
111
+ helpEmbed . AddField ( "Arguments" , argumentsStr . Trim ( ) ) ;
112
+ }
113
+ //helpBuilder.WithCommand(cmd);
114
+
115
+ if ( cmd . Subcommands . Any ( ) )
116
+ {
117
+ IEnumerable < Command > commandsToSearch = cmd . Subcommands ;
118
+ List < Command > eligibleCommands = [ ] ;
119
+ foreach ( Command ? candidateCommand in commandsToSearch )
120
+ {
121
+ var executionChecks = candidateCommand . Attributes . Where ( x => x is ContextCheckAttribute ) as List < ContextCheckAttribute > ;
122
+
123
+ if ( executionChecks == null || ! executionChecks . Any ( ) )
124
+ {
125
+ eligibleCommands . Add ( candidateCommand ) ;
126
+ continue ;
127
+ }
128
+
129
+ IEnumerable < ContextCheckAttribute > candidateFailedChecks = await CheckPermissionsAsync ( ctx , candidateCommand ) ;
130
+ if ( ! candidateFailedChecks . Any ( ) )
131
+ {
132
+ eligibleCommands . Add ( candidateCommand ) ;
133
+ }
134
+ }
135
+
136
+ if ( eligibleCommands . Count != 0 )
137
+ {
138
+ eligibleCommands = eligibleCommands . OrderBy ( x => x . Name ) . ToList ( ) ;
139
+ string cmdList = "" ;
140
+ foreach ( var subCommand in eligibleCommands )
141
+ {
142
+ cmdList += $ "`{ subCommand . Name } `, ";
143
+ }
144
+ helpEmbed . AddField ( "Subcommands" , cmdList . TrimEnd ( ',' , ' ' ) ) ;
145
+ //helpBuilder.WithSubcommands(eligibleCommands.OrderBy(xc => xc.Name));
146
+ }
147
+ }
148
+ }
149
+ else
150
+ {
151
+ IEnumerable < Command > commandsToSearch = cmds ;
152
+ List < Command > eligibleCommands = [ ] ;
153
+ foreach ( Command ? sc in commandsToSearch )
154
+ {
155
+ var executionChecks = sc . Attributes . Where ( x => x is ContextCheckAttribute ) ;
156
+
157
+ if ( ! executionChecks . Any ( ) )
158
+ {
159
+ eligibleCommands . Add ( sc ) ;
160
+ continue ;
161
+ }
162
+
163
+ IEnumerable < ContextCheckAttribute > candidateFailedChecks = await CheckPermissionsAsync ( ctx , sc ) ;
164
+ if ( ! candidateFailedChecks . Any ( ) )
165
+ {
166
+ eligibleCommands . Add ( sc ) ;
167
+ }
168
+ }
169
+
170
+ if ( eligibleCommands . Count != 0 )
171
+ {
172
+ eligibleCommands = eligibleCommands . OrderBy ( x => x . Name ) . ToList ( ) ;
173
+ string cmdList = "" ;
174
+ foreach ( var eligibleCommand in eligibleCommands )
175
+ {
176
+ cmdList += $ "`{ eligibleCommand . Name . Replace ( "textcmd" , "" ) } `, ";
177
+ }
178
+ helpEmbed . AddField ( "Commands" , cmdList . TrimEnd ( ',' , ' ' ) ) ;
179
+ helpEmbed . Description = "Listing all top-level commands and groups. Specify a command to see more information." ;
180
+ //helpBuilder.WithSubcommands(eligibleCommands.OrderBy(xc => xc.Name));
181
+ }
182
+ }
183
+
184
+ DiscordMessageBuilder builder = new DiscordMessageBuilder ( ) . AddEmbed ( helpEmbed ) ;
185
+
186
+ await ctx . RespondAsync ( builder ) ;
187
+ }
188
+
189
+ [ Command ( "pingtextcmd" ) ]
190
+ [ TextAlias ( "ping" ) ]
191
+ [ Description ( "Pong? This command lets you know whether I'm working well." ) ]
192
+ [ AllowedProcessors ( typeof ( TextCommandProcessor ) ) ]
193
+ public async Task Ping ( TextCommandContext ctx )
194
+ {
195
+ ctx . Client . Logger . LogDebug ( ctx . Client . GetConnectionLatency ( Program . cfgjson . ServerID ) . ToString ( ) ) ;
196
+ DiscordMessage return_message = await ctx . Message . RespondAsync ( "Pinging..." ) ;
197
+ ulong ping = ( return_message . Id - ctx . Message . Id ) >> 22 ;
198
+ char [ ] choices = new char [ ] { 'a' , 'e' , 'o' , 'u' , 'i' , 'y' } ;
199
+ char letter = choices [ Program . rand . Next ( 0 , choices . Length ) ] ;
200
+ await return_message . ModifyAsync ( $ "P{ letter } ng! 🏓\n " +
201
+ $ "• It took me `{ ping } ms` to reply to your message!\n " +
202
+ $ "• Last Websocket Heartbeat took `{ Math . Round ( ctx . Client . GetConnectionLatency ( 0 ) . TotalMilliseconds , 0 ) } ms`!") ;
203
+ }
204
+
205
+ [ Command ( "userinfo" ) ]
206
+ [ TextAlias ( "user-info" , "whois" ) ]
207
+ [ Description ( "Show info about a user." ) ]
208
+ [ AllowedProcessors ( typeof ( SlashCommandProcessor ) , typeof ( TextCommandProcessor ) ) ]
209
+ public async Task UserInfoSlashCommand ( CommandContext ctx , [ Parameter ( "user" ) , Description ( "The user to retrieve information about." ) ] DiscordUser user = null , [ Parameter ( "public" ) , Description ( "Whether to show the output publicly." ) ] bool publicMessage = false )
210
+ {
211
+ if ( user is null )
212
+ user = ctx . User ;
213
+
214
+ await ctx . RespondAsync ( embed : await DiscordHelpers . GenerateUserEmbed ( user , ctx . Guild ) , ephemeral : ! publicMessage ) ;
215
+ }
216
+
217
+ [ Command ( "remindmetextcmd" ) ]
218
+ [ Description ( "Set a reminder for yourself. Example: !reminder 1h do the thing" ) ]
219
+ [ TextAlias ( "remindme" , "reminder" , "rember" , "wemember" , "remember" , "remind" ) ]
220
+ [ AllowedProcessors ( typeof ( TextCommandProcessor ) ) ]
221
+ [ RequireHomeserverPerm ( ServerPermLevel . Tier4 , WorkOutside = true ) ]
222
+ public async Task RemindMe (
223
+ TextCommandContext ctx ,
224
+ [ Description ( "The amount of time to wait before reminding you. For example: 2s, 5m, 1h, 1d" ) ] string timetoParse ,
225
+ [ RemainingText , Description ( "The text to send when the reminder triggers." ) ] string reminder
226
+ )
227
+ {
228
+ DateTime t = HumanDateParser . HumanDateParser . Parse ( timetoParse ) ;
229
+ if ( t <= DateTime . Now )
230
+ {
231
+ await ctx . RespondAsync ( $ "{ Program . cfgjson . Emoji . Error } Time can't be in the past!") ;
232
+ return ;
233
+ }
234
+ #if ! DEBUG
235
+ else if ( t < ( DateTime . Now + TimeSpan . FromSeconds ( 59 ) ) )
236
+ {
237
+ await ctx . RespondAsync ( $"{Program.cfgjson.Emoji.Error} Time must be at least a minute in the future!" ) ;
238
+ return ;
239
+ }
240
+ #endif
241
+ string guildId ;
242
+
243
+ if ( ctx . Channel . IsPrivate )
244
+ guildId = "@me" ;
245
+ else
246
+ guildId = ctx . Guild . Id . ToString ( ) ;
247
+
248
+ var reminderObject = new Reminder ( )
249
+ {
250
+ UserID = ctx . User . Id ,
251
+ ChannelID = ctx . Channel . Id ,
252
+ MessageID = ctx . Message . Id ,
253
+ MessageLink = $ "https://discord.com/channels/{ guildId } /{ ctx . Channel . Id } /{ ctx . Message . Id } ",
254
+ ReminderText = reminder ,
255
+ ReminderTime = t ,
256
+ OriginalTime = DateTime . Now
257
+ } ;
258
+
259
+ await Program . db . ListRightPushAsync ( "reminders" , JsonConvert . SerializeObject ( reminderObject ) ) ;
260
+ await ctx . RespondAsync ( $ "{ Program . cfgjson . Emoji . Success } I'll try my best to remind you about that on <t:{ TimeHelpers . ToUnixTimestamp ( t ) } :f> (<t:{ TimeHelpers . ToUnixTimestamp ( t ) } :R>)") ; // (In roughly **{TimeHelpers.TimeToPrettyFormat(t.Subtract(ctx.Message.Timestamp.DateTime), false)}**)");
261
+ }
262
+
263
+ public class Reminder
264
+ {
265
+ [ JsonProperty ( "userID" ) ]
266
+ public ulong UserID { get ; set ; }
267
+
268
+ [ JsonProperty ( "channelID" ) ]
269
+ public ulong ChannelID { get ; set ; }
270
+
271
+ [ JsonProperty ( "messageID" ) ]
272
+ public ulong MessageID { get ; set ; }
273
+
274
+ [ JsonProperty ( "messageLink" ) ]
275
+ public string MessageLink { get ; set ; }
276
+
277
+ [ JsonProperty ( "reminderText" ) ]
278
+ public string ReminderText { get ; set ; }
279
+
280
+ [ JsonProperty ( "reminderTime" ) ]
281
+ public DateTime ReminderTime { get ; set ; }
282
+
283
+ [ JsonProperty ( "originalTime" ) ]
284
+ public DateTime OriginalTime { get ; set ; }
285
+ }
286
+
287
+ // Runs command context checks manually. Returns a list of failed checks.
288
+ // Unfortunately DSharpPlus.Commands does not provide a way to execute a command's context checks manually,
289
+ // so this will have to do. This may not include all checks, but it includes everything I could think of. -Milkshake
290
+ private async Task < IEnumerable < ContextCheckAttribute > > CheckPermissionsAsync ( CommandContext ctx , Command cmd )
291
+ {
292
+ var contextChecks = cmd . Attributes . Where ( x => x is ContextCheckAttribute ) ;
293
+ var failedChecks = new List < ContextCheckAttribute > ( ) ;
294
+
295
+ foreach ( var check in contextChecks )
296
+ {
297
+ if ( check is HomeServerAttribute homeServerAttribute )
298
+ {
299
+ if ( ctx . Channel . IsPrivate || ctx . Guild is null || ctx . Guild . Id != Program . cfgjson . ServerID )
300
+ {
301
+ failedChecks . Add ( homeServerAttribute ) ;
302
+ }
303
+ }
304
+
305
+ if ( check is RequireHomeserverPermAttribute requireHomeserverPermAttribute )
306
+ {
307
+ if ( ctx . Member is null && ! requireHomeserverPermAttribute . WorkOutside )
308
+ {
309
+ failedChecks . Add ( requireHomeserverPermAttribute ) ;
310
+ }
311
+ else
312
+ {
313
+ if ( ! requireHomeserverPermAttribute . WorkOutside )
314
+ {
315
+ var level = await GetPermLevelAsync ( ctx . Member ) ;
316
+ if ( level < requireHomeserverPermAttribute . TargetLvl )
317
+ {
318
+ failedChecks . Add ( requireHomeserverPermAttribute ) ;
319
+ }
320
+ }
321
+ }
322
+
323
+ }
324
+
325
+ if ( check is RequirePermissionsAttribute requirePermissionsAttribute )
326
+ {
327
+ if ( ctx . Member is null || ctx . Guild is null
328
+ || ! ctx . Channel . PermissionsFor ( ctx . Member ) . HasAllPermissions ( requirePermissionsAttribute . UserPermissions )
329
+ || ! ctx . Channel . PermissionsFor ( ctx . Guild . CurrentMember ) . HasAllPermissions ( requirePermissionsAttribute . BotPermissions ) )
330
+ {
331
+ failedChecks . Add ( requirePermissionsAttribute ) ;
332
+ }
333
+ }
334
+
335
+ if ( check is IsBotOwnerAttribute isBotOwnerAttribute )
336
+ {
337
+ if ( ! Program . cfgjson . BotOwners . Contains ( ctx . User . Id ) )
338
+ {
339
+ failedChecks . Add ( isBotOwnerAttribute ) ;
340
+ }
341
+ }
342
+
343
+ if ( check is UserRolesPresentAttribute userRolesPresentAttribute )
344
+ {
345
+ if ( Program . cfgjson . UserRoles is null )
346
+ {
347
+ failedChecks . Add ( userRolesPresentAttribute ) ;
348
+ }
349
+ }
350
+ }
351
+
352
+ return failedChecks ;
353
+ }
354
+ }
355
+ }
0 commit comments