Uses 10819K of libraries. Click here for Pure Java version (15926L/85K).
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 | init { |
10 | if (myName == null) setField(myName := "HyperCubes"); |
11 | preprocessAtSelfToMyName = true; |
12 | escapeAtEveryone = true; |
13 | } |
14 | |
15 | sync S processSimplifiedLine(S s, O... _) null { |
16 | try answer super.processSimplifiedLine(s, _); |
17 | optPar long channelID; |
18 | if (channelID == 0) null; |
19 | optPar Message msg; |
20 | |
21 | Message.Attachment attachment = msg == null ? null : first(msg.getAttachments()); |
22 | if (attachment != null) { |
23 | File file = downloadFileForChannel(channelID); |
24 | if (checkPerms(msg) != null) |
25 | file = appendToFileName(file, ".temp"); // don't save for further operations |
26 | print("Downloading attachment " + attachment.getFileName() + " to " + file); |
27 | deleteFile(file); |
28 | attachment.downloadToFile(file) |
29 | .exceptionally(error -> { temp enter(); print("download fail"); ret null with postInChannel(channelID, "Couldn't download attachment"); }) |
30 | .thenAccept(file2 -> postInChannel(channelID, handleAttachment(file2))); |
31 | } |
32 | |
33 | if null (s = dropMyPrefixOrNull(s)) null; |
34 | |
35 | try { |
36 | cmd "help" ret trimAllLines([[ |
37 | Upload a JSON file to channel to inspect/import it |
38 | |
39 | @myName export - export roles + members as JSON |
40 | @myName rights - check bot's rights |
41 | @myName diff - diff last upload against server |
42 | MILDLY DANGEROUS @myName add roles - add missing roles from last upload |
43 | @myName unused roles - list roles not used in current server |
44 | DANGEROUS @myName delete unused roles - delete unused roles |
45 | @myName missing member roles - list roles that members should have according to last import |
46 | SOMEWHAT DANGEROUS @myName add member roles - add all roles mentioned in last line |
47 | |
48 | ]].replace("@myName", myName); |
49 | |
50 | cmd "export" |
51 | ret uploadFileInChannel(channelID, |
52 | toUtf8(jsonEncode_breakAtNLevels(3, makeData())), |
53 | "members-and-roles-" + guild.getIdLong() + ".json.txt", null, null); |
54 | |
55 | cmd "rights" |
56 | ret "Manage roles: " + yesNo(canManageRoles()); |
57 | |
58 | cmd "diff" { |
59 | File f = downloadFileForChannel(channelID); |
60 | if (!fileExists(f)) ret "No past upload found in this channel"; |
61 | ret pairA(diff(f)); |
62 | } |
63 | |
64 | cmd "add roles" { |
65 | try answer checkPerms(msg); |
66 | Either<S, O> p = diffWithLastUpload(channelID); |
67 | if (!isB(p)) ret eitherGetA(p); |
68 | Map diff = cast p.b(); |
69 | Map rolesDiff = cast mapGet(diff, "roles"); |
70 | print(+rolesDiff); |
71 | new LS buf; |
72 | for (JsonDiff.GatheredKey roleName, O roleDiff : unnull(rolesDiff)) { |
73 | //buf.add(roleName + ": " + className(roleDiff)); |
74 | if (roleDiff cast JsonDiff.Added) { |
75 | //buf.add("Add role " + quote(roleName) + ": " + roleDiff); |
76 | //buf.add(className(roleDiff.b)); |
77 | Map roleMap = cast roleDiff.b; |
78 | print(+roleMap); |
79 | S name = getString name(roleMap); |
80 | long permissions = getLong permissions(roleMap); |
81 | queue(guild.createRole() |
82 | .setName(name) |
83 | .setPermissions(permissions)); |
84 | buf.add("Added role " + quote(roleName)); |
85 | } |
86 | } |
87 | ret or2(lines(buf), "Nothing to do"); |
88 | } |
89 | |
90 | cmd "unused roles" |
91 | ret or2(lines(unusedRoles()), "No unused roles"); |
92 | |
93 | cmd "delete unused roles" { |
94 | try answer checkPerms(msg); |
95 | ret or2(mapToLines(unusedRoles(), |
96 | role -> { queue(role.delete()); ret "Deleted role: " + role.getName(); }), |
97 | "No unused roles"); |
98 | } |
99 | |
100 | cmd "missing member roles" |
101 | ret missingMemberRoles(false, _); |
102 | |
103 | cmd "add member roles" |
104 | ret missingMemberRoles(true, _); |
105 | |
106 | } catch print e { |
107 | ret "Internal error: " + e; |
108 | } |
109 | } |
110 | |
111 | S missingMemberRoles(bool doIt, O... _) { |
112 | optPar long channelID; |
113 | |
114 | Either<S, O> p = diffWithLastUpload(channelID); |
115 | if (!isB(p)) ret eitherGetA(p); |
116 | Map diff = cast p.b(); |
117 | Map membersDiff = cast mapGet(diff, "members"); |
118 | new LS buf; |
119 | for (JsonDiff.GatheredKey memberID, O memberDiff : unnull(membersDiff)) { |
120 | long _memberID = (long) memberID!; |
121 | Member member = guild.getMemberById(_memberID); |
122 | if (member == null) continue; |
123 | //buf.add(roleName + ": " + className(roleDiff)); |
124 | if (memberDiff cast Map) { |
125 | Cl roleDiffs = values((Map) get roles(memberDiff)); |
126 | for (O roleDiff : roleDiffs) |
127 | if (roleDiff cast JsonDiff.Added) { |
128 | S roleName = getString name(roleDiff.b); |
129 | buf.add(discordAt(_memberID) + " needs role " + roleName); |
130 | if (doIt) { |
131 | L<Role> roles = guild.getRolesByName(roleName, false); |
132 | if (empty(roles)) |
133 | buf.add("Role " + quote(roleName) + " not found"); |
134 | for (Role role : roles) { |
135 | queue(guild.addRoleToMember(member, role)); |
136 | buf.add(" Added role " + role); |
137 | } |
138 | } |
139 | } |
140 | } |
141 | } |
142 | ret or2(lines(buf), "Nothing to do"); |
143 | } |
144 | |
145 | L<Role> unusedRoles() { |
146 | ret filter(guild.getRoles(), r -> empty(guild.getMembersWithRoles(r))); |
147 | } |
148 | |
149 | S checkPerms(Message msg) { |
150 | if (!msg.getMember().hasPermission(Permission.MANAGE_ROLES)) |
151 | ret "You have no permission to do that you sneaky boy"; |
152 | null; |
153 | } |
154 | |
155 | File downloadFileForChannel(long channelID) { |
156 | ret prepareCacheProgramFile(guildID + "/" + channelID); |
157 | } |
158 | |
159 | bool canManageRoles() { |
160 | ret guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES); |
161 | } |
162 | |
163 | Map makeData() { |
164 | L roles = map(guild.getRoles(), |
165 | r -> litorderedmap( |
166 | "name" := r.getName(), |
167 | "id" := r.getIdLong(), |
168 | "permissions" := r.getPermissionsRaw())); |
169 | |
170 | L members = map(guild.getMembers(), |
171 | m -> litorderedmap( |
172 | "nickName" := m.getNickname(), |
173 | "effectiveName" := m.getEffectiveName(), |
174 | "userName" := m.getUser().getName(), |
175 | "id" := m.getIdLong(), |
176 | "isOwner" := trueOrNull(m.isOwner()), |
177 | "roles" := map(m.getRoles(), |
178 | r -> litorderedmap("name" := r.getName(), "id" := r.getIdLong())))); |
179 | |
180 | ret litorderedmap( |
181 | dataType := "members and roles for guild", |
182 | dateExported := dateWithSecondsGMT(), |
183 | guild := litorderedmap( |
184 | name := guild.getName(), |
185 | id := guild.getIdLong()), |
186 | +roles, |
187 | +members); |
188 | } |
189 | |
190 | S handleAttachment(File file) enter { |
191 | try { |
192 | print("Handling attachment " + f2s(file)); |
193 | ret pairA(diff(file)); |
194 | } catch print e { |
195 | ret "Internal error"; |
196 | } |
197 | } |
198 | |
199 | // returns null if error |
200 | // else: result for user, diff |
201 | Pair<S, O> diff(File file) { |
202 | O json; |
203 | try { |
204 | json = decodeJson(loadTextFile(file)); |
205 | } catch e { |
206 | ret null with print("Not a JSON file: " + f2s(file)); |
207 | } |
208 | |
209 | fixImportedData(json); |
210 | |
211 | new JsonDiff differ; |
212 | // identify roles by names and members by ID |
213 | differ.getListKey = path -> eq(last(path), 'roles) ? 'name : 'id; |
214 | O diff = differ.diff(makeData(), json); |
215 | |
216 | //ret "Differences: " + structForUser(diff); |
217 | ret pair("Differences:\n" + nicelyFormatJsonDiff(diff), diff); |
218 | } |
219 | |
220 | void fixImportedData(O data) { |
221 | changeKey((Map) data, 'allRoles, 'roles); |
222 | } |
223 | |
224 | // either error or diff |
225 | Either<S, O> diffWithLastUpload(long channelID) { |
226 | File f = downloadFileForChannel(channelID); |
227 | if (!fileExists(f)) ret eitherA("No past upload found in this channel"); |
228 | Pair<S, O> p = diff(f); |
229 | if (p == null) ret eitherA("There was an error with the last upload"); |
230 | ret eitherB(p.b); |
231 | } |
232 | } |
Began life as a copy of #1026297
download show line numbers debug dex old transpilations
Travelled to 6 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1026316 |
Snippet name: | Discord Cloner v1 [saves and restores roles and members] |
Eternal ID of this version: | #1026316/4 |
Text MD5: | 15cdf74fd3e9ae9918a76a8d9f59f556 |
Transpilation MD5: | 1a3cda2d3f338e3a8d3aea1c4933f7b5 |
Author: | stefan |
Category: | javax / discord |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-12-15 22:59:57 |
Source code size: | 8042 bytes / 232 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 225 / 318 |
Version history: | 3 change(s) |
Referenced in: | [show references] |