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: | 960 / 92737 | 
| Version history: | 162 change(s) | 
| Referenced in: | [show references] |