From 5104cb7fddc54893c4ff089241d21e3cd5767864 Mon Sep 17 00:00:00 2001 From: xdnw Date: Tue, 1 Aug 2023 01:54:48 +0100 Subject: [PATCH] Add invite anonymization ish --- .../discord/apiv1/enums/DepositType.java | 2 +- .../commands/manager/v2/impl/pw/CM.java | 21 ++ .../manager/v2/impl/pw/CommandManager2.java | 27 +-- .../v2/impl/pw/commands/AdminCommands.java | 14 ++ .../v2/impl/pw/commands/UnsortedCommands.java | 179 ++++++++++++++++++ .../java/link/locutus/discord/db/GuildDB.java | 13 ++ 6 files changed, 243 insertions(+), 13 deletions(-) diff --git a/src/main/java/link/locutus/discord/apiv1/enums/DepositType.java b/src/main/java/link/locutus/discord/apiv1/enums/DepositType.java index f3053aa0..d092753a 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/DepositType.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/DepositType.java @@ -128,7 +128,7 @@ public String toString() { if (city != 0) { note += " #city=" + city; } - if (ignore) { + if (ignore && type != IGNORE) { note += " #ignore"; } return note.trim(); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CM.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CM.java index 45a62ba7..972418eb 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CM.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CM.java @@ -546,6 +546,27 @@ public create create(String sendTo, String subject, String announcement, String return createArgs("sendTo", sendTo, "subject", subject, "announcement", announcement, "replacements", replacements, "channel", channel, "bottomText", bottomText, "requiredVariation", requiredVariation, "requiredDepth", requiredDepth, "seed", seed, "sendMail", sendMail, "sendDM", sendDM, "force", force); } } + @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.AdminCommands.class,method="find_announcement") + public static class find extends CommandRef { + public static final find cmd = new find(); + public find create(String announcementId, String message) { + return createArgs("announcementId", announcementId, "message", message); + } + } + @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.UnsortedCommands.class,method="sendInvite") + public static class invite extends CommandRef { + public static final invite cmd = new invite(); + public invite create(String message, String inviteTo, String sendTo, String expire, String maxUsesEach, String sendDM, String sendMail, String force) { + return createArgs("message", message, "inviteTo", inviteTo, "sendTo", sendTo, "expire", expire, "maxUsesEach", maxUsesEach, "sendDM", sendDM, "sendMail", sendMail, "force", force); + } + } + @AutoRegister(clazz=link.locutus.discord.web.test.TestCommands.class,method="ocr") + public static class ocr extends CommandRef { + public static final ocr cmd = new ocr(); + public ocr create(String discordImageUrl, String type) { + return createArgs("discordImageUrl", discordImageUrl, "type", type); + } + } @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.PlayerSettingCommands.class,method="readAnnouncement") public static class read extends CommandRef { public static final read cmd = new read(); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java index f2d2f933..d043df91 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java @@ -199,6 +199,21 @@ public CommandManager2 registerDefaults() { this.commands.registerMethod(new SettingCommands(), List.of("settings"), "sheets", "sheets"); this.commands.registerMethod(new SettingCommands(), List.of("settings"), "info", "info"); + this.commands.registerMethod(new AdminCommands(), List.of("admin"), "loginTimes", "list_login_times"); + + this.commands.registerMethod(new StatCommands(), List.of("stats_war"), "warAttacksByDay", "warattacksbyday"); + this.commands.registerMethod(new FunCommands(), List.of("fun"), "stealBorgsCity", "stealborgscity"); + + this.commands.registerMethod(new PlayerSettingCommands(), List.of("alerts", "audit"), "auditAlertOptOut", "optout"); + this.commands.registerMethod(new PlayerSettingCommands(), List.of("alerts", "enemy"), "enemyAlertOptOut", "optout"); + this.commands.registerMethod(new PlayerSettingCommands(), List.of("announcement"), "viewAnnouncement", "view"); + + this.commands.registerMethod(new TestCommands(), List.of("announcement"), "ocr", "ocr"); + this.commands.registerMethod(new AdminCommands(), List.of("announcement"), "find_announcement", "find"); + this.commands.registerMethod(new AdminCommands(), List.of("announcement"), "find_invite", "find_invite"); + this.commands.registerMethod(new UnsortedCommands(), List.of("announcement"), "sendInvite", "invite"); + + for (GuildSetting setting : GuildKey.values()) { List path = List.of("settings_" + setting.getCategory().name().toLowerCase(Locale.ROOT)); @@ -272,18 +287,6 @@ public CommandManager2 registerDefaults() { // this.commands.registerMethod(new TradeCommands(), List.of("trade", "offer"), "sellList", "sell_list"); // this.commands.registerMethod(new TradeCommands(), List.of("trade", "offer"), "myOffers", "my_offers"); - this.commands.registerMethod(new AdminCommands(), List.of("admin"), "loginTimes", "list_login_times"); - - this.commands.registerMethod(new StatCommands(), List.of("stats_war"), "warAttacksByDay", "warattacksbyday"); - this.commands.registerMethod(new FunCommands(), List.of("fun"), "stealBorgsCity", "stealborgscity"); - - this.commands.registerMethod(new PlayerSettingCommands(), List.of("alerts", "audit"), "auditAlertOptOut", "optout"); - this.commands.registerMethod(new PlayerSettingCommands(), List.of("alerts", "enemy"), "enemyAlertOptOut", "optout"); - this.commands.registerMethod(new PlayerSettingCommands(), List.of("announcement"), "viewAnnouncement", "view"); - - this.commands.registerMethod(new TestCommands(), List.of("announcement"), "ocr", "ocr"); - this.commands.registerMethod(new TestCommands(), List.of("announcement"), "find_announcement", "find"); - StringBuilder output = new StringBuilder(); this.commands.generatePojo("", output, 0); System.out.println(output); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java index b3ea4d82..6bacef97 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/AdminCommands.java @@ -67,6 +67,7 @@ import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import org.json.JSONObject; +import rocker.guild.ia.message; import java.io.File; import java.io.IOException; @@ -315,6 +316,19 @@ public String archiveAnnouncement(@Me GuildDB db, int announcementId, @Default B return (archive ? "Archived" : "Unarchived") + " announcement with id: #" + announcementId; } + @Command(desc = "Find the announcement closest matching a message") + @RolePermission(Roles.ADMIN) + @NoFormat + public String find_invite(@Me GuildDB db, String invite) throws IOException { + List matches = db.getPlayerAnnouncementsContaining(invite); + if (matches.isEmpty()) { + return "No announcements found with content: `" + invite + "`"; + } else { + return "Found " + matches.size() + " matches:\n- " + + matches.stream().map(f -> "{ID:" + f.ann_id + ", receiver:" + f.receiverNation + "}").collect(Collectors.joining("\n- ")); + } + } + @Command(desc = "Find the announcement closest matching a message") @RolePermission(Roles.ADMIN) @NoFormat diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java index 08834f8e..19e0b126 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java @@ -19,6 +19,8 @@ import link.locutus.discord.commands.manager.v2.command.CommandBehavior; import link.locutus.discord.commands.manager.v2.command.IMessageBuilder; import link.locutus.discord.commands.manager.v2.command.IMessageIO; +import link.locutus.discord.commands.manager.v2.impl.discord.DiscordChannelIO; +import link.locutus.discord.commands.manager.v2.impl.discord.DiscordHookIO; import link.locutus.discord.commands.manager.v2.impl.discord.permission.HasApi; import link.locutus.discord.commands.manager.v2.impl.discord.permission.HasOffshore; import link.locutus.discord.commands.manager.v2.impl.discord.permission.IsAlliance; @@ -70,7 +72,12 @@ import link.locutus.discord.apiv1.enums.city.project.Project; import link.locutus.discord.util.sheet.templates.TransferSheet; import link.locutus.discord.util.task.ia.IACheckup; +import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion; +import net.dv8tion.jda.api.requests.restaction.InviteAction; import org.json.JSONObject; import java.io.IOException; @@ -1513,4 +1520,176 @@ public String optimalBuild(@Me JSONObject command, @Me IMessageIO io, @Me Guild return new OptimalBuild().onCommand(io, guild, author, me, cmd, flags); } + + @Command(desc = "Get the invite for the linked milcom server") + @RolePermission(Roles.ADMIN) + public String sendInvite(@Me GuildDB db, + @Me User author, + @Me DBNation me, + @Me JSONObject command, + @Me IMessageIO currentChannel, + String message, + Guild inviteTo, + @Default NationList sendTo, + @Switch("e") @Timediff Long expire, + @Switch("u") Integer maxUsesEach, + @Arg("Send the invite via discord direct message") @Switch("d") boolean sendDM, + @Switch("m") boolean sendMail, + @Switch("f") boolean force) throws IOException { + DefaultGuildChannelUnion defaultChannel = inviteTo.getDefaultChannel(); + if (defaultChannel == null) { + throw new IllegalArgumentException("No default channel found for " + inviteTo + ". Please set one in the server settings."); + } + if (sendTo == null) { + SimpleNationList tmp = new SimpleNationList(Collections.singleton(me)); + tmp.setFilter(me.getQualifiedName()); + sendTo = tmp; + } + if (sendDM && !Roles.MAIL.hasOnRoot(author)) { + throw new IllegalArgumentException("You do not have permission to send direct mesages"); + } + // ensure admin on inviteTo guild + Member member = inviteTo.getMember(author); + if (member == null) { + throw new IllegalArgumentException("You are not a member of " + inviteTo); + } + if (!member.hasPermission(Permission.CREATE_INSTANT_INVITE) && !Roles.ADMIN.has(author, inviteTo) && !Roles.INTERNAL_AFFAIRS.has(author, inviteTo)) { + throw new IllegalArgumentException("You do not have permission to create invites in " + inviteTo); + } + + // dm user instructions find_announcement + ApiKeyPool keys = (sendMail || sendDM) ? db.getMailKey() : null; + if ((sendMail || sendDM) && keys == null) throw new IllegalArgumentException("No API_KEY set, please use " + GuildKey.API_KEY.getCommandMention() + ""); + Set aaIds = db.getAllianceIds(); + + List errors = new ArrayList<>(); + Collection nations = sendTo.getNations(); + for (DBNation nation : nations) { + User user = nation.getUser(); + if (user.getMutualGuilds().contains(inviteTo)) { + errors.add("Already in the guild: `" + nation.getNation() + "`"); + continue; + } + if (user == null) { + errors.add("Cannot find user for `" + nation.getNation() + "`"); + } else if (db.getGuild().getMember(user) == null) { + errors.add("Cannot find member in guild for `" + nation.getNation() + "` | `" + user.getName() + "`"); + } else { + continue; + } + if (!aaIds.isEmpty() && !aaIds.contains(nation.getAlliance_id())) { + throw new IllegalArgumentException("Cannot send to nation not in alliance: " + nation.getNation() + " | " + user); + } + if (!force) { + if (nation.getActive_m() > 20000) + return "The " + nations.size() + " receivers includes inactive for >2 weeks. Use `" + sendTo.getFilter() + ",#active_m<20000` or set `force` to confirm"; + if (nation.getVm_turns() > 0) + return "The " + nations.size() + " receivers includes vacation mode nations. Use `" + sendTo.getFilter() + ",#vm_turns=0` or set `force` to confirm"; + if (nation.getPosition() < 1) { + return "The " + nations.size() + " receivers includes applicants. Use `" + sendTo.getFilter() + ",#position>1` or set `force` to confirm"; + } + } + } + + if (!force) { + StringBuilder confirmBody = new StringBuilder(); + if (!sendDM && !sendMail) confirmBody.append("**Warning: No ingame or direct message option has been specified**\n"); + confirmBody.append("Send DM (`-d`): " + sendDM).append("\n"); + confirmBody.append("Send Ingame (`-m`): " + sendMail).append("\n"); + if (!errors.isEmpty()) { + confirmBody.append("\n**Errors**:\n- " + StringMan.join(errors, "\n- ")).append("\n"); + } + DiscordUtil.pending(currentChannel, command, "Send to " + nations.size() + " nations", confirmBody + "\nPress to confirm"); + return null; + } + + currentChannel.send("Please wait..."); + + List failedToDM = new ArrayList<>(); + List failedToMail = new ArrayList<>(); + + StringBuilder output = new StringBuilder(); + + Map sentMessages = new HashMap<>(); + + String subject = "Invite to: " + inviteTo.getName(); + + for (DBNation nation : nations) { + InviteAction create = defaultChannel.createInvite().setUnique(true); + if (expire != null) { + create = create.setMaxAge((int) (expire / 1000L)); + } + if (maxUsesEach != null) { + create = create.setMaxUses(maxUsesEach); + } + Invite invite = RateLimitUtil.complete(create); + + String replaced = message + "\n" + invite.getUrl(); + String personal = replaced + "\n\n- " + author.getAsMention(); + + boolean result = sendDM && nation.sendDM(personal); + if (!result && sendDM) { + failedToDM.add(nation.getNation_id()); + } + if ((!result && sendDM) || sendMail) { + try { + nation.sendMail(keys, subject, personal); + } catch (IllegalArgumentException | IOException e) { + failedToMail.add(nation.getNation_id()); + } + } + + sentMessages.put(nation, replaced); + + output.append("\n\n```" + replaced + "```" + "^ " + nation.getNation()); + } + + output.append("\n\n------\n"); + if (errors.size() > 0) { + output.append("Errors:\n- " + StringMan.join(errors, "\n- ")); + } + if (failedToDM.size() > 0) { + output.append("\nFailed DM (sent ingame): " + StringMan.getString(failedToDM)); + } + if (failedToMail.size() > 0) { + output.append("\nFailed Mail: " + StringMan.getString(failedToMail)); + } + + int annId = db.addAnnouncement(author, subject, message, "", sendTo.getFilter()); + for (Map.Entry entry : sentMessages.entrySet()) { + byte[] diff = StringMan.getDiffBytes(message, entry.getValue()); + db.addPlayerAnnouncement(entry.getKey(), annId, diff); + } + + MessageChannel channel; + if (currentChannel instanceof DiscordHookIO hook) { + channel = hook.getHook().getInteraction().getMessageChannel(); + } else if (currentChannel instanceof DiscordChannelIO channelIO) { + channel = channelIO.getChannel(); + } else { + channel = null; + } + if (channel != null) { + IMessageBuilder msg = new DiscordChannelIO(channel).create(); + StringBuilder body = new StringBuilder(); + body.append("From: " + author.getAsMention() + "\n"); + body.append("To: `" + sendTo.getFilter() + "`\n"); + + if (sendMail) { + body.append("- A copy of this announcement has been sent ingame\n"); + } + if (sendDM) { + body.append("- A copy of this announcement has been sent as a direct message\n"); + } + + body.append("\n\nPress `view` to view the announcement"); + + msg = msg.embed("[#" + annId + "] " + subject, body.toString()); + + CM.announcement.view cmd = CM.announcement.view.cmd.create(annId + ""); + msg.commandButton(CommandBehavior.EPHEMERAL, cmd, "view").send(); + } + + return "Done. See " + CM.announcement.find.cmd.toSlashMention(); + } } \ No newline at end of file diff --git a/src/main/java/link/locutus/discord/db/GuildDB.java b/src/main/java/link/locutus/discord/db/GuildDB.java index 6fc78fae..b509c910 100644 --- a/src/main/java/link/locutus/discord/db/GuildDB.java +++ b/src/main/java/link/locutus/discord/db/GuildDB.java @@ -1297,6 +1297,19 @@ public Map> getAllPlayerAnno }); return result; } + + public List getPlayerAnnouncementsContaining(String invite) { + List result = new ArrayList<>(); + query("select * FROM ANNOUNCEMENTS_PLAYER2 WHERE diff LIKE ? ORDER BY ann_id desc", + (ThrowingConsumer) stmt -> stmt.setString(1, "%" + invite + "%"), + (ThrowingConsumer) rs -> { + while (rs.next()) { + result.add(new Announcement.PlayerAnnouncement(this, null, rs)); + } + }); + return result; + } + public List getPlayerAnnouncementsByAnnId(int ann_id) { Announcement announcement = getAnnouncement(ann_id); if (announcement == null) return null;