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

232
LINES

< > BotCompany Repo | #1026316 // Discord Cloner v1 [saves and restores roles and members]

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

Uses 10819K of libraries. Click here for Pure Java version (15926L/85K).

!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;
    
    try {
      cmd "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"
        ret uploadFileInChannel(channelID,
          toUtf8(jsonEncode_breakAtNLevels(3, makeData())),
          "members-and-roles-" + guild.getIdLong() + ".json.txt", null, null);
          
      cmd "rights"
        ret "Manage roles: " + yesNo(canManageRoles());
        
      cmd "diff" {
        File f = downloadFileForChannel(channelID);
        if (!fileExists(f)) ret "No past upload found in this channel";
        ret pairA(diff(f));
      }
      
      cmd "add roles" {
        try answer checkPerms(msg);
        Either<S, O> 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"
        ret or2(lines(unusedRoles()), "No unused roles");
        
      cmd "delete unused roles" {
        try answer checkPerms(msg);
        ret or2(mapToLines(unusedRoles(),
          role -> { queue(role.delete()); ret "Deleted role: " + role.getName(); }),
          "No unused roles");
      }
      
      cmd "missing member roles"
        ret missingMemberRoles(false, _);
        
      cmd "add member roles"
        ret missingMemberRoles(true, _);

    } catch print e {
      ret "Internal error: " + e;
    }
  }
  
  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 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 or2(lines(buf), "Nothing to do");
  }
  
  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) 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<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);
  }
  
  // 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);
  }
}

Author comment

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: 137 / 208
Version history: 3 change(s)
Referenced in: [show references]