1 | set flag JDA40. |
2 | |
3 | import net.dv8tion.jda.api.events.Event; |
4 | import net.dv8tion.jda.api.events.user.update.*; |
5 | |
6 | asclass DynTalkBot2<A extends DynTalkBot2.ByServer> extends DynServerAwareDiscordBot<A> { |
7 | switchable S myName = "Anonymous bot"; |
8 | transient bool useAGIBlueForDropPunctuation = true; |
9 | transient bool preprocessAtSelfToMyName = true; |
10 | transient bool dropPunctuation = true; |
11 | transient bool ngbCommands = true; |
12 | L<Long> authorizedUsers = ll(547706854680297473); // stefan |
13 | |
14 | srecord ISaid(long channelID, long date, S text) {} |
15 | |
16 | void init { |
17 | super.init(); |
18 | |
19 | onPostedInChannel.add((channel, msg) -> { |
20 | if (channel cast GuildChannel) { |
21 | Guild guild = channel.getGuild(); |
22 | if (guild == null) ret; |
23 | ByServer bs = getByServer(guild); |
24 | long channelID = channel.getIdLong(); |
25 | mapPut(bs.lastSaidInChannel, channelID, nu ISaid(+channelID, text := msg)); |
26 | change(); |
27 | } |
28 | }); |
29 | |
30 | // We leave it as null to force subclasses to set this |
31 | // (avoids serializing bad instances) |
32 | // makeByServer = () -> (A) new ByServer; |
33 | |
34 | dm_vmBus_onMessage_q discordGuildJoin(voidfunc(Map map) { |
35 | ret unless map.get('module) == module(); |
36 | print("Got join"); |
37 | getByServer(getLong guildID(map), true) |
38 | .onUserJoin(getLong userID(map), map); |
39 | }); |
40 | |
41 | onDiscordEvent(e -> { |
42 | //print("Generic Discord event: " + e); |
43 | if (e cast UserUpdateOnlineStatusEvent) |
44 | getByServer(e.getGuild()).onOnlineStatusChange(e); |
45 | }); |
46 | } |
47 | |
48 | class ByServer extends DynServerAwareDiscordBot<DynTalkBot2.ByServer>.ByServer { |
49 | transient Lock lock; |
50 | L<Long> guildAuthorizedUsers = synchroList(); |
51 | Map<Long, ISaid> lastSaidInChannel = synchroMap(); |
52 | long preferredChannelInGuild; |
53 | |
54 | DynTalkBot2 m2() { ret DynTalkBot2.this; } // for debugging |
55 | |
56 | // overridable |
57 | void onUserJoin(long userID, O... _) {} |
58 | void onOnlineStatusChange(UserUpdateOnlineStatusEvent e) {} |
59 | |
60 | // to change name by server |
61 | S myName() { ret myName; } |
62 | |
63 | @Override S answer(S input, Map map) { |
64 | if (preferredChannelInGuild == 0) { |
65 | long channelID = longPar channelID(map); |
66 | if (channelID != 0) { |
67 | preferredChannelInGuild = channelID; |
68 | change(); |
69 | } |
70 | } |
71 | |
72 | S answer = processLine(input, map); |
73 | try answer super.answer(input, map); // server-aware stuff |
74 | |
75 | ret answer; |
76 | } |
77 | |
78 | S dropMyPrefixOpt(S s) { |
79 | ret or(dropMyPrefixOrNull(s), s); |
80 | } |
81 | |
82 | S dropMyPrefixOrNull(S s) { |
83 | S sOld = s; |
84 | s = dropPrefixICTrim_orNull(myPrefix(), s); |
85 | if (s == null) |
86 | print("no got prefix: " + quote(myPrefix()) + " / " + quote(sOld)); |
87 | ret s; |
88 | } |
89 | |
90 | S processLine(S s, O... _) { |
91 | s = preprocess(s, _); |
92 | ret processSimplifiedLine(s, _); |
93 | } |
94 | |
95 | S preprocess(S s, O... _) { |
96 | print("Preprocessing: " + quote(s)); |
97 | //pcall { print("mother: " + DynTalkBot2.this); } |
98 | if (preprocessAtSelfToMyName && discordBotID != 0) { |
99 | //S a = dropPunctuation ? dropPunctuation3(atSelf()) : atSelf(); |
100 | LS l = ll(atSelf(), atExclamSelf()); |
101 | print("Replacing " + sfu(l)); |
102 | s = replace_multi(s, l, " " + myName + " "); |
103 | print("Got " + quote(s)); |
104 | } |
105 | if (dropPunctuation) |
106 | s = dropPunctuation3_withAGIBlue(useAGIBlueForDropPunctuation, s); |
107 | s = trim(simpleSpaces_noTok(s)); |
108 | print("simplified >> " + quote(s)); |
109 | ret s; |
110 | } |
111 | |
112 | S myPrefix() { |
113 | ret preprocessAtSelfToMyName ? |
114 | (endsWithLetterOrDigit(myName()) ? myName() + " " : myName()) : |
115 | (dropPunctuation ? replace(atSelf(), "@", "") /* @ is killed by preprocessing */ : atSelf()) + " "; |
116 | } |
117 | |
118 | synchronized Lock myLock() { |
119 | if (lock == null) lock = lock(); |
120 | ret lock; |
121 | } |
122 | |
123 | // extend me |
124 | S processSimplifiedLine(S input, O... _) { |
125 | lock myLock(); |
126 | new Matches m; |
127 | |
128 | input = dropMyPrefixOrNull(input); |
129 | if (input == null) null; |
130 | |
131 | if (ngbCommands) { |
132 | if (eqicOneOf(input, "support channel", "support server", "support")) |
133 | ret "Get support for me here: " + nextGenBotsDiscordInvite(); |
134 | |
135 | if (eqicOneOf(input, "source", "sources", "source code")) |
136 | ret snippetLink(programID()); |
137 | } |
138 | |
139 | if (swic_trim(input, "add master ", m)) { |
140 | try answer checkAuth(_); |
141 | |
142 | setAdd(authorizedUsers, parseFirstLong(m.rest())); |
143 | change(); |
144 | |
145 | ret "Okidoki. Have " + n2(l(authorizedUsers), "master"); |
146 | } |
147 | |
148 | if (eqic(input, "masters")) |
149 | ret linesLL("General: " + renderMasters(), |
150 | "For this guild: " + renderGuildMasters()); |
151 | |
152 | if (eqic(input, "guild masters")) |
153 | ret renderGuildMasters(); |
154 | |
155 | if (swic_trim(input, "delete master ", m)) { |
156 | try answer checkAuth(_); |
157 | |
158 | remove(authorizedUsers, parseFirstLong(m.rest())); |
159 | change(); |
160 | |
161 | ret "Okidoki. Have " + n2(l(authorizedUsers), "master"); |
162 | } |
163 | |
164 | if (swic_trim(input, "add guild master ", m)) { |
165 | try answer checkPerGuildAuth(_); |
166 | |
167 | setAdd(guildAuthorizedUsers, parseFirstLong(m.rest())); |
168 | change(); |
169 | |
170 | ret "Okidoki. " + renderGuildMasters(); |
171 | } |
172 | |
173 | if (swic_trim(input, "delete guild master ", m)) { |
174 | try answer checkPerGuildAuth(_); |
175 | |
176 | remove(guildAuthorizedUsers, parseFirstLong(m.rest())); |
177 | change(); |
178 | |
179 | ret "Okidoki. " + renderGuildMasters(); |
180 | } |
181 | |
182 | if (eqic(input, "guild count")) { |
183 | try answer checkAuth(_); |
184 | ret renderGuildCount(); |
185 | } |
186 | |
187 | if (eqic(input, "guild names")) { |
188 | try answer checkAuth(_); |
189 | ret renderGuildNames(); |
190 | } |
191 | |
192 | null; |
193 | } |
194 | |
195 | S renderGuildMasters() { |
196 | ret empty(guildAuthorizedUsers) ? "Only this guild's owner can control me." |
197 | : "I am controlled by this guild's owner and these people: " + joinWithComma(map discordAtPlusID(guildAuthorizedUsers)); |
198 | } |
199 | |
200 | S renderGuildCount() { |
201 | ret "Total guilds joined: " + guildCount + ". Live guilds: " + liveGuildCount(); |
202 | } |
203 | |
204 | S renderGuildNames() { |
205 | ret lines(map(discord.getGuilds(), g -> g.getName())); |
206 | } |
207 | |
208 | bool authed(O... _) { |
209 | ret contains(authorizedUsers, optPar userID(_)); |
210 | } |
211 | |
212 | bool perGuildAuthed(O... _) { |
213 | long userID = longPar userID(_); |
214 | ret contains(guildAuthorizedUsers, userID) |
215 | || guildID != 0 && userID == getGuild().getOwnerIdLong(); |
216 | } |
217 | |
218 | Guild getGuild() { |
219 | ret guildID == 0 ? null : discord.getGuildById(guildID); |
220 | } |
221 | |
222 | S checkAuth(O... _) { |
223 | long userID = longPar userID(_); |
224 | bool result = authed(_); |
225 | print("Auth-checking user ID: " + userID + " => " + result); |
226 | if (!result) ret "You are not authorized"; |
227 | null; |
228 | } |
229 | |
230 | S checkPerGuildAuth(O... _) { |
231 | if (authed(_)) null; |
232 | long userID = longPar userID(_); |
233 | bool result = perGuildAuthed(_); |
234 | print("Guild-auth-checking user ID: " + userID + " => " + result); |
235 | if (!result) ret "You are not authorized"; |
236 | null; |
237 | } |
238 | |
239 | S guildStructure() { |
240 | ret structure_nullingInstancesOfClass(_getClass(module()), this); |
241 | } |
242 | |
243 | // e.g. for help texts |
244 | S atSelfOrMyName() { |
245 | ret preprocessAtSelfToMyName ? myName() : atSelf(); |
246 | } |
247 | |
248 | S atSelfOrMyName_space() { |
249 | ret preprocessAtSelfToMyName ? appendSpaceIfEndsWithLetter(myName) : atSelf(); |
250 | } |
251 | |
252 | S guildMastersHelp() { |
253 | ret [[ |
254 | @me **masters** -- show who controls me |
255 | @me **add guild master @user** -- allow @user to control me |
256 | @me **delete guild master @user** -- @user can no longer control me |
257 | ]]; |
258 | } |
259 | |
260 | } // end of class ByServer |
261 | |
262 | S serveGuildBackup(MessageChannel channel, Guild guild, S data) { |
263 | if (guild == null) ret "Do this in a guild"; |
264 | if (containsDiscordToken(data)) ret "DISCORD TOKEN EXPOSED ALARM!! NOTIFY ADMINISTRATOR"; |
265 | S baseName = urlencode(discordBotName) |
266 | + "-" + guild.getIdLong() + "-" + ymd_minus_hms(); |
267 | S zipName = baseName + ".zip"; |
268 | File zip = programFile("backups/" + zipName); |
269 | createZipFileWithSingleTextFile(zip, baseName + ".txt", data); |
270 | channel.sendMessage("Here's my latest brain contents for this guild.").addFile(zip).queue(); |
271 | null; |
272 | } |
273 | |
274 | // use structure() on ByServer |
275 | S serveGuildBackup(MessageChannel channel, Guild guild, ByServer bs) { |
276 | ret serveGuildBackup(channel, guild, bs.guildStructure()); |
277 | } |
278 | |
279 | S renderMasters() { |
280 | ret empty(authorizedUsers) ? "I have no masters." : "My masters are: " + joinWithComma(map discordAtPlusID(authorizedUsers)); |
281 | } |
282 | } |
Began life as a copy of #1034052
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1034054 |
Snippet name: | DynShardedTalkBot2 renamed to DynTalkBot2 for legacy data |
Eternal ID of this version: | #1034054/1 |
Text MD5: | daf3d2df7e1dd5f19c8f1dc359616060 |
Author: | stefan |
Category: | javax / discord |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-01-19 17:07:44 |
Source code size: | 9031 bytes / 282 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 98 / 132 |
Referenced in: | [show references] |