Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

352
LINES

< > BotCompany Repo | #1026297 // Discord Cloner ["Hypercubes Bot", LIVE]

JavaX source code (Dynamic Module) [tags: use-pretranspiled] - run with: Stefan's OS

Uses 11217K of libraries. Click here for Pure Java version (20089L/107K).

!7

!include once #1026298 // DynTalkBot2 for JDA 4.0

set flag AllowMetaCode.
meta-transformNow { tok_cmdStatement }

standardBot1 RolesExporter {
  bool qaEnabled = true; // disable if guild is spammy
  
  allServers {
    transient S discordInvite = "https://discord.gg/RXqf97d";
    transient S status = discordInvite;
  }
  
  init {
    print("All permissions: " + Permission.ALL_PERMISSIONS);
    if (myName == null) setField(myName := "HyperCubes");
    preprocessAtSelfToMyName = true;
    escapeAtEveryone = true;
    dropPunctuation = false;

    onDiscordStarted(r {
      doEveryAndNow(20.0, r { gazelle_discord_playingGame(status) });
    });
  }
  
  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 = appendToFileName(downloadFileForChannel(channelID), "." + now());
      print("Downloading attachment " + attachment.getFileName() + " to " + file);
      bool authed = checkPerms(msg) == null;
      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, channelID, authed)));
    }
    
    optPar MessageChannel channel;
    
    S s1 = s;
    if ((s = dropMyPrefixOrNull(s)) != null) try {
      cmd "help" { 
        new EmbedBuilder eb;
        eb.setColor(toColor("F14527"));
        
        eb.setAuthor(fancyName(), 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.)

Type `HyperCubes howto` for a short tutorial.

If you like to support this project, join us at <https://discord.gg/JyCEwmb> and consider donating. We thank you in advance

If you are interested to make your own smart bot, visit <https://BotCompany.de>
]]);

        discordEmbed_addField(eb, "Upload", "Upload a JSON file to channel to inspect/import it");

        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 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]`

List roles not used in current server
`  HyperCubes unused roles         [ur]`
Delete unused roles
`  HyperCubes delete unused roles [dur]`

Invite bot to other server
`  HyperCubes invite                   `
]];
        
        discordEmbed_addField(eb, "Syntax (abbreviations in brackets)", syntax);
        
        eb.setThumbnail(imageSnippetURL_noHttps(#1102865));
        channel.sendMessage(eb.build()).queue();
        null;
      }

      cmd "howto|how to|how-to|tutorial" {
        new EmbedBuilder eb;
        eb.setColor(toColor("F14527"));
        
        eb.setAuthor(fancyName(), null, null);
        eb.setTitle(" ", null);
        
        eb.setDescription([[
**How to copy roles from an old server to a new one**

In the old server, type `HyperCubes export` which gives you a JSON file.

In a channel in the new server:
*Simply upload the JSON file as an attachment. You will see a list of all differences in members+roles
*Type `HyperCubes lmr` to see missing roles
*Type `HyperCubes amr` to create missing roles and assign them to appropriate members        
      ]]);
        channel.sendMessage(eb.build()).queue();
        null;
      }
      
      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" 
        ret or2(addRoles(_), "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 or2(missingMemberRoles(false, _), "Nothing to do");
        
      cmd "add member roles|amr"
 {
        try answer checkPerms(msg);
        S a = addRoles(_);
        ret or2(a + missingMemberRoles(true, _), "Nothing to do");
      }
        
      cmd "stats"
        ret "Brains: " + l(dataByServer) + "\n"
        + "Persistent data size: " + (l(dm_moduleStruct()) + l(conceptsFile()));

      cmd "invite"
        ret "Invite me to a server: <" + discordBotInviteLink(discordBotID, discord_adminPermission()) + ">\n" +
          "Join my Discord: <" + discordInvite + ">";

    } catch print e {
      ret "Internal error: " + e;
    }
    
    if (qaEnabled)
      try answer webBot_answer(s1, #1026544);
  }
  
  S missingMemberRoles(bool doIt, O... _) {
    optPar long channelID;
    
    Either<S, O> p = diffWithLastUpload(channelID);
    if (!isB(p)) ret eitherGetA(p);
    Map diff = cast p.b();
    Map<JsonDiff.GatheredKey, O> 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<Role> 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 lines(buf);
  }
  
  L<Role> 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, long channelID, bool authed) enter {
    try {
      print("Handling attachment " + f2s(file));

      O json;
      try {
        json = decodeJson(loadTextFile(file));
      } catch e {
        ret null with print("Not a JSON file: " + f2s(file));
      }

      if (!isMyKindOfData(json)) null;

      S a = "";
      if (authed) {
        copyFile(file, downloadFileForChannel(channelID));
        a = "Saving upload\n";
      }
      
      ret a + unnull(pairA(diff(file)));
    } catch print e {
      ret "Internal error";
    }
  }

  // returns null if error
  // else: result for user, diff
  Pair<S, O> 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);
  }

  bool isMyKindOfData(O data) {
    ret isMap(data) && containsOneKeyOf((Map) data, "roles", "allRoles");
  }
  
  // either error or diff
  Either<S, O> diffWithLastUpload(long channelID) {
    File f = downloadFileForChannel(channelID);
    if (!fileExists(f)) ret eitherA("No past upload found in this channel");
    Pair<S, O> p = diff(f);
    if (p == null) ret eitherA("There was an error with the last upload");
    ret eitherB(p.b);
  }

  S addRoles(O... _) {
    optPar Message msg;
    optPar long channelID;
    try answer checkPerms(msg);
    Either<S, O> p = diffWithLastUpload(channelID);
    if (!isB(p)) ret eitherGetA(p);
    Map diff = cast p.b();
    Map<JsonDiff.GatheredKey, O> 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);
        guild.createRole()
          .setName(name)
          .setPermissions(permissions & Permission.ALL_PERMISSIONS) // clear undefined bits
          .complete(); // further actions may depend on this, so block until done
        buf.add("Added role " + quote(name));
      }
    }
    ret lines(buf);
  }

  // HYPERCUBES in fancy font
  S fancyName() {
    ret hexToUTF8("e2848d20e284bd20e2849920e284b020e2849b20e2848220e182ae20e284ac20e284b020d58f");
  }
}

Author comment

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: 711 / 92196
Version history: 162 change(s)
Referenced in: #1026316 - Discord Cloner v1 [saves and restores roles and members]