Example #1
0
        public void Initialize(DalamudPluginInterface pluginInterface)
        {
            generalStopwatch.Start();
            cacheTimer.Start();
#if DEBUG
            drawConfigWindow = true;
#endif
            this.PluginInterface = pluginInterface;

            LoadConfig();

            PluginInterface.Framework.OnUpdateEvent += FrameworkUpdate;

            IconManager = new IconManager(pluginInterface);

            actionManagerStatic = pluginInterface.TargetModuleScanner.GetStaticAddressFromSig("48 89 05 ?? ?? ?? ?? C3 CC C2 00 00 CC CC CC CC CC CC CC CC CC CC CC CC CC 48 8D 0D ?? ?? ?? ?? E9 ?? ?? ?? ??");
            blueSpellBook       = (uint *)(pluginInterface.TargetModuleScanner.GetStaticAddressFromSig("0F B7 0D ?? ?? ?? ?? 84 C0") + 0x2A);
            PluginLog.Verbose($"Blue Spell Book: {(ulong) blueSpellBook:X}");

            ActionManager = new ActionManager(this, actionManagerStatic);

            pluginInterface.UiBuilder.OnOpenConfigUi += OnOpenConfigUi;

            PluginInterface.UiBuilder.OnBuildUi += this.BuildUI;

            pluginInterface.ClientState.TerritoryChanged += TerritoryChanged;
            TerritoryChanged(this, pluginInterface.ClientState.TerritoryType);

            updateRetainerListHook = new Hook <UpdateRetainerListDelegate>(pluginInterface.TargetModuleScanner.ScanText("40 53 48 83 EC 20 48 8B 01 48 8B D9 FF 50 20 84 C0 74 0F 48 8B 03 48 8B CB 48 83 C4 20 5B 48 FF 60 18 E8"), new UpdateRetainerListDelegate(UpdateRetainerListDetour));
            updateRetainerListHook.Enable();

            Client = new ClientInterface(pluginInterface.TargetModuleScanner, pluginInterface.Data);

            SetupCommands();
        }
Example #2
0
        public bool Execute(string message)
        {
            // First try to process the command through Dalamud.
            if (Dalamud.Commands.ProcessCommand(message))
            {
                PluginLog.Verbose("Executed Dalamud command \"{Message:l}\".", message);
                return(true);
            }

            if (_uiModulePtr == IntPtr.Zero)
            {
                PluginLog.Error("Can not execute \"{Message:l}\" because no uiModulePtr is available.", message);
                return(false);
            }

            // Then prepare a string to send to the game itself.
            var(text, length) = PrepareString(message);
            var payload = PrepareContainer(text, length);

            _processChatBox.Invoke(_uiModulePtr, payload, IntPtr.Zero, (byte)0);

            Marshal.FreeHGlobal(payload);
            Marshal.FreeHGlobal(text);
            return(false);
        }
        /// <summary>
        /// Check if the sender of this message is set as the owner of this plugin, and send an error message to the specified channel if not null.
        /// </summary>
        /// <param name="user">User in question.</param>
        /// <param name="errorMessageChannel">Channel for error message.</param>
        /// <returns>True if the user is the owner of this plugin.</returns>
        private async Task <bool> EnsureOwner(IUser user, ISocketMessageChannel errorMessageChannel = null)
        {
            PluginLog.Verbose("EnsureOwner: " + user.Username + "#" + user.Discriminator);
            if (user.Username + "#" + user.Discriminator == this.plugin.Config.DiscordOwnerName)
            {
                return(true);
            }

            if (ulong.TryParse(this.plugin.Config.DiscordOwnerName, out ulong parsed))
            {
                if (user.Id == parsed)
                {
                    return(true);
                }
            }

            if (errorMessageChannel == null)
            {
                return(false);
            }

            await SendGenericEmbed(errorMessageChannel, "You are not allowed to run commands for this bot.\n\nIf this is your bot, please use the \"/pdiscord\" command in-game to enter your username.", "Error", EmbedColorError);

            return(false);
        }
        private async Task SocketClientOnReady()
        {
            this.State = DiscordState.Ready;
            await this.specialChars.TryFindEmote(this.socketClient);

            PluginLog.Verbose("DiscordHandler READY!!");
        }
        /// <summary>
        /// Get the webhook for the respective channel, or create one if it doesn't exist.
        /// </summary>
        /// <param name="channel">The channel to get the webhook for</param>
        /// <returns><see cref="IWebhook"/> for the respective channel.</returns>
        private async Task <DiscordWebhookClient> GetOrCreateWebhookClient(SocketChannel channel)
        {
            if (!(channel is SocketTextChannel textChannel))
            {
                throw new ArgumentNullException(nameof(textChannel));
            }

            if (!this.plugin.Config.ChannelConfigs.TryGetValue(channel.Id, out var channelConfig))
            {
                throw new ArgumentException("No configuration for channel.", nameof(channel));
            }

            IWebhook hook;

            if (channelConfig.WebhookId != 0)
            {
                hook = await textChannel.GetWebhookAsync(channelConfig.WebhookId) ?? await textChannel.CreateWebhookAsync("FFXIV Bridge Worker");
            }
            else
            {
                hook = await textChannel.CreateWebhookAsync("FFXIV Bridge Worker");
            }

            this.plugin.Config.ChannelConfigs[channel.Id].WebhookId = hook.Id;
            this.plugin.Config.Save();

            PluginLog.Verbose("Webhook for {0} OK!! {1}", channel.Id, hook.Id);

            return(new DiscordWebhookClient(hook));
        }
 public void Dispose()
 {
     PluginLog.Verbose("Discord DISPOSE!!");
     this.MessageQueue?.Stop();
     this.socketClient?.LogoutAsync().GetAwaiter().GetResult();
     this.socketClient?.Dispose();
 }
Example #7
0
        public void Initialize(DalamudPluginInterface pluginInterface)
        {
            generalStopwatch.Start();
            cacheTimer.Start();
#if DEBUG
            drawConfigWindow = true;
#endif
            this.PluginInterface = pluginInterface;

            LoadConfig();

            PluginInterface.Framework.OnUpdateEvent += FrameworkUpdate;

            IconManager = new IconManager(pluginInterface);

            actionManagerStatic = pluginInterface.TargetModuleScanner.GetStaticAddressFromSig("48 89 05 ?? ?? ?? ?? C3 CC C2 00 00 CC CC CC CC CC CC CC CC CC CC CC CC CC 48 8D 0D ?? ?? ?? ?? E9 ?? ?? ?? ??");
            blueSpellBook       = (uint *)(pluginInterface.TargetModuleScanner.GetStaticAddressFromSig("0F B7 0D ?? ?? ?? ?? 84 C0") + 0x2A);
            PluginLog.Verbose($"Blue Spell Book: {(ulong) blueSpellBook:X}");

            ActionManager = new ActionManager(this, actionManagerStatic);

            pluginInterface.UiBuilder.OnOpenConfigUi += OnOpenConfigUi;

            PluginInterface.UiBuilder.OnBuildUi += this.BuildUI;

            pluginInterface.ClientState.TerritoryChanged += TerritoryChanged;
            TerritoryChanged(this, pluginInterface.ClientState.TerritoryType);

            SetupCommands();
        }
Example #8
0
    // We need to set the correct collection for the actual material path that is loaded
    // before actually loading the file.
    private bool MtrlLoadHandler(Utf8String split, Utf8String path, ResourceManager *resourceManager,
                                 SeFileDescriptor *fileDescriptor, int priority, bool isSync, out byte ret)
    {
        ret = 0;
        if (fileDescriptor->ResourceHandle->FileType != ResourceType.Mtrl)
        {
            return(false);
        }

        var lastUnderscore = split.LastIndexOf(( byte )'_');
        var name           = lastUnderscore == -1 ? split.ToString() : split.Substring(0, lastUnderscore).ToString();

        if (Penumbra.CollectionManager.ByName(name, out var collection))
        {
#if DEBUG
            PluginLog.Verbose("Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path);
#endif
            SetCollection(path, collection);
        }
        else
        {
#if DEBUG
            PluginLog.Verbose("Using MtrlLoadHandler with no collection for path {$Path:l}.", path);
#endif
        }

        // Force isSync = true for this call. I don't really understand why,
        // or where the difference even comes from.
        // Was called with True on my client and with false on other peoples clients,
        // which caused problems.
        ret = Penumbra.ResourceLoader.DefaultLoadResource(path, resourceManager, fileDescriptor, priority, true);
        PathCollections.TryRemove(path, out _);
        return(true);
    }
        public async Task Start()
        {
            if (string.IsNullOrEmpty(this.plugin.Config.DiscordToken))
            {
                this.State = DiscordState.TokenInvalid;

                PluginLog.Error("Token empty, cannot start bot.");
                return;
            }

            try
            {
                await this.socketClient.LoginAsync(TokenType.Bot, this.plugin.Config.DiscordToken);

                await this.socketClient.StartAsync();
            }
            catch (Exception ex)
            {
                PluginLog.Error(ex, "Token invalid, cannot start bot.");
            }

            this.MessageQueue.Start();

            lodestoneClient = await LodestoneClient.GetClientAsync();

            PluginLog.Verbose("DiscordHandler START!!");
        }
Example #10
0
        public void SendEvent(MidiEvent midiEvent)
        {
            var keyboard = Plugin.pluginInterface.Framework.Gui.GetAddonByName("PerformanceModeWide", 1);

            if (keyboard == null)
            {
                return;
            }

            if (midiEvent is NoteOnEvent noteOnEvent)
            {
                var noteNum       = noteOnEvent.NoteNumber - 48 + Plugin.config.NoteNumberOffset;
                var adaptedOctave = 0;
                if (Plugin.config.AdaptNotesOOR)
                {
                    while (noteNum < 0)
                    {
                        noteNum += 12;
                        adaptedOctave++;
                    }
                    while (noteNum > 36)
                    {
                        noteNum -= 12;
                        adaptedOctave--;
                    }
                }
                PluginLog.Verbose($"{noteOnEvent.GetNoteName().ToString().Replace("Sharp", "#")}{noteOnEvent.GetNoteOctave()} ({noteNum})" +
                                  $"{(noteNum < 0 || noteNum > 36 ? "(out of range)" : string.Empty)}" +
                                  $"{(adaptedOctave != 0 ? $"[adapted {adaptedOctave} Oct]" : string.Empty)}");
                if (noteNum < 0 || noteNum > 36)
                {
                    return;
                }
                playlib.PressKey(keyboard.Address, noteNum);
            }
            else if (midiEvent is NoteOffEvent noteOffEvent)
            {
                var noteNum       = noteOffEvent.NoteNumber - 48 + Plugin.config.NoteNumberOffset;
                var adaptedOctave = 0;
                if (Plugin.config.AdaptNotesOOR)
                {
                    while (noteNum < 0)
                    {
                        noteNum += 12;
                        adaptedOctave++;
                    }
                    while (noteNum > 36)
                    {
                        noteNum -= 12;
                        adaptedOctave--;
                    }
                }
                if (noteNum < 0 || noteNum > 36)
                {
                    return;
                }
                playlib.ReleaseKey(keyboard.Address, noteNum);
            }
        }
 // Only create, do not update.
 private void CreateCache()
 {
     if (_cache == null)
     {
         CalculateEffectiveFileList();
         PluginLog.Verbose("Created new cache for collection {Name:l}.", Name);
     }
 }
Example #12
0
        protected override void Setup64Bit(SigScanner scanner)
        {
            ActiveSpellsAddress       = scanner.GetStaticAddressFromSig(ActionManagerSignature, 316);
            ClientGameUiHotbarAddress = scanner.GetStaticAddressFromSig(ClientGameUiHotbarSignature, 1);
            IsActionUnlockedAddress   = scanner.ScanText(IsActionUnlockedSignature);

            PluginLog.Verbose("===== BLU DEX =====");
            PluginLog.Verbose($"{nameof(ClientGameUiHotbarAddress)} {ClientGameUiHotbarAddress.ToInt64():X}");
            PluginLog.Verbose($"{nameof(IsActionUnlockedAddress)} {IsActionUnlockedAddress.ToInt64():X}");
        }
Example #13
0
        public void UwuCommand(string command, string args)
        {
            config.Enabed = false;
            config.Save();
            // You may want to assign these references to private variables for convenience.
            // Keep in mind that the local player does not exist until after logging in.
            var chat = this._pi.Framework.Gui.Chat;

            chat.Print($"Goodbye Owofied chat. ;_;");
            PluginLog.Verbose("OwO has been disabled.");
        }
Example #14
0
        private Gatherable?FindItemLogging(string itemName)
        {
            var item   = _world.FindItemByName(itemName);
            var output = item == null
                ? $"Could not find corresponding item to \"{itemName}\"."
                : $"Identified [{item.ItemId}: {item.NameList[_language]}] for \"{itemName}\".";

            _chat.Print(output);
            PluginLog.Verbose(output);
            return(item);
        }
Example #15
0
        public World(DalamudPluginInterface pi, GatherBuddyConfiguration config)
        {
            _pi         = pi;
            Language    = pi.ClientState.ClientLanguage;
            Territories = new TerritoryManager();
            Aetherytes  = new AetheryteManager(pi, Territories);
            Items       = new ItemManager(pi);
            Nodes       = new NodeManager(pi, config, this, Aetherytes, Items);
            Fish        = new FishManager(pi, this, Aetherytes);

            PluginLog.Verbose("{Count} regions collected.",     Territories.Regions.Count);
            PluginLog.Verbose("{Count} territories collected.", Territories.Territories);
        }
    /// <inheritdoc/>
    protected override void Setup64Bit(SigScanner scanner)
    {
        this.ComboTimer = scanner.GetStaticAddressFromSig("F3 0F 11 05 ?? ?? ?? ?? F3 0F 10 45 ?? E8");

        this.GetAdjustedActionId = scanner.ScanText("E8 ?? ?? ?? ?? 8B F8 3B DF");  // Client::Game::ActionManager.GetAdjustedActionId

        this.IsActionIdReplaceable = scanner.ScanText("81 F9 ?? ?? ?? ?? 7F 35");

        PluginLog.Verbose("===== X I V C O M B O =====");
        PluginLog.Verbose($"{nameof(this.GetAdjustedActionId)}   0x{this.GetAdjustedActionId:X}");
        PluginLog.Verbose($"{nameof(this.IsActionIdReplaceable)} 0x{this.IsActionIdReplaceable:X}");
        PluginLog.Verbose($"{nameof(this.ComboTimer)}            0x{this.ComboTimer:X}");
        PluginLog.Verbose($"{nameof(this.LastComboMove)}         0x{this.LastComboMove:X}");
    }
Example #17
0
        private void Ring(Alarm alarm, int currentMinute)
        {
            if (alarm.SoundId > Sounds.Unknown)
            {
                _sounds.Play(alarm.SoundId);
            }

            if (alarm.PrintMessage && _config.AlarmFormat.Length > 0)
            {
                _pi.Framework.Gui.Chat.PrintError(ReplaceFormatPlaceholders(_config.AlarmFormat, alarm, currentMinute));
                PluginLog.Verbose(ReplaceFormatPlaceholders(GatherBuddyConfiguration.DefaultAlarmFormat, alarm, currentMinute));
            }

            LastAlarm = alarm;
        }
Example #18
0
        public void TryCreateTeleporterWatcher(DalamudPluginInterface pi, bool useTeleport)
        {
            const string teleporterPluginConfigFile = "TeleporterPlugin.json";

            _teleporterLanguage = _language;
            if (!useTeleport || _teleporterWatcher != null)
            {
                _teleporterWatcher?.Dispose();
                _teleporterWatcher = null;
                return;
            }

            var dir = new DirectoryInfo(pi.GetPluginConfigDirectory());

            if (!dir.Exists || (dir.Parent?.Exists ?? false))
            {
                return;
            }

            dir = dir.Parent;

            var file = new FileInfo(Path.Combine(dir !.FullName, teleporterPluginConfigFile));

            if (file.Exists)
            {
                ParseTeleporterFile(file.FullName);
            }

            void OnTeleporterConfigChange(object source, FileSystemEventArgs args)
            {
                PluginLog.Verbose("Reloading Teleporter Config.");
                if (args.ChangeType != WatcherChangeTypes.Changed && args.ChangeType != WatcherChangeTypes.Created)
                {
                    return;
                }

                ParseTeleporterFile(args.FullPath);
            }

            _teleporterWatcher = new FileSystemWatcher
            {
                Path         = dir.FullName,
                NotifyFilter = NotifyFilters.LastWrite,
                Filter       = teleporterPluginConfigFile,
            };
            _teleporterWatcher.Changed += OnTeleporterConfigChange;
            _teleporterWatcher !.EnableRaisingEvents = true;
        }
        protected override void Setup64Bit(SigScanner scanner)
        {
            ComboTimer = scanner.GetStaticAddressFromSig("48 89 2D ?? ?? ?? ?? 85 C0 74 0F");

            GetIcon = scanner.ScanText("E8 ?? ?? ?? ?? 8B F8 3B DF");  // Client::Game::ActionManager.GetAdjustedActionId

            IsIconReplaceable = scanner.ScanText("81 F9 ?? ?? ?? ?? 7F 39 81 F9 ?? ?? ?? ??");

            GetActionCooldown = scanner.ScanText("E8 ?? ?? ?? ?? 0F 57 FF 48 85 C0");

            PluginLog.Verbose("===== H O T B A R S =====");
            PluginLog.Verbose($"GetIcon address   0x{GetIcon.ToInt64():X}");
            PluginLog.Verbose($"IsIconReplaceable 0x{IsIconReplaceable.ToInt64():X}");
            PluginLog.Verbose($"ComboTimer        0x{ComboTimer.ToInt64():X}");
            PluginLog.Verbose($"LastComboMove     0x{LastComboMove.ToInt64():X}");
        }
Example #20
0
        private bool DrawDeletionConfirmationWindow()
        {
            if (!DeleteConfirmationVisible)
            {
                return(false);
            }

            var ret = false;

            ImGui.SetNextWindowSize(new Vector2(232, 100), ImGuiCond.Always);
            if (ImGui.Begin("Remove this channel config?", ref this.deleteConfirmationVisible,
                            ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
            {
                ImGui.Text("Are you sure you want to delete this?");
                ImGui.Text("This cannot be undone.");
                if (ImGui.Button("Yes"))
                {
                    PluginLog.Verbose("Killing the thing.");

                    this.configuration.ChatTypeConfigurations.Remove(currentEntry);

                    PluginLog.Verbose($"Removed the configuration for {currentEntry.Channel.ToString()}");
                    Save();

                    currentEntry = null;
                    // Visible = false;
                    // Visible = true;

                    ret = true;
                    DeleteConfirmationVisible = false;
                }
                ImGui.SameLine();
                if (ImGui.Button("No"))
                {
                    Visible = false;
                    Visible = true;
                    DeleteConfirmationVisible = false;
                }
            }



            ImGui.End();

            return(ret);
        }
Example #21
0
        protected override void Setup64Bit(SigScanner scanner)
        {
            AddonSelectYesNoOnSetupAddress          = scanner.ScanText(AddonSelectYesNoOnSetupSignature);
            AddonSalvageDialongOnSetupAddress       = scanner.ScanText(AddonSalvageDialogOnSetupSignature);
            AddonMaterializeDialongOnSetupAddress   = scanner.ScanText(AddonMaterializeDialogOnSetupSignature);
            AddonItemInspectionResultOnSetupAddress = scanner.ScanText(AddonItemInspectionResultOnSetupSignature);
            AddonRetainerTaskAskOnSetupAddress      = scanner.ScanText(AddonRetainerTaskAskOnSetupSignature);
            AddonRetainerTaskResultOnSetupAddress   = scanner.ScanText(AddonRetainerTaskResultOnSetupSignature);

            PluginLog.Verbose("===== YES ALREADY =====");
            PluginLog.Verbose($"{nameof(AddonSelectYesNoOnSetupAddress)} {AddonSelectYesNoOnSetupAddress.ToInt64():X}");
            PluginLog.Verbose($"{nameof(AddonSalvageDialongOnSetupAddress)} {AddonSalvageDialongOnSetupAddress.ToInt64():X}");
            PluginLog.Verbose($"{nameof(AddonMaterializeDialongOnSetupAddress)} {AddonMaterializeDialongOnSetupAddress.ToInt64():X}");
            PluginLog.Verbose($"{nameof(AddonItemInspectionResultOnSetupAddress)} {AddonItemInspectionResultOnSetupAddress.ToInt64():X}");
            PluginLog.Verbose($"{nameof(AddonRetainerTaskAskOnSetupAddress)} {AddonRetainerTaskAskOnSetupAddress.ToInt64():X}");
            PluginLog.Verbose($"{nameof(AddonRetainerTaskResultOnSetupAddress)} {AddonRetainerTaskResultOnSetupAddress.ToInt64():X}");
        }
Example #22
0
        protected override void Setup64Bit(SigScanner scanner)
        {
            ComboTimer = scanner.GetStaticAddressFromSig("E8 ?? ?? ?? ?? 80 7E 21 00", 0x178);

            // this.GetIcon = scanner.ScanText("48 89 5c 24 08 48 89 6c 24 10 48 89 74 24 18 57 48 83 ec 30 8b da be dd 1c 00 00 bd d3 0d 00 00");  // 5.35
            GetIcon = scanner.ScanText("E8 ?? ?? ?? ?? 8B F8 3B DF"); // 5.4

            // this.IsIconReplaceable = scanner.ScanText("81 f9 2e 01 00 00 7f 39 81 f9 2d 01 00 00 0f 8d 11 02 00 00 83 c1 eb");  // 5.35
            IsIconReplaceable = scanner.ScanText("81 F9 ?? ?? ?? ?? 7F 39 81 F9 ?? ?? ?? ??"); // 5.4

            BuffVTableAddr = scanner.GetStaticAddressFromSig("48 89 05 ?? ?? ?? ?? 88 05 ?? ?? ?? ?? 88 05 ?? ?? ?? ??");

            PluginLog.Verbose("===== H O T B A R S =====");
            PluginLog.Verbose($"GetIcon address   0x{GetIcon.ToInt64():X}");
            PluginLog.Verbose($"IsIconReplaceable 0x{IsIconReplaceable.ToInt64():X}");
            PluginLog.Verbose($"ComboTimer        0x{ComboTimer.ToInt64():X}");
            PluginLog.Verbose($"LastComboMove     0x{LastComboMove.ToInt64():X}");
        }
        internal static bool DrawConfigUi(Configuration config, DalamudPluginInterface pi,
                                          Action <IPluginConfiguration> save, IReadOnlyCollection <ClassJob> jobs,
                                          IReadOnlyCollection <FFXIVAction> allActions, ref IEnumerator <VibrationPattern.Step?>?patternEnumerator)
        {
            var shouldDrawConfigUi = true;
            var changed            = false;
            var scale = ImGui.GetIO().FontGlobalScale;

            ImGuiHelpers.ForceNextWindowMainViewport();
            ImGui.SetNextWindowSize(new Vector2(575 * scale, 400 * scale), ImGuiCond.FirstUseEver);
            ImGui.SetNextWindowSizeConstraints(new Vector2(350 * scale, 200 * scale),
                                               new Vector2(float.MaxValue, float.MaxValue));
            if (!ImGui.Begin($"{GentleTouch.PluginName} Configuration", ref shouldDrawConfigUi,
                             ImGuiWindowFlags.NoCollapse))
            {
                ImGui.End();
                return(shouldDrawConfigUi);
            }

            if (config.OnboardingStep != Onboarding.Done)
            {
                changed |= DrawRisksWarning(config, ref shouldDrawConfigUi, scale);
                changed |= DrawOnboarding(config, jobs, allActions, scale);
            }

            ImGui.BeginTabBar("ConfigurationTabs", ImGuiTabBarFlags.NoTooltip);
            changed |= DrawGeneralTab(config, scale);
            changed |= DrawPatternTab(config, scale, ref patternEnumerator);
            changed |= DrawTriggerTab(config, pi, scale, jobs, allActions);
            ImGui.EndTabBar();
            ImGui.End();
            if (!changed)
            {
                return(shouldDrawConfigUi);
            }
#if DEBUG
            PluginLog.Verbose("Config changed, saving...");
#endif
            save(config);
            return(shouldDrawConfigUi);
        }
Example #24
0
        private static async Task <dynamic> Get(string endpoint, bool noCache = false)
        {
            PluginLog.Verbose("XIVAPI FETCH: {0}", endpoint);

            if (CachedResponses.TryGetValue(endpoint, out var val) && !noCache)
            {
                return(val);
            }

            var client   = new HttpClient();
            var response = await client.GetAsync(URL + endpoint);

            var result = await response.Content.ReadAsStringAsync();

            var obj = JObject.Parse(result);

            if (!noCache)
            {
                CachedResponses.TryAdd(endpoint, obj);
            }

            return(obj);
        }
Example #25
0
        public void Draw()
        {
            if (!this.isVisible)
            {
                return;
            }

            ImGui.Begin("Discord Bridge Setup", ref this.isVisible);

            ImGui.Text("In this window, you can set up the XIVLauncher Discord Bridge.\n\n" +
                       "To begin, enter your discord bot token and username below, then click \"Save\".\n" +
                       "As soon as the red text says \"connected\", click the \"Join my server\" button and add the bot to one of your personal servers.\n" +
                       $"You can then use the {this.plugin.Config.DiscordBotPrefix}help command in your discord server to specify channels.");

            ImGui.Dummy(new Vector2(10, 10));

            ImGui.InputText("Enter your bot token", ref this.token, 100);
            ImGui.InputText("Enter your Username(e.g. user#0000)", ref this.username, 50);

            ImGui.Dummy(new Vector2(10, 10));

            ImGui.Text("Status: ");
            ImGui.SameLine();

            var message = this.plugin.Discord.State switch
            {
                DiscordState.None => "Not started",
                DiscordState.Ready => "Connected!",
                DiscordState.TokenInvalid => "Token empty or invalid.",
                _ => "Unknown"
            };

            ImGui.TextColored(this.plugin.Discord.State == DiscordState.Ready ? fineColor : errorColor, message);
            if (this.plugin.Discord.State == DiscordState.Ready && ImGui.Button("Join my server"))
            {
                Process.Start(
                    $"https://discordapp.com/oauth2/authorize?client_id={this.plugin.Discord.UserId}&scope=bot&permissions=537258064");
            }

            ImGui.Dummy(new Vector2(10, 10));

            if (ImGui.Button("How does this work?"))
            {
                Process.Start(Constant.HelpLink);
            }

            ImGui.SameLine();

            if (ImGui.Button("Save"))
            {
                PluginLog.Verbose("Reloading Discord...");

                this.plugin.Config.DiscordToken     = this.token;
                this.plugin.Config.DiscordOwnerName = this.username;
                this.plugin.Config.Save();

                this.plugin.Discord.Dispose();
                this.plugin.Discord = new DiscordHandler(this.plugin);
                this.plugin.Discord.Start();
            }
        }
    }
Example #26
0
 // Save the current sort order.
 // Does not save or copy the backup in the current mod directory,
 // as this is done on mod directory changes only.
 private void SaveFilesystem()
 {
     SaveToFile(new FileInfo(ModFileSystemFile), SaveMod, true);
     PluginLog.Verbose("Saved mod filesystem.");
 }
Example #27
0
        public NodeManager(DalamudPluginInterface pi, GatherBuddyConfiguration config, World territories,
                           AetheryteManager aetherytes, ItemManager gatherables)
        {
            var baseSheet = pi.Data.GetExcelSheet <GatheringPointBase>();
            var nodeSheet = pi.Data.GetExcelSheet <GatheringPoint>();

            Dictionary <uint, Node> baseIdToNode = new((int)baseSheet.RowCount);

            NodeIdToNode = new Dictionary <uint, Node>((int)nodeSheet.RowCount);

            foreach (var nodeRow in nodeSheet)
            {
                var baseId = nodeRow.GatheringPointBase.Row;
                if (baseId >= baseSheet.RowCount)
                {
                    continue;
                }

                if (baseIdToNode.TryGetValue(baseId, out var node))
                {
                    NodeIdToNode[nodeRow.RowId] = node;
                    if ((node.Nodes !.Territory?.Id ?? 0) != nodeRow.TerritoryType.Row)
                    {
                        PluginLog.Error($"Different gathering nodes to the same base {baseId} have different territories.");
                    }

                    if (!node.Nodes.Nodes.ContainsKey(nodeRow.RowId))
                    {
                        node.Nodes.Nodes[nodeRow.RowId] = null;
                    }
                    continue;
                }

                if (nodeRow.TerritoryType.Row < 2)
                {
                    continue;
                }

                node = new Node
                {
                    PlaceNameEn = FFName.FromPlaceName(pi, nodeRow.PlaceName.Row)[Dalamud.ClientLanguage.English],
                    Nodes       = new SubNodes()
                    {
                        Territory = territories.FindOrAddTerritory(nodeRow.TerritoryType.Value),
                    },
                };
                node.Nodes.Nodes[nodeRow.RowId] = null;
                if (node.Nodes.Territory == null)
                {
                    continue;
                }

                var(times, type) = GetTimes(pi, nodeRow.RowId);
                node.Times       = times;

                var baseRow = baseSheet.GetRow(baseId);
                node.Meta = new NodeMeta(baseRow, type);

                if (node.Meta.GatheringType >= GatheringType.Spearfishing)
                {
                    continue;
                }

                node.Items = new NodeItems(node, baseRow.Item, gatherables);
                if (node.Items.NoItems())
                {
                    PluginLog.Debug("Gathering node {RowId} has no items, skipped.", nodeRow.RowId);
                    continue;
                }

                baseIdToNode[baseId]        = node;
                NodeIdToNode[nodeRow.RowId] = node;
            }

            Records = new NodeRecorder(pi, this, config.Records);

            PluginLog.Verbose("{Count} unique gathering nodes collected.", NodeIdToNode.Count);
            PluginLog.Verbose("{Count} base gathering nodes collected.", baseIdToNode.Count);

            ApplyHiddenItemsAndCoordinates(gatherables, aetherytes, baseIdToNode);
        }
        public async Task SendChatEvent(string message, string senderName, string senderWorld, XivChatType chatType, string avatarUrl = "")
        {
            // set fields for true chat messages or custom via ipc
            if (chatType != XivChatTypeExtensions.IpcChatType)
            {
                // Special case for outgoing tells, these should be sent under Incoming tells
                if (chatType == XivChatType.TellOutgoing)
                {
                    chatType = XivChatType.TellIncoming;
                }
            }
            else
            {
                senderWorld = null;
            }

            // default avatar url to logo link if empty
            if (string.IsNullOrEmpty(avatarUrl))
            {
                avatarUrl = Constant.LogoLink;
            }

            var applicableChannels =
                this.plugin.Config.ChannelConfigs.Where(x => x.Value.ChatTypes.Contains(chatType));

            if (!applicableChannels.Any())
            {
                return;
            }

            message = this.specialChars.TransformToUnicode(message);


            try
            {
                switch (chatType)
                {
                case XivChatType.Echo:
                    break;

                case (XivChatType)61:     // npc talk
                    break;

                case (XivChatType)68:     // npc announce
                    break;

                default:
                    // don't even bother searching if it's gonna be invalid
                    bool doSearch = true;
                    if (string.IsNullOrEmpty(senderName))
                    {
                        PluginLog.Verbose($"Sender Name was null or empty: {senderName}");
                        doSearch = false;
                    }
                    if (string.IsNullOrEmpty(senderWorld))
                    {
                        PluginLog.Verbose($"Sender World was null or empty: {senderWorld}");
                        doSearch = false;
                    }
                    if (senderName == "Sonar" || !senderName.Contains(" "))
                    {
                        PluginLog.Verbose($"Sender Name was a plugin or invalid: {senderName}");
                        doSearch = false;
                    }
                    if (doSearch)
                    {
                        var playerCacheName = $"{senderName}@{senderWorld}";
                        PluginLog.Verbose($"Searching for {playerCacheName}");

                        if (CachedResponses.TryGetValue(playerCacheName, out LodestoneCharacter lschar))
                        {
                            PluginLog.Verbose($"Retrived cached data for {lschar.Name} {lschar.Avatar.ToString()}");
                            avatarUrl = lschar.Avatar.ToString();
                        }
                        else
                        {
                            PluginLog.Verbose($"Searching lodestone for {playerCacheName}");
                            lschar = await lodestoneClient.SearchCharacter(new CharacterSearchQuery()
                            {
                                CharacterName = senderName,
                                World         = senderWorld,
                            }).Result.Results.FirstOrDefault(result => result.Name == senderName).GetCharacter();

                            CachedResponses.TryAdd(playerCacheName, lschar);
                            PluginLog.Verbose($"Adding cached data for {lschar.Name} {lschar.Avatar}");
                            avatarUrl = lschar.Avatar.ToString();
                        }

                        // avatarUrl = (await XivApiClient.GetCharacterSearch(senderName, senderWorld)).AvatarUrl;
                    }

                    break;
                }
            }
            catch (Exception ex)
            {
                if (string.IsNullOrEmpty(senderName))
                {
                    PluginLog.Error($"senderName was null or empty. How did we get this far?");
                    senderName = "Bridge Error - sendername";
                }
                else
                {
                    PluginLog.Error(ex, $"Cannot fetch XIVAPI character search for {senderName} on {senderWorld}");
                }
            }

            var displayName = senderName + (string.IsNullOrEmpty(senderWorld) || string.IsNullOrEmpty(senderName)
                ? ""
                : $"@{senderWorld}");

            this.plugin.Config.PrefixConfigs.TryGetValue(chatType, out var prefix);

            var chatTypeText = this.plugin.Config.CustomSlugsConfigs.TryGetValue(chatType, out var x) ? x : chatType.GetSlug();


            foreach (var channelConfig in applicableChannels)
            {
                var socketChannel = this.socketClient.GetChannel(channelConfig.Key);

                if (socketChannel == null)
                {
                    PluginLog.Error("Could not find channel {0} for {1}", channelConfig.Key, chatType);

                    var channelConfigs = this.plugin.Config.ChannelConfigs;
                    channelConfigs.Remove(channelConfig.Key);
                    this.plugin.Config.ChannelConfigs = channelConfigs;


                    PluginLog.Log("Removing channel {0}'s config because it no longer exists or cannot be accessed.", channelConfig.Key);
                    this.plugin.Config.Save();

                    continue;
                }

                var webhookClient = await GetOrCreateWebhookClient(socketChannel);

                var messageContent = chatType != XivChatTypeExtensions.IpcChatType ? $"{prefix}**[{chatTypeText}]** {message}" : $"{prefix} {message}";


                // check for duplicates before sending
                // straight up copied from the previous bot, but I have no way to test this myself.
                var recentMessages = (socketChannel as SocketTextChannel).GetCachedMessages();
                var recentMsg      = recentMessages.FirstOrDefault(msg => msg.Content == messageContent);


                if (this.plugin.Config.DuplicateCheckMS > 0 && recentMsg != null)
                {
                    long msgDiff = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - recentMsg.Timestamp.ToUnixTimeMilliseconds();

                    if (msgDiff < this.plugin.Config.DuplicateCheckMS)
                    {
                        PluginLog.Log($"[IN TESTING]\n DIFF:{msgDiff}ms Skipping duplicate message: {messageContent}");
                        return;
                    }
                }

                await webhookClient.SendMessageAsync(
                    messageContent, username : displayName, avatarUrl : avatarUrl,
                    allowedMentions : new AllowedMentions(AllowedMentionTypes.Roles | AllowedMentionTypes.Users | AllowedMentionTypes.None)
                    );

                // the message to a list of recently sent messages.
                // If someone else sent the same thing at the same time
                // both will need to be checked and the earlier timestamp kept
                // while the newer one is removed
                // refer to https://discord.com/channels/581875019861328007/684745859497590843/791207648619266060
            }
        }
 // Clear the current cache.
 private void ClearCache()
 {
     _cache?.Dispose();
     _cache = null;
     PluginLog.Verbose("Cleared cache of collection {Name:l}.", Name);
 }
        private async Task SocketClientOnMessageReceived(SocketMessage message)
        {
            if (message.Author.IsBot || message.Author.IsWebhook)
            {
                return;
            }

            var args = message.Content.Split();

            // if it doesn't start with the bot prefix, ignore it.
            if (!args[0].StartsWith(this.plugin.Config.DiscordBotPrefix))
            {
                return;
            }

            /*
             * // this is only needed for debugging purposes.
             * foreach (var s in args)
             * {
             *  PluginLog.Verbose(s);
             * }
             */

            PluginLog.Verbose("Received command: {0}", args[0]);

            try
            {
                if (args[0] == this.plugin.Config.DiscordBotPrefix + "setchannel" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length == 1)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You need to specify some chat kinds to use.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    var kinds = args[1].Split(',').Select(x => x.ToLower());

                    // Is there any chat type that's not recognized?
                    if (kinds
                        .Any(x =>
                             XivChatTypeExtensions.TypeInfoDict.All(y => y.Value.Slug != x) && x != "any"))
                    {
                        PluginLog.Verbose("Could not find kinds");
                        await SendGenericEmbed(message.Channel,
                                               $"One or more of the chat kinds you specified could not be found.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    if (!this.plugin.Config.ChannelConfigs.TryGetValue(message.Channel.Id, out var config))
                    {
                        config = new DiscordChannelConfig();
                    }

                    foreach (var selectedKind in kinds)
                    {
                        PluginLog.Verbose(selectedKind);

                        if (selectedKind == "any")
                        {
                            config.SetUnique(DefaultChatTypes);
                        }
                        else
                        {
                            var chatType = XivChatTypeExtensions.GetBySlug(selectedKind);
                            config.SetUnique(chatType);
                        }
                    }

                    this.plugin.Config.ChannelConfigs[message.Channel.Id] = config;
                    this.plugin.Config.Save();

                    await SendGenericEmbed(message.Channel,
                                           $"OK! This channel has been set to receive the following chat kinds:\n\n```\n{config.ChatTypes.Select(x => $"{x.GetFancyName()}").Aggregate((x, y) => x + "\n" + y)}```",
                                           "Chat kinds set", EmbedColorFine);

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "unsetchannel" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length == 1)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You need to specify some chat kinds to use.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    var kinds = args[1].Split(',').Select(x => x.ToLower());

                    // Is there any chat type that's not recognized?
                    if (kinds.Any(x =>
                                  XivChatTypeExtensions.TypeInfoDict.All(y => y.Value.Slug != x) && x != "any"))
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"One or more of the chat kinds you specified could not be found.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    if (!this.plugin.Config.ChannelConfigs.TryGetValue(message.Channel.Id, out var config))
                    {
                        config = new DiscordChannelConfig();
                    }

                    foreach (var selectedKind in kinds)
                    {
                        if (selectedKind == "any")
                        {
                            config.UnsetUnique(DefaultChatTypes);
                        }
                        else
                        {
                            var chatType = XivChatTypeExtensions.GetBySlug(selectedKind);
                            config.UnsetUnique(chatType);
                        }
                    }

                    this.plugin.Config.ChannelConfigs[message.Channel.Id] = config;
                    this.plugin.Config.Save();

                    if (config.ChatTypes.Count() == 0)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"All chat kinds have been removed from this channel.",
                                               "Chat Kinds unset", EmbedColorFine);
                    }
                    await SendGenericEmbed(message.Channel,
                                           $"OK! This channel will still receive the following chat kinds:\n\n```\n{config.ChatTypes.Select(x => $"{x.GetSlug()} - {x.GetFancyName()}").Aggregate((x, y) => x + "\n" + y)}```",
                                           "Chat kinds unset", EmbedColorFine);

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "setprefix" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length < 3)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You need to specify some chat kinds and a prefix to use.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    var kinds = args[1].Split(',').Select(x => x.ToLower());

                    // Is there any chat type that's not recognized?
                    if (kinds.Any(x =>
                                  XivChatTypeExtensions.TypeInfoDict.All(y => y.Value.Slug != x) && x != "any"))
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"One or more of the chat kinds you specified could not be found.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    if (args[2] == "none")
                    {
                        args[2] = string.Empty;
                    }

                    foreach (var selectedKind in kinds)
                    {
                        var type = XivChatTypeExtensions.GetBySlug(selectedKind);
                        this.plugin.Config.PrefixConfigs[type] = args[2];
                    }

                    this.plugin.Config.Save();


                    await SendGenericEmbed(message.Channel,
                                           $"OK! The following prefixes are set:\n\n```\n{this.plugin.Config.PrefixConfigs.Select(x => $"{x.Key.GetFancyName()} - {x.Value}").Aggregate((x, y) => x + "\n" + y)}```",
                                           "Prefix set", EmbedColorFine);

                    return;
                }



                if (args[0] == this.plugin.Config.DiscordBotPrefix + "unsetprefix" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length < 2)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You need to specify some chat kinds and a prefix to use.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    var kinds = args[1].Split(',').Select(x => x.ToLower());

                    // Is there any chat type that's not recognized?
                    if (kinds.Any(x =>
                                  XivChatTypeExtensions.TypeInfoDict.All(y => y.Value.Slug != x) && x != "any"))
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"One or more of the chat kinds you specified could not be found.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    foreach (var selectedKind in kinds)
                    {
                        var type = XivChatTypeExtensions.GetBySlug(selectedKind);
                        this.plugin.Config.PrefixConfigs.Remove(type);
                    }

                    this.plugin.Config.Save();

                    if (this.plugin.Config.PrefixConfigs.Count() == 0)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"All prefixes have been removed.",
                                               "Prefix unset", EmbedColorFine);
                    }
                    else // this doesn't seem to trigger when there's only one entry left. I don't know why.
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"OK! The prefix for {XivChatTypeExtensions.GetBySlug(args[2])} has been removed.\n\n"
                                               + $"The following prefixes are still set:\n\n```\n{this.plugin.Config.PrefixConfigs.Select(x => $"{x.Key.GetFancyName()} - {x.Value}").Aggregate((x, y) => x + "\n" + y)}```",
                                               "Prefix unset", EmbedColorFine);
                    }

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "setchattypename" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length < 3)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You need to specify one or more chat kinds and a custom name.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }



                    var kinds = args[1].Split(',').Select(x => x.ToLower());
                    var chatChannelOverride = string.Join(" ", args.Skip(2)).Trim('"');

                    // PluginLog.Information($"arg1: {args[1]}; arg2: {chatChannelOverride}");

                    // Is there any chat type that's not recognized?
                    if (kinds.Any(x =>
                                  XivChatTypeExtensions.TypeInfoDict.All(y => y.Value.Slug != x) && x != "any"))
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"One or more of the chat kinds you specified could not be found.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    if (chatChannelOverride == "none")
                    {
                        foreach (var selectedKind in kinds)
                        {
                            var type = XivChatTypeExtensions.GetBySlug(selectedKind);
                            this.plugin.Config.CustomSlugsConfigs[type] = type.GetSlug();
                        }

                        await SendGenericEmbed(message.Channel,
                                               $"OK! The following custom chat type names have been set:\n\n```\n{this.plugin.Config.CustomSlugsConfigs.Select(x => $"{x.Key.GetFancyName()} - {x.Value}").Aggregate((x, y) => x + "\n" + y)}```",
                                               "Custom chat type set", EmbedColorFine);
                    }
                    else
                    {
                        foreach (var selectedKind in kinds)
                        {
                            var type = XivChatTypeExtensions.GetBySlug(selectedKind);
                            this.plugin.Config.CustomSlugsConfigs[type] = chatChannelOverride;
                        }

                        await SendGenericEmbed(message.Channel,
                                               $"OK! The following custom chat type names have been set:\n\n```\n{this.plugin.Config.CustomSlugsConfigs.Select(x => $"{x.Key.GetFancyName()} - {x.Value}").Aggregate((x, y) => x + "\n" + y)}```",
                                               "Custom chat type set", EmbedColorFine);
                    }

                    this.plugin.Config.Save();

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "unsetchattypename" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length < 2)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"One or more of the chat kinds you specified could not be found.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }



                    var kinds = args[1].Split(',').Select(x => x.ToLower());

                    PluginLog.Information($"Unsetting custom type name for arg1: {args[1]}");

                    // Is there any chat type that's not recognized?
                    if (kinds.Any(x =>
                                  XivChatTypeExtensions.TypeInfoDict.All(y => y.Value.Slug != x) && x != "any"))
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"One or more of the chat kinds you specified could not be found.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }


                    foreach (var selectedKind in kinds)
                    {
                        var type = XivChatTypeExtensions.GetBySlug(selectedKind);
                        this.plugin.Config.CustomSlugsConfigs[type] = type.GetSlug();
                    }

                    await SendGenericEmbed(message.Channel,
                                           $"OK! The following custom chat type names have been set:\n\n```\n{this.plugin.Config.CustomSlugsConfigs.Select(x => $"{x.Key.GetFancyName()} - {x.Value}").Aggregate((x, y) => x + "\n" + y)}```",
                                           "Custom chat type unset", EmbedColorFine);


                    this.plugin.Config.Save();

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "setduplicatems" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length == 1)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You need to specify a number in milliseconds to use.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    var kinds = args[1].Split(',').Select(x => x.ToLower());

                    // Make sure that it's a number (or assume it is)
                    int newDelay = int.Parse(args[1]);

                    this.plugin.Config.DuplicateCheckMS = newDelay;
                    this.plugin.Config.Save();

                    await SendGenericEmbed(message.Channel,
                                           $"OK! Any messages with the same content within the last **{newDelay}** milliseconds will be skipped, preventing duplicate posts.",
                                           "Duplicate Message Check", EmbedColorFine);

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "toggledf" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    if (!this.plugin.Config.ChannelConfigs.TryGetValue(message.Channel.Id, out var config))
                    {
                        config = new DiscordChannelConfig();
                    }

                    config.IsContentFinder = !config.IsContentFinder;

                    this.plugin.Config.ChannelConfigs[message.Channel.Id] = config;
                    this.plugin.Config.Save();

                    await SendGenericEmbed(message.Channel,
                                           $"OK! This channel has been {(config.IsContentFinder ? "enabled" : "disabled")} from receiving Duty Finder notifications.",
                                           "Duty Finder set", EmbedColorFine);

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "setcfprefix" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    // Are there parameters?
                    if (args.Length < 2)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You need to specify a prefix to use, or type \"none\" if you want to remove it.\nCheck the ``{this.plugin.Config.DiscordBotPrefix}help`` command for more information.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    if (args[1] == "none")
                    {
                        args[1] = string.Empty;
                    }

                    this.plugin.Config.CFPrefixConfig = args[1];

                    this.plugin.Config.Save();


                    await SendGenericEmbed(message.Channel,
                                           $"OK! The following prefix was set:\n\n```\n{this.plugin.Config.CFPrefixConfig}```",
                                           "Prefix set", EmbedColorFine);

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "listchannel" &&
                    await EnsureOwner(message.Author, message.Channel))
                {
                    if (!this.plugin.Config.ChannelConfigs.TryGetValue(message.Channel.Id, out var config))
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"You didn't set up any channel kinds for this channel yet.\nPlease use the ``{this.plugin.Config.DiscordBotPrefix}setchannel`` command to do this.",
                                               "Error", EmbedColorError);

                        return;
                    }

                    if (config == null || config.ChatTypes.Count == 0)
                    {
                        await SendGenericEmbed(message.Channel,
                                               $"There are no channel kinds set for this channel right now.\nPlease use the ``{this.plugin.Config.DiscordBotPrefix}setchannel`` command to do this.",
                                               "Error", EmbedColorFine);

                        return;
                    }

                    await SendGenericEmbed(message.Channel,
                                           $"OK! This channel has been set to receive the following chat kinds:\n\n```\n{config.ChatTypes.Select(x => $"{x.GetFancyName()}").Aggregate((x, y) => x + "\n" + y)}```",
                                           "Chat kinds set", EmbedColorFine);

                    return;
                }

                if (args[0] == this.plugin.Config.DiscordBotPrefix + "help")
                {
                    PluginLog.Verbose("Help time");

                    var builder = new EmbedBuilder()
                                  .WithTitle("Discord Bridge Help")
                                  .WithDescription("You can use the following commands to set up the Discord bridge.")
                                  .WithColor(new Color(EmbedColorFine))
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}setchannel", "Select, which kinds of chat should arrive in this channel.\n" +
                                            $"Format: ``{this.plugin.Config.DiscordBotPrefix}setchannel <kind1,kind2,...>``\n\n" +
                                            $"See [this link for a list of all available chat kinds]({Constant.KindListLink}) or type ``any`` to enable it for all regular chat messages.")
                                  //$"The following chat kinds are available:\n```all - All regular chat\n{XivChatTypeExtensions.TypeInfoDict.Select(x => $"{x.Value.Slug} - {x.Value.FancyName}").Aggregate((x, y) => x + "\n" + y)}```")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}unsetchannel", "Works like the previous command, but removes kinds of chat from the list of kinds that are sent to this channel.")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}listchannel", "List all chat kinds that are sent to this channel.")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}toggledf", "Enable or disable sending duty finder updates to this channel.")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}setduplicatems", "Set time in milliseconds that the bot will check to see if any past messages were the same. Default is 0 ms.")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}setprefix", "Set a prefix for chat kinds. "
                                            + $"This can be an emoji or a string that will be prepended to every chat message that will arrive with this chat kind. "
                                            + $"You can also set it to `none` if you want to remove it.\n"
                                            + $"Format: ``{this.plugin.Config.DiscordBotPrefix}setchannel <kind1,kind2,...> <prefix>``")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}setcfprefix", "Set a prefix for duty finder posts. "
                                            + $"You can also set it to `none` if you want to remove it.\n"
                                            + $"Format: ``{this.plugin.Config.DiscordBotPrefix}setcfprefix <prefix>``")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}setchattypename ", "Set custom text for chat kinds. "
                                            + $"This can be an emoji or a string that will replace the short name of a chat kind for every chat message that will arrive with this chat kind. "
                                            + $"You can also set it to `none` if you want to remove it.\n"
                                            + $"Format: ``{this.plugin.Config.DiscordBotPrefix}setchattypename  <kind1,kind2,...> <custom text>``")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}unsetprefix", "Remove prefix set for a chat kind. \n"
                                            + $"Format: ``{this.plugin.Config.DiscordBotPrefix}unsetprefix <kind>``")
                                  .AddField($"{this.plugin.Config.DiscordBotPrefix}unsetchattypename", "Remove custom name for a chat kind. \n"
                                            + $"Format: ``{this.plugin.Config.DiscordBotPrefix}unsetchattypename <kind>``")
                                  .AddField("Need more help?",
                                            $"You can [read the full step-by-step guide]({Constant.HelpLink}) or [join our Discord server]({Constant.DiscordJoinLink}) to ask for help.")
                                  .WithFooter(footer =>
                    {
                        footer
                        .WithText("Dalamud Discord Bridge")
                        .WithIconUrl(Constant.LogoLink);
                    })
                                  .WithThumbnailUrl(Constant.LogoLink);
                    var embed = builder.Build();

                    var m = await message.Channel.SendMessageAsync(
                        null,
                        embed : embed)
                            .ConfigureAwait(false);

                    ;
                    PluginLog.Verbose(m.Id.ToString());

                    return;
                }
            }
            catch (Exception ex)
            {
                PluginLog.Error(ex, "Could not handle incoming Discord message.");
            }
        }