Uses 11217K of libraries. Click here for Pure Java version (20089L/107K).
1 | !7 |
2 | |
3 | !include once #1026298 // DynTalkBot2 for JDA 4.0 |
4 | |
5 | set flag AllowMetaCode. |
6 | meta-transformNow { tok_cmdStatement } |
7 | |
8 | standardBot1 RolesExporter { |
9 | bool qaEnabled = true; // disable if guild is spammy |
10 | |
11 | allServers { |
12 | transient S discordInvite = "https://discord.gg/RXqf97d"; |
13 | transient S status = discordInvite; |
14 | } |
15 | |
16 | init { |
17 | print("All permissions: " + Permission.ALL_PERMISSIONS); |
18 | if (myName == null) setField(myName := "HyperCubes"); |
19 | preprocessAtSelfToMyName = true; |
20 | escapeAtEveryone = true; |
21 | dropPunctuation = false; |
22 | |
23 | onDiscordStarted(r { |
24 | doEveryAndNow(20.0, r { gazelle_discord_playingGame(status) }); |
25 | }); |
26 | } |
27 | |
28 | sync S processSimplifiedLine(S s, O... _) null { |
29 | try answer super.processSimplifiedLine(s, _); |
30 | optPar long channelID; |
31 | if (channelID == 0) null; |
32 | optPar Message msg; |
33 | |
34 | Message.Attachment attachment = msg == null ? null : first(msg.getAttachments()); |
35 | if (attachment != null) { |
36 | File file = appendToFileName(downloadFileForChannel(channelID), "." + now()); |
37 | print("Downloading attachment " + attachment.getFileName() + " to " + file); |
38 | bool authed = checkPerms(msg) == null; |
39 | deleteFile(file); |
40 | attachment.downloadToFile(file) |
41 | .exceptionally(error -> { temp enter(); print("download fail"); ret null with postInChannel(channelID, "Couldn't download attachment"); }) |
42 | .thenAccept(file2 -> postInChannel(channelID, handleAttachment(file2, channelID, authed))); |
43 | } |
44 | |
45 | optPar MessageChannel channel; |
46 | |
47 | S s1 = s; |
48 | if ((s = dropMyPrefixOrNull(s)) != null) try { |
49 | cmd "help" { |
50 | new EmbedBuilder eb; |
51 | eb.setColor(toColor("F14527")); |
52 | |
53 | eb.setAuthor(fancyName(), null, null); |
54 | |
55 | eb.setTitle("Backup Your Discord Roles", null); |
56 | eb.setDescription([[ |
57 | In next update: Manage roles, full layout + roles backup |
58 | |
59 | (This bot is still under development, many features will be added later on.) |
60 | |
61 | Type `HyperCubes howto` for a short tutorial. |
62 | |
63 | If you like to support this project, join us at <https://discord.gg/JyCEwmb> and consider donating. We thank you in advance |
64 | |
65 | If you are interested to make your own smart bot, visit <https://BotCompany.de> |
66 | ]]); |
67 | |
68 | discordEmbed_addField(eb, "Upload", "Upload a JSON file to channel to inspect/import it"); |
69 | |
70 | S syntax = [[ |
71 | Export roles + members as JSON |
72 | ` HyperCubes export [e]` |
73 | Check bot's rights |
74 | ` HyperCubes rights [r]` |
75 | |
76 | Diff last upload against server |
77 | ` HyperCubes compare [c]` |
78 | Add missing roles from last upload |
79 | ` HyperCubes add roles [ar]` |
80 | List roles that members should have according to last import |
81 | ` HyperCubes list missing roles [lmr]` |
82 | Add all roles mentioned in last line |
83 | ` HyperCubes add member roles [amr]` |
84 | |
85 | List roles not used in current server |
86 | ` HyperCubes unused roles [ur]` |
87 | Delete unused roles |
88 | ` HyperCubes delete unused roles [dur]` |
89 | |
90 | Invite bot to other server |
91 | ` HyperCubes invite ` |
92 | ]]; |
93 | |
94 | discordEmbed_addField(eb, "Syntax (abbreviations in brackets)", syntax); |
95 | |
96 | eb.setThumbnail(imageSnippetURL_noHttps(#1102865)); |
97 | channel.sendMessage(eb.build()).queue(); |
98 | null; |
99 | } |
100 | |
101 | cmd "howto|how to|how-to|tutorial" { |
102 | new EmbedBuilder eb; |
103 | eb.setColor(toColor("F14527")); |
104 | |
105 | eb.setAuthor(fancyName(), null, null); |
106 | eb.setTitle(" ", null); |
107 | |
108 | eb.setDescription([[ |
109 | **How to copy roles from an old server to a new one** |
110 | |
111 | In the old server, type `HyperCubes export` which gives you a JSON file. |
112 | |
113 | In a channel in the new server: |
114 | *Simply upload the JSON file as an attachment. You will see a list of all differences in members+roles |
115 | *Type `HyperCubes lmr` to see missing roles |
116 | *Type `HyperCubes amr` to create missing roles and assign them to appropriate members |
117 | ]]); |
118 | channel.sendMessage(eb.build()).queue(); |
119 | null; |
120 | } |
121 | |
122 | cmd "export|e" |
123 | ret uploadFileInChannel(channelID, |
124 | toUtf8(jsonEncode_breakAtNLevels(3, makeData())), |
125 | "members-and-roles-" + guild.getIdLong() + ".json.txt", null, null); |
126 | |
127 | cmd "rights|r" |
128 | ret canManageRoles() |
129 | ? "I have the right to manage roles. I have everything I want!" |
130 | : "I am not authorized to manage roles, so I can only preview changes, not perform them."; |
131 | |
132 | cmd "compare|c" { |
133 | File f = downloadFileForChannel(channelID); |
134 | if (!fileExists(f)) ret "No past upload found in this channel"; |
135 | ret pairA(diff(f)); |
136 | } |
137 | |
138 | cmd "add roles|ar" |
139 | ret or2(addRoles(_), "Nothing to do"); |
140 | |
141 | cmd "unused roles|ur" |
142 | ret or2(lines(unusedRoles()), "No unused roles"); |
143 | |
144 | cmd "delete unused roles|dur" { |
145 | try answer checkPerms(msg); |
146 | ret or2(mapToLines(unusedRoles(), |
147 | role -> { queue(role.delete()); ret "Deleted role: " + role.getName(); }), |
148 | "No unused roles"); |
149 | } |
150 | |
151 | cmd "list missing roles|lmr" |
152 | ret or2(missingMemberRoles(false, _), "Nothing to do"); |
153 | |
154 | cmd "add member roles|amr" |
155 | { |
156 | try answer checkPerms(msg); |
157 | S a = addRoles(_); |
158 | ret or2(a + missingMemberRoles(true, _), "Nothing to do"); |
159 | } |
160 | |
161 | cmd "stats" |
162 | ret "Brains: " + l(dataByServer) + "\n" |
163 | + "Persistent data size: " + (l(dm_moduleStruct()) + l(conceptsFile())); |
164 | |
165 | cmd "invite" |
166 | ret "Invite me to a server: <" + discordBotInviteLink(discordBotID, discord_adminPermission()) + ">\n" + |
167 | "Join my Discord: <" + discordInvite + ">"; |
168 | |
169 | } catch print e { |
170 | ret "Internal error: " + e; |
171 | } |
172 | |
173 | if (qaEnabled) |
174 | try answer webBot_answer(s1, #1026544); |
175 | } |
176 | |
177 | S missingMemberRoles(bool doIt, O... _) { |
178 | optPar long channelID; |
179 | |
180 | Either<S, O> p = diffWithLastUpload(channelID); |
181 | if (!isB(p)) ret eitherGetA(p); |
182 | Map diff = cast p.b(); |
183 | Map<JsonDiff.GatheredKey, O> membersDiff = cast mapGet(diff, "members"); |
184 | new LS buf; |
185 | for (JsonDiff.GatheredKey memberID, O memberDiff : unnull(membersDiff)) { |
186 | long _memberID = (long) memberID!; |
187 | Member member = guild.getMemberById(_memberID); |
188 | if (member == null) continue; |
189 | //buf.add(roleName + ": " + className(roleDiff)); |
190 | if (memberDiff cast Map) { |
191 | Cl roleDiffs = values((Map) get roles(memberDiff)); |
192 | for (O roleDiff : roleDiffs) |
193 | if (roleDiff cast JsonDiff.Added) { |
194 | S roleName = getString name(roleDiff.b); |
195 | buf.add(discordAt(_memberID) + " needs role " + roleName); |
196 | if (doIt) { |
197 | L<Role> roles = guild.getRolesByName(roleName, false); |
198 | if (empty(roles)) |
199 | buf.add("Role " + quote(roleName) + " not found"); |
200 | for (Role role : roles) { |
201 | queue(guild.addRoleToMember(member, role)); |
202 | buf.add(" Added role " + role); |
203 | } |
204 | } |
205 | } |
206 | } |
207 | } |
208 | ret lines(buf); |
209 | } |
210 | |
211 | L<Role> unusedRoles() { |
212 | ret filter(guild.getRoles(), r -> empty(guild.getMembersWithRoles(r))); |
213 | } |
214 | |
215 | S checkPerms(Message msg) { |
216 | if (!msg.getMember().hasPermission(Permission.MANAGE_ROLES)) |
217 | ret "You have no permission to do that you sneaky boy"; |
218 | null; |
219 | } |
220 | |
221 | File downloadFileForChannel(long channelID) { |
222 | ret prepareCacheProgramFile(guildID + "/" + channelID); |
223 | } |
224 | |
225 | bool canManageRoles() { |
226 | ret guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES); |
227 | } |
228 | |
229 | Map makeData() { |
230 | L roles = map(guild.getRoles(), |
231 | r -> litorderedmap( |
232 | "name" := r.getName(), |
233 | "id" := r.getIdLong(), |
234 | "permissions" := r.getPermissionsRaw())); |
235 | |
236 | L members = map(guild.getMembers(), |
237 | m -> litorderedmap( |
238 | "nickName" := m.getNickname(), |
239 | "effectiveName" := m.getEffectiveName(), |
240 | "userName" := m.getUser().getName(), |
241 | "id" := m.getIdLong(), |
242 | "isOwner" := trueOrNull(m.isOwner()), |
243 | "roles" := map(m.getRoles(), |
244 | r -> litorderedmap("name" := r.getName(), "id" := r.getIdLong())))); |
245 | |
246 | ret litorderedmap( |
247 | dataType := "members and roles for guild", |
248 | dateExported := dateWithSecondsGMT(), |
249 | guild := litorderedmap( |
250 | name := guild.getName(), |
251 | id := guild.getIdLong()), |
252 | +roles, |
253 | +members); |
254 | } |
255 | |
256 | S handleAttachment(File file, long channelID, bool authed) enter { |
257 | try { |
258 | print("Handling attachment " + f2s(file)); |
259 | |
260 | O json; |
261 | try { |
262 | json = decodeJson(loadTextFile(file)); |
263 | } catch e { |
264 | ret null with print("Not a JSON file: " + f2s(file)); |
265 | } |
266 | |
267 | if (!isMyKindOfData(json)) null; |
268 | |
269 | S a = ""; |
270 | if (authed) { |
271 | copyFile(file, downloadFileForChannel(channelID)); |
272 | a = "Saving upload\n"; |
273 | } |
274 | |
275 | ret a + unnull(pairA(diff(file))); |
276 | } catch print e { |
277 | ret "Internal error"; |
278 | } |
279 | } |
280 | |
281 | // returns null if error |
282 | // else: result for user, diff |
283 | Pair<S, O> diff(File file) { |
284 | O json; |
285 | try { |
286 | json = decodeJson(loadTextFile(file)); |
287 | } catch e { |
288 | ret null with print("Not a JSON file: " + f2s(file)); |
289 | } |
290 | |
291 | fixImportedData(json); |
292 | |
293 | new JsonDiff differ; |
294 | // identify roles by names and members by ID |
295 | differ.getListKey = path -> eq(last(path), 'roles) ? 'name : 'id; |
296 | O diff = differ.diff(makeData(), json); |
297 | |
298 | //ret "Differences: " + structForUser(diff); |
299 | ret pair("Differences:\n" + nicelyFormatJsonDiff(diff), diff); |
300 | } |
301 | |
302 | void fixImportedData(O data) { |
303 | changeKey((Map) data, 'allRoles, 'roles); |
304 | } |
305 | |
306 | bool isMyKindOfData(O data) { |
307 | ret isMap(data) && containsOneKeyOf((Map) data, "roles", "allRoles"); |
308 | } |
309 | |
310 | // either error or diff |
311 | Either<S, O> diffWithLastUpload(long channelID) { |
312 | File f = downloadFileForChannel(channelID); |
313 | if (!fileExists(f)) ret eitherA("No past upload found in this channel"); |
314 | Pair<S, O> p = diff(f); |
315 | if (p == null) ret eitherA("There was an error with the last upload"); |
316 | ret eitherB(p.b); |
317 | } |
318 | |
319 | S addRoles(O... _) { |
320 | optPar Message msg; |
321 | optPar long channelID; |
322 | try answer checkPerms(msg); |
323 | Either<S, O> p = diffWithLastUpload(channelID); |
324 | if (!isB(p)) ret eitherGetA(p); |
325 | Map diff = cast p.b(); |
326 | Map<JsonDiff.GatheredKey, O> rolesDiff = cast mapGet(diff, "roles"); |
327 | print(+rolesDiff); |
328 | new LS buf; |
329 | for (JsonDiff.GatheredKey roleName, O roleDiff : unnull(rolesDiff)) { |
330 | //buf.add(roleName + ": " + className(roleDiff)); |
331 | if (roleDiff cast JsonDiff.Added) { |
332 | //buf.add("Add role " + quote(roleName) + ": " + roleDiff); |
333 | //buf.add(className(roleDiff.b)); |
334 | Map roleMap = cast roleDiff.b; |
335 | print(+roleMap); |
336 | S name = getString name(roleMap); |
337 | long permissions = getLong permissions(roleMap); |
338 | guild.createRole() |
339 | .setName(name) |
340 | .setPermissions(permissions & Permission.ALL_PERMISSIONS) // clear undefined bits |
341 | .complete(); // further actions may depend on this, so block until done |
342 | buf.add("Added role " + quote(name)); |
343 | } |
344 | } |
345 | ret lines(buf); |
346 | } |
347 | |
348 | // HYPERCUBES in fancy font |
349 | S fancyName() { |
350 | ret hexToUTF8("e2848d20e284bd20e2849920e284b020e2849b20e2848220e182ae20e284ac20e284b020d58f"); |
351 | } |
352 | } |
Began life as a copy of #1025767
download show line numbers debug dex old transpilations
Travelled to 8 computer(s): bhatertpkbcr, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1026297 |
Snippet name: | Discord Cloner ["Hypercubes Bot", LIVE] |
Eternal ID of this version: | #1026297/163 |
Text MD5: | 519794b2d4f9696c6f790d18f95cfc70 |
Transpilation MD5: | f28532a9e8080681f680a4730558c410 |
Author: | stefan |
Category: | javax / discord |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-10-28 23:04:57 |
Source code size: | 11733 bytes / 352 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 710 / 92196 |
Version history: | 162 change(s) |
Referenced in: | [show references] |