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).

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  
}

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