!7 !include once #1026298 // DynTalkBot2 for JDA 4.0 set flag AllowMetaCode. meta-transformNow { tok_cmdStatement } standardBot1 RolesExporter { init { if (myName == null) setField(myName := "HyperCubes"); preprocessAtSelfToMyName = true; escapeAtEveryone = true; } sync S processSimplifiedLine(S s, O... _) null { try answer super.processSimplifiedLine(s, _); optPar long channelID; if (channelID == 0) null; optPar Message msg; Message.Attachment attachment = msg == null ? null : first(msg.getAttachments()); if (attachment != null) { File file = downloadFileForChannel(channelID); if (checkPerms(msg) != null) file = appendToFileName(file, ".temp"); // don't save for further operations print("Downloading attachment " + attachment.getFileName() + " to " + file); deleteFile(file); attachment.downloadToFile(file) .exceptionally(error -> { temp enter(); print("download fail"); ret null with postInChannel(channelID, "Couldn't download attachment"); }) .thenAccept(file2 -> postInChannel(channelID, handleAttachment(file2))); } if null (s = dropMyPrefixOrNull(s)) null; optPar MessageChannel channel; try { cmd "help" { new EmbedBuilder eb; eb.setColor(toColor("F14527")); // "? ? ? ? ? ? ? ? ? ?" eb.setAuthor(hexToUTF8("e2848d20e284bd20e2849920e284b020e2849b20e2848220e182ae20e284ac20e284b020d58f"), null, null); eb.setTitle("Backup Your Discord Roles", null); eb.setDescription([[ In next update: Manage roles, full layout + roles backup (This bot is still under development, many features will be added later on.) If you like to support this project, join us at and consider donating. We thank you in advance If you are interested to make your own smart bot, visit ]]); S syntax = [[ Export roles + members as JSON ` HyperCubes export [e]` Check bot's rights ` HyperCubes rights [r]` Diff last upload against server ` HyperCubes compare [c]` Add missing roles from last upload ` HyperCubes add roles [ar]` List roles not used in current server ` HyperCubes unused roles [ur]` Delete unused roles ` HyperCubes delete unused roles [dur]` List roles that members should have according to last import ` HyperCubes list missing roles [lmr]` Add all roles mentioned in last line ` HyperCubes add member roles [amr]` ]]; discordEmbed_addField(eb, "Syntax (abbreviations in brackets)", syntax); eb.setThumbnail(imageSnippetURL_noHttps(#1102865)); channel.sendMessage(eb.build()).queue(); null; } cmd "old help" ret trimAllLines([[ Upload a JSON file to channel to inspect/import it @myName export - export roles + members as JSON @myName rights - check bot's rights @myName diff - diff last upload against server MILDLY DANGEROUS @myName add roles - add missing roles from last upload @myName unused roles - list roles not used in current server DANGEROUS @myName delete unused roles - delete unused roles @myName missing member roles - list roles that members should have according to last import SOMEWHAT DANGEROUS @myName add member roles - add all roles mentioned in last line ]].replace("@myName", myName); cmd "export|e" ret uploadFileInChannel(channelID, toUtf8(jsonEncode_breakAtNLevels(3, makeData())), "members-and-roles-" + guild.getIdLong() + ".json.txt", null, null); cmd "rights|r" ret canManageRoles() ? "I have the right to manage roles. I have everything I want!" : "I am not authorized to manage roles, so I can only preview changes, not perform them."; cmd "compare|c" { File f = downloadFileForChannel(channelID); if (!fileExists(f)) ret "No past upload found in this channel"; ret pairA(diff(f)); } cmd "add roles|ar" { try answer checkPerms(msg); Either p = diffWithLastUpload(channelID); if (!isB(p)) ret eitherGetA(p); Map diff = cast p.b(); Map rolesDiff = cast mapGet(diff, "roles"); print(+rolesDiff); new LS buf; for (JsonDiff.GatheredKey roleName, O roleDiff : unnull(rolesDiff)) { //buf.add(roleName + ": " + className(roleDiff)); if (roleDiff cast JsonDiff.Added) { //buf.add("Add role " + quote(roleName) + ": " + roleDiff); //buf.add(className(roleDiff.b)); Map roleMap = cast roleDiff.b; print(+roleMap); S name = getString name(roleMap); long permissions = getLong permissions(roleMap); queue(guild.createRole() .setName(name) .setPermissions(permissions)); buf.add("Added role " + quote(roleName)); } } ret or2(lines(buf), "Nothing to do"); } cmd "unused roles|ur" ret or2(lines(unusedRoles()), "No unused roles"); cmd "delete unused roles|dur" { try answer checkPerms(msg); ret or2(mapToLines(unusedRoles(), role -> { queue(role.delete()); ret "Deleted role: " + role.getName(); }), "No unused roles"); } cmd "list missing roles|lmr" ret missingMemberRoles(false, _); cmd "add member roles|amr" ret missingMemberRoles(true, _); cmd "stats" ret "Brains: " + l(dataByServer) + "\n" + "Persistent data size: " + (l(dm_moduleStruct()) + l(conceptsFile())); } catch print e { ret "Internal error: " + e; } } S missingMemberRoles(bool doIt, O... _) { optPar long channelID; Either p = diffWithLastUpload(channelID); if (!isB(p)) ret eitherGetA(p); Map diff = cast p.b(); Map membersDiff = cast mapGet(diff, "members"); new LS buf; for (JsonDiff.GatheredKey memberID, O memberDiff : unnull(membersDiff)) { long _memberID = (long) memberID!; Member member = guild.getMemberById(_memberID); if (member == null) continue; //buf.add(roleName + ": " + className(roleDiff)); if (memberDiff cast Map) { Cl roleDiffs = values((Map) get roles(memberDiff)); for (O roleDiff : roleDiffs) if (roleDiff cast JsonDiff.Added) { S roleName = getString name(roleDiff.b); buf.add(discordAt(_memberID) + " needs role " + roleName); if (doIt) { L roles = guild.getRolesByName(roleName, false); if (empty(roles)) buf.add("Role " + quote(roleName) + " not found"); for (Role role : roles) { queue(guild.addRoleToMember(member, role)); buf.add(" Added role " + role); } } } } } ret or2(lines(buf), "Nothing to do"); } L unusedRoles() { ret filter(guild.getRoles(), r -> empty(guild.getMembersWithRoles(r))); } S checkPerms(Message msg) { if (!msg.getMember().hasPermission(Permission.MANAGE_ROLES)) ret "You have no permission to do that you sneaky boy"; null; } File downloadFileForChannel(long channelID) { ret prepareCacheProgramFile(guildID + "/" + channelID); } bool canManageRoles() { ret guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES); } Map makeData() { L roles = map(guild.getRoles(), r -> litorderedmap( "name" := r.getName(), "id" := r.getIdLong(), "permissions" := r.getPermissionsRaw())); L members = map(guild.getMembers(), m -> litorderedmap( "nickName" := m.getNickname(), "effectiveName" := m.getEffectiveName(), "userName" := m.getUser().getName(), "id" := m.getIdLong(), "isOwner" := trueOrNull(m.isOwner()), "roles" := map(m.getRoles(), r -> litorderedmap("name" := r.getName(), "id" := r.getIdLong())))); ret litorderedmap( dataType := "members and roles for guild", dateExported := dateWithSecondsGMT(), guild := litorderedmap( name := guild.getName(), id := guild.getIdLong()), +roles, +members); } S handleAttachment(File file) enter { try { print("Handling attachment " + f2s(file)); ret pairA(diff(file)); } catch print e { ret "Internal error"; } } // returns null if error // else: result for user, diff Pair diff(File file) { O json; try { json = decodeJson(loadTextFile(file)); } catch e { ret null with print("Not a JSON file: " + f2s(file)); } fixImportedData(json); new JsonDiff differ; // identify roles by names and members by ID differ.getListKey = path -> eq(last(path), 'roles) ? 'name : 'id; O diff = differ.diff(makeData(), json); //ret "Differences: " + structForUser(diff); ret pair("Differences:\n" + nicelyFormatJsonDiff(diff), diff); } void fixImportedData(O data) { changeKey((Map) data, 'allRoles, 'roles); } // either error or diff Either diffWithLastUpload(long channelID) { File f = downloadFileForChannel(channelID); if (!fileExists(f)) ret eitherA("No past upload found in this channel"); Pair p = diff(f); if (p == null) ret eitherA("There was an error with the last upload"); ret eitherB(p.b); } }