├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── blacklist_appeal.yml │ ├── bug_report.yml │ ├── config.yml │ ├── feature_request.yml │ └── security_vulnerabilty.yml └── workflows │ ├── build.yml │ ├── deploy.yml │ └── docs.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── tasks.json ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── docs ├── CNAME ├── assets │ ├── logo.svg │ └── theme.css ├── index.md ├── legal │ ├── index.md │ ├── privacy-policy.md │ └── tos.md ├── overrides │ ├── assets │ │ └── stylesheets │ │ │ └── main.css │ └── main.html └── request-data-deletion.md ├── mkdocs.yml ├── proto └── grid_bot.proto ├── scripts └── clean.sh ├── services ├── Directory.Build.props ├── Directory.Build.targets ├── grid-bot │ ├── .component.yaml │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── grid-bot-bare.sln │ ├── grid-bot.sln │ ├── lib │ │ ├── Directory.Build.props │ │ ├── Directory.Build.targets │ │ ├── commands │ │ │ ├── Attributes │ │ │ │ ├── LockDownCommandAttribute.cs │ │ │ │ └── RequireCommandBotRoleAttribute.cs │ │ │ ├── Modules │ │ │ │ ├── Commands │ │ │ │ │ ├── ExecuteScript.cs │ │ │ │ │ ├── Help.cs │ │ │ │ │ ├── Render.cs │ │ │ │ │ └── Support.cs │ │ │ │ └── Interactions │ │ │ │ │ ├── ExecuteScript.cs │ │ │ │ │ ├── Render.cs │ │ │ │ │ └── Support.cs │ │ │ ├── Monitoring │ │ │ │ ├── RenderPerformanceCounters.cs │ │ │ │ └── ScriptExecutionPerformanceCounters.cs │ │ │ ├── PrivateModules │ │ │ │ └── Commands │ │ │ │ │ ├── ClientSettings.cs │ │ │ │ │ ├── Maintenance.cs │ │ │ │ │ ├── Roles.cs │ │ │ │ │ └── Settings.cs │ │ │ └── Shared.Commands.csproj │ │ ├── events │ │ │ ├── Events │ │ │ │ ├── OnCommandExecuted.cs │ │ │ │ ├── OnLogMessage.cs │ │ │ │ ├── OnMessage.cs │ │ │ │ ├── OnReady.cs │ │ │ │ ├── OnSlashCommand.cs │ │ │ │ └── OnSlashCommandExecuted.cs │ │ │ └── Shared.Events.csproj │ │ ├── grpc │ │ │ ├── Extensions │ │ │ │ └── IServiceProviderExtensions.cs │ │ │ ├── Grid.Bot.Grpc.csproj │ │ │ └── Implementation │ │ │ │ └── GridBotGrpcServer.cs │ │ ├── settings │ │ │ ├── EnvironmentProvider.cs │ │ │ ├── Providers │ │ │ │ ├── AvatarSettings.cs │ │ │ │ ├── BacktraceSettings.cs │ │ │ │ ├── BaseSettingsProvider.cs │ │ │ │ ├── ClientSettingsSettings.cs │ │ │ │ ├── CommandsSettings.cs │ │ │ │ ├── ConsulSettings.cs │ │ │ │ ├── DiscordRolesSettings.cs │ │ │ │ ├── DiscordSettings.cs │ │ │ │ ├── FloodCheckerSettings.cs │ │ │ │ ├── GlobalSettings.cs │ │ │ │ ├── GridSettings.cs │ │ │ │ ├── GrpcSettings.cs │ │ │ │ ├── MaintenanceSettings.cs │ │ │ │ ├── ScriptsSettings.cs │ │ │ │ ├── UsersClientSettings.cs │ │ │ │ └── WebSettings.cs │ │ │ ├── SettingsProvidersDefaults.cs │ │ │ └── Shared.Settings.csproj │ │ ├── utility │ │ │ ├── Enums │ │ │ │ ├── BotRole.cs │ │ │ │ ├── FilterType.cs │ │ │ │ └── SettingType.cs │ │ │ ├── Extensions │ │ │ │ ├── DictionaryExtensions.cs │ │ │ │ ├── IAttachmentExtensions.cs │ │ │ │ ├── IUserMessageExtensions.cs │ │ │ │ ├── ModuleBaseExtensions.cs │ │ │ │ └── SocketInteractionExtensions.cs │ │ │ ├── Implementation │ │ │ │ ├── AdminUtility.cs │ │ │ │ ├── AvatarUtility.cs │ │ │ │ ├── BacktraceUtility.cs │ │ │ │ ├── ClientSettingsFactory.cs │ │ │ │ ├── ClientSettingsNameHelper.cs │ │ │ │ ├── DiscordWebhookAlertManager.cs │ │ │ │ ├── ExpirableDictionary.cs │ │ │ │ ├── FilteredValue.cs │ │ │ │ ├── FloodCheckerRegistry.cs │ │ │ │ ├── JobManager.cs │ │ │ │ ├── LazyWithRetry.cs │ │ │ │ ├── LoggerFactory.cs │ │ │ │ ├── LuaUtility.cs │ │ │ │ ├── MicrosoftLogger.cs │ │ │ │ ├── MicrosoftLoggerProvider.cs │ │ │ │ ├── RbxUsersUtility.cs │ │ │ │ ├── RefreshAhead.cs │ │ │ │ └── ScriptLogger.cs │ │ │ ├── Interfaces │ │ │ │ ├── IAdminUtility.cs │ │ │ │ ├── IAvatarUtility.cs │ │ │ │ ├── IBacktraceUtility.cs │ │ │ │ ├── IClientSettingsFactory.cs │ │ │ │ ├── IDiscordWebhookAlertManager.cs │ │ │ │ ├── IFloodCheckerRegistry.cs │ │ │ │ ├── IJobManager.cs │ │ │ │ ├── ILoggerFactory.cs │ │ │ │ ├── ILuaUtility.cs │ │ │ │ ├── IRbxUsersUtility.cs │ │ │ │ └── IScriptLogger.cs │ │ │ ├── Lua │ │ │ │ └── LuaVMTemplate.lua │ │ │ └── Shared.Utility.csproj │ │ └── web │ │ │ ├── Extensions │ │ │ ├── HttpContextExtensions.cs │ │ │ ├── HttpServerTelemetryExtensions.cs │ │ │ └── IServiceProviderExtensions.cs │ │ │ ├── Grid.Bot.Web.csproj │ │ │ ├── Middleware │ │ │ ├── HttpServerConcurrentRequestsMiddleware.cs │ │ │ ├── HttpServerMiddlewareBase.cs │ │ │ ├── HttpServerRequestCountMiddleware.cs │ │ │ ├── HttpServerRequestLoggingMiddleware.cs │ │ │ ├── HttpServerResponseMiddleware.cs │ │ │ └── UnhandledExceptionMiddleware.cs │ │ │ └── Routes │ │ │ ├── Avatar.cs │ │ │ ├── ClientSettings.cs │ │ │ └── VersionCompatibility.cs │ ├── src │ │ ├── App.config │ │ ├── Grid.Bot.csproj │ │ ├── Program.cs │ │ ├── Runner.cs │ │ └── appsettings.json │ └── ssl │ │ └── global-root-ca.crt └── recovery │ ├── .component.yaml │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── grid-bot-recovery.sln │ └── src │ ├── Events │ ├── OnLogMessage.cs │ ├── OnMessage.cs │ ├── OnReady.cs │ └── OnSlashCommand.cs │ ├── Grid.Bot.Recovery.csproj │ ├── Implementation │ ├── BotCheckWorker.cs │ ├── BotManager.cs │ ├── DiscordWebhookAlertManager.cs │ └── Settings.cs │ ├── Interfaces │ ├── IBotManager.cs │ ├── IDiscordWebhookAlertManager.cs │ └── ISettings.cs │ ├── Program.cs │ └── Runner.cs └── targets └── git-metadata.targets /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | *.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | *.jpg binary 44 | *.png binary 45 | *.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/blacklist_appeal.yml: -------------------------------------------------------------------------------- 1 | name: Blacklist Appeal 2 | description: Request a blacklist appeal. File this if you would like to appeal a blacklist for yourself or a guild you own. 3 | title: "[Appeal]: " 4 | labels: ["kind: appeal", "status: backlogged", "status: needs-review"] 5 | assignees: ["nikita-petko"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Thanks for taking the time to fill out this blacklist appeal! 10 | - type: checkboxes 11 | attributes: 12 | label: You have read the blacklist guidelines? 13 | description: Please check the [guidelines](https://grid-bot.ops.vmminfra.net/moderation) to determine if your appeal conforms to them. 14 | options: 15 | - label: "I double checked the guidelines and my appeal conforms to them." 16 | required: true 17 | - type: checkboxes 18 | attributes: 19 | label: You haven't been blacklisted before? 20 | description: If you have been blacklisted before, then don't file an appeal as you'll get declined. 21 | options: 22 | - label: "I double checked the blacklist and I haven't been blacklisted before." 23 | required: true 24 | - type: input 25 | id: discord-user-id 26 | attributes: 27 | label: The ID of the Discord User you are appealing for. If you are appealing for a guild this must be the ID of the owner. 28 | description: Please refer to the [Discord Helpcenter](https://support.discord.com/hc/en-us/articles/206346498) for more information on how to find the Discord ID of a user. 29 | placeholder: ex. 819791451436482600 30 | validations: 31 | required: true 32 | - type: input 33 | id: date-blacklisted 34 | attributes: 35 | label: The date you were blacklisted. 36 | description: You must provide the date you were blacklisted. As the moderation team will need to determine *why* you were blacklisted. In the format of `YYYY-MM-DD HH:MM:SS`. 37 | placeholder: ex. 2020-01-01 00:00:00 38 | validations: 39 | required: true 40 | - type: input 41 | id: guild-id 42 | attributes: 43 | label: The ID of the guild you think you were blacklisted in or you are getting a blacklist revoked for. 44 | description: Please refer to the [Discord Helpcenter](https://support.discord.com/hc/en-us/articles/206346498) for more information on how to find the ID of a guild. 45 | placeholder: ex. 559083251805978644 46 | validations: 47 | required: true 48 | - type: input 49 | id: channel-id 50 | attributes: 51 | label: The ID of the channel you think you were blacklisted in. This does not apply if you are appealing guild blacklist. 52 | description: Please refer to the [Discord Helpcenter](https://support.discord.com/hc/en-us/articles/206346498) for more information on how to find the ID of a channel. 53 | placeholder: ex. 954168914273767484 54 | validations: 55 | required: false 56 | - type: textarea 57 | id: appeal-reason 58 | attributes: 59 | label: The reason you are appealing. 60 | description: Minimum of 50 characters on why you are appealing. 61 | placeholder: I am appealing because I was blacklisted for spamming... 62 | validations: 63 | required: true 64 | - type: textarea 65 | id: apology 66 | attributes: 67 | label: An apology for the moderation team. 68 | description: Minimum of 100 characters apology towards the moderation team and the clients who use the bot. 69 | placeholder: I am sorry for the spamming and I am trying to get this removed. 70 | validations: 71 | required: true 72 | - type: textarea 73 | id: images 74 | attributes: 75 | label: Images 76 | description: Attach images to the appeal. This is optional if you have evidence to prove your innocence. 77 | validations: 78 | required: false 79 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report, use this if you have discovered a bug in production and wish the internal team to investigate. 3 | title: "[Bug]: " 4 | labels: ["kind: fix", "status: backlogged", "status: needs-review"] 5 | assignees: ["nikita-petko"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Thanks for taking the time to fill out this bug report! 10 | - type: checkboxes 11 | attributes: 12 | label: Check if there is an existing issue or pull request. 13 | description: Please check the [issues](https://github.com/mfdlabs/grid-bot/issues) to determine if your issue has already been filed. 14 | options: 15 | - label: "I double checked issues and couldn't find any duplicates." 16 | required: true 17 | - type: checkboxes 18 | attributes: 19 | label: This occurs on the latest version of the bot. 20 | description: If this bug occurs on a previous version of the bot, please check this box. There will be a version string in the README.md that will correlate to a version that you can check on the bot. 21 | options: 22 | - label: I verified that the bot is running on the latest version. 23 | required: true 24 | - type: checkboxes 25 | attributes: 26 | label: Does this bug occur frequently? 27 | description: If this bug occurs frequently, please tick this box. 28 | options: 29 | - label: This bug occurs frequently. 30 | required: false 31 | - type: textarea 32 | id: description 33 | attributes: 34 | label: Description 35 | description: A detailed explination of the bug. 36 | placeholder: When rendering, it will throw an error. 37 | validations: 38 | required: true 39 | - type: input 40 | id: deployment-id 41 | attributes: 42 | label: The deployment ID of the bot. 43 | description: This refers to the deployment ID of the bot, there will be a command that lets you retrieve this value (/support). Use this if it's version specific. 44 | placeholder: ex. 2022.03.06-01.36.27_master_561a09f-net48-Release 45 | validations: 46 | required: false 47 | - type: input 48 | id: dates-of-occurrence 49 | attributes: 50 | label: The dates of occurrence of the bug. 51 | description: This refers to the dates of occurrence of the bug. These can be on any time zone, but must include the year, month, day, hour, minute, and second (if possible). This accepts multiple dates, separated by commas. 52 | placeholder: ex. 2022/03/06 01:36:27 UTC+1, 2022/03/06 01:37:28 UTC+1, 2022/03/06 01:38:11 UTC+1 53 | validations: 54 | required: false 55 | - type: input 56 | id: machine-id 57 | attributes: 58 | label: The hostname, Machine ID and IP of the machine 59 | description: This refers to the hostname, Machine ID and IP of the machine, there will be a command that lets you retrieve this value (/support). This is optional, but is very helpful for debugging. 60 | placeholder: ex. dapp181-dec.distrubuted.jfk01-us-east-01.mfdlabs.local, JFK01-DApp181, 10.128.29.28 61 | validations: 62 | required: false 63 | - type: textarea 64 | id: images 65 | attributes: 66 | label: Images 67 | description: Attach images to the bug report. This is optional. 68 | validations: 69 | required: false 70 | - type: textarea 71 | id: steps-to-reproduce 72 | attributes: 73 | label: Steps to Reproduce 74 | description: Please provide steps to reproduce the bug. This is optional, but is highly recommended. 75 | placeholder: > 76 | 1. Execute command /execute script script:BLAH BLAH 77 | 2. Paste this code in blah blah. 78 | 3. Magic!! 79 | validations: 80 | required: false 81 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Discord Guild Invite 4 | url: https://discord.gg/DURUeUcfar 5 | about: Join this Guild if you want to talk directly to the owner of the bot. 6 | - name: Discord Bot Invite 7 | url: https://discord.com/api/oauth2/authorize?client_id=819791451436482600&permissions=2147798080&response_type=code&scope=bot%20applications.commands%20messages.read 8 | about: If you wish to add the bot to your guilds, use this invite url to do so. 9 | - name: Grid Bot Moderation Guidelines 10 | url: https://grid-bot.ops.vmminfra.net/moderation 11 | about: Please refer to here before opening Blacklist appeals. 12 | - name: Grid Bot Terms of Service 13 | url: https://grid-bot.ops.vmminfra.net/legal/tos 14 | about: Please refer to here for the terms and conditions for using or integrating this bot within Discord. 15 | - name: Grid Bot Privacy Policy 16 | url: https://grid-bot.ops.vmminfra.net/legal/privacy-policy 17 | about: Please refer to here for the information on how we use your data internally. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request a new feature. File this if you would like to see a new feature added to the bot. 3 | title: "[Feature]: " 4 | labels: ["kind: feature", "kind: enhancement", "status: backlogged", "status: needs-review"] 5 | assignees: ["nikita-petko"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Thanks for taking the time to fill out this feature request! 10 | - type: checkboxes 11 | attributes: 12 | label: Check if there is an existing issue or pull request 13 | description: Please check the [issues](https://github.com/mfdlabs/grid-bot/issues) to determine if your feature request is already in the works or has already been requested. 14 | options: 15 | - label: "I double checked issues and couldn't find any duplicates." 16 | required: true 17 | - type: checkboxes 18 | attributes: 19 | label: Verify Feature Quality. 20 | description: > 21 | If your feature is actually worth being integrated. This includes things like: 22 | - Does it work? 23 | - Is it a good idea? 24 | - Does it benefit the user? 25 | - Will it generate traffic? 26 | options: 27 | - label: I verified the feature I requested, and it is worth being integrated. 28 | required: true 29 | - type: textarea 30 | id: description 31 | attributes: 32 | label: Description 33 | description: A detailed explaintion of the feature. Be as detailed as possible. Going from how the feature works to the benefits of the feature. 34 | placeholder: It adds new code and stuff blah blah. 35 | validations: 36 | required: true 37 | - type: textarea 38 | id: images 39 | attributes: 40 | label: Images 41 | description: Attach images to the feature request. 42 | validations: 43 | required: false 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security_vulnerabilty.yml: -------------------------------------------------------------------------------- 1 | name: Security Vulnerabilty Report 2 | description: File a security vulnerability report, use this if you have discovered a security vulnerability and wish the internal team to investigate. 3 | title: "[Vulnerability]: " 4 | labels: ["kind: hotfix", "priority: key deliverable", "status: backlogged", "status: needs-review"] 5 | assignees: ["nikita-petko"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Thanks for taking the time to fill out this security vulnerability report! This will be marked with the label "key deliverable", which will email @mfdlabs/sec-ops, @mfdlabs/ops and @mfdlabs/grid-team 10 | - type: checkboxes 11 | attributes: 12 | label: Check if there is an existing issue or pull request. 13 | description: Please check the [issues](https://github.com/mfdlabs/grid-bot/issues) to determine if your vulnerability has already been filed. 14 | options: 15 | - label: "I double checked issues and couldn't find any duplicates." 16 | required: true 17 | - type: checkboxes 18 | attributes: 19 | label: This occurs on the latest version of the bot. 20 | description: If this bug occurs on the latest version of the bot, please check this box. There will be a version string in the README.md that will correlate to a version that you can check on the bot. 21 | options: 22 | - label: I verified that this occurs on the latest version. 23 | required: true 24 | - type: checkboxes 25 | attributes: 26 | label: Have you read the Security Vulnerability Guidelines? 27 | description: If you have read the [Security Vulnerability Guidelines](https://github.com/mfdlabs/grid-bot/blob/master/SECURITY.md), and this issue conforms to them, please check this box. 28 | options: 29 | - label: I have read the Security Vulnerability Guidelines. 30 | required: true 31 | - type: dropdown 32 | id: severity 33 | attributes: 34 | label: Vulernability Severity 35 | description: This refers to the severity of the vulnerability. This is required. 36 | options: 37 | - "Critical" 38 | - "High" 39 | - "Medium" 40 | - "Low" 41 | - "Invonvenience" 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: description 46 | attributes: 47 | label: Description 48 | description: A brief explination of the vulnerability. 49 | placeholder: ex. Public IP address is exposed. 50 | validations: 51 | required: true 52 | - type: input 53 | id: deployment-id 54 | attributes: 55 | label: The deployment ID of the bot. 56 | description: This refers to the deployment ID of the bot, there will be a command that lets you retrieve this value (/support). Use this if it's version specific. 57 | placeholder: ex. 2022.03.06-01.36.27_master_561a09f-net48-Release 58 | validations: 59 | required: false 60 | - type: input 61 | id: dates-of-occurrence 62 | attributes: 63 | label: The dates of occurrence of the vulnerability. 64 | description: This refers to the dates of occurrence of the vulnerability. These can be on any time zone, but must include the year, month, day, hour, minute, and second (if possible). This accepts multiple dates, separated by commas. 65 | placeholder: ex. 2022/03/06 01:36:27 UTC+1, 2022/03/06 01:37:28 UTC+1, 2022/03/06 01:38:11 UTC+1 66 | validations: 67 | required: false 68 | - type: input 69 | id: machine-id 70 | attributes: 71 | label: The hostname, Machine ID and IP of the machine 72 | description: This refers to the hostname, Machine ID and IP of the machine, there will be a command that lets you retrieve this value (/support). This is optional, but is very helpful for debugging. 73 | placeholder: ex. dapp181-dec.distrubuted.jfk01-us-east-01.mfdlabs.local, JFK01-DApp181, 10.128.29.28 74 | validations: 75 | required: false 76 | - type: textarea 77 | id: images 78 | attributes: 79 | label: Images 80 | description: Attach images to the vulnerability report. This is optional, but is very helpful for debugging. 81 | validations: 82 | required: false 83 | - type: textarea 84 | id: steps-to-reproduce 85 | attributes: 86 | label: Steps to Reproduce 87 | description: Please provide steps to reproduce the bug. This is optional, but is highly recommended. 88 | placeholder: > 89 | 1. Execute command /execute script script:BLAH BLAH 90 | 2. Paste this code in blah blah. 91 | 3. Magic!! 92 | validations: 93 | required: false 94 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: MkDocs Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | paths: 8 | - "docs/**" 9 | - ".github/workflows/docs.yml" 10 | - "mkdocs.yml" 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | mkdocs: 17 | name: MkDocs Deploy 18 | if: ${{ !contains(toJSON(github.event.head_commit.message), '!#skip-build#!') }} 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.10' 30 | 31 | - name: Install dependencies 32 | run: | 33 | pip install mkdocs-material mkdocs-minify-plugin 34 | 35 | - name: Configure Git user 36 | run: | 37 | git config --local user.email "ops+grid-bot-gh@vmminfra.net" 38 | git config --local user.name "grid-bot-gh" 39 | 40 | - name: Deploy docs 41 | run: | 42 | mkdocs gh-deploy --force 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.DEPLOYER_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib"] 2 | path = lib 3 | url = git@github.com:mfdlabs/grid-bot-libraries.git 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Grid.Bot", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceFolder}/services/grid-bot/src/bin/Debug/net8.0/Grid.Bot.dll", 10 | "cwd": "${workspaceFolder}/services/grid-bot/src/", 11 | "console": "internalConsole", 12 | "stopAtEntry": false, 13 | "logging": { 14 | "moduleLoad": false 15 | }, 16 | "requireExactSource": false, 17 | "envFile": "${workspaceFolder}/.env" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/services/grid-bot/src/Grid.Bot.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary", 13 | "/p:Configuration=Debug", 14 | "/p:Platform=AnyCPU", 15 | ], 16 | "problemMatcher": "$msCompile" 17 | }, 18 | { 19 | "label": "build-full", 20 | "command": "dotnet", 21 | "type": "process", 22 | "args": [ 23 | "build", 24 | "${workspaceFolder}/services/grid-bot/src/Grid.Bot.csproj", 25 | "/property:GenerateFullPaths=true", 26 | "/consoleloggerparameters:NoSummary", 27 | "/p:Configuration=Debug", 28 | "/p:Platform=AnyCPU", 29 | "/p:LocalBuild=true" 30 | ], 31 | "problemMatcher": "$msCompile" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # "Who Owns This Sector?", "Grid Team" 2 | * @mfdlabs/grid-team @mfdlabs/who-owns-this-sector 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | 3 | Please conform to the following guidelines when contributing: 4 | 5 | ## Development Cycle 6 | 7 | We prefer all changes to the app to be discussed beforehand, 8 | either in a GitHub issue, or in a an email thread with primary developers or other contributors. 9 | 10 | ### Issues 11 | 12 | Unless the change is tied to a hot-fix, all pull requests must have an issue related to them. 13 | 14 | ### Labels 15 | 16 | Issues and pull requests must include labels prefixed with "kind" and "priority", all others are optional and can be applied as such. 17 | 18 | ### Pull Requests 19 | 20 | We prefer pull-requests that are descriptive of the changes being made 21 | and highlight any potential benefits/drawbacks of the change, but these 22 | types of write-ups are not required. 23 | 24 | Even if your change is really small, please open a pull request about it. 25 | 26 | Please note that as this project is under MFDLABS, branch naming rules for this organization will be enforced and are as follows: 27 | 28 | > Revised Code Branches, supersedes old branches rules.
29 | > Branches are split like this for branches ***not specific to teams***:
30 | > ***{operation}/{fixture}*** 31 | > 32 | > Branchesare split like this for branches ***specific to teams***:
33 | > ***{operation}/{teamName}/{fixture}*** 34 | > 35 | > The following operations are allowed:
36 | >
37 | > 38 | > **feature** -> *Represents a single feature to be integrated.*
39 | > **ops** -> *Refers to a major overhaul with a core operation of the system or a dependency change.*
40 | > **fix** -> *Refers to a single bug fix.*
41 | > **enhancement** -> *Refers to changes or enhancements made to already existing repository files.*
42 | > **dev** -> *Use this to refer to an entire branch prefix of releases dedicated to a team (as in it will be merged here before it gets merged to master)*
43 | > **hotfix** -> *Use this when implementing crucial hotfixes into production.*
44 | > 45 | >
46 | > 47 | > You ***MUST*** create a new branch for every new change, fix etc. you make, if the changes can be split, do it. 48 | > 49 | > For the fixture, it can only contain characters *`a-zA-Z1-0\-_`*, try to be brief with the name but also giving information about the change. 50 | > 51 | > If you can, try to also include Jira ticket IDs or backlog issue urls. 52 | > Example:
53 | > PR Title: ***[BCKLOG-657](https://jira.dex2.vmminfra.dev/BCKLOG-657?from=gh)***: *Runtime fixture for possible crash exploit exposure through admin API.*
54 | > Branch: ***[fix/security-ops/bcklog-657-potential-crash-exploit](https://github.vmminfra.dev/mfdlabs/backlog/tree/fix/security-ops/bcklog-657-potential-crash-exploit)***. 55 | > 56 | > When you merge your PR into your chosen branch, do not delete the branch. 57 | > 58 | > Pull requests must be reviewed by codeowners (if applicable) or a repository owner if you are contributing to them. 59 | 60 | ### Commits 61 | 62 | The main ruling for commits is that they are preferred be signed with a GPG key, if you made the change via github.com, they should be signed as default. 63 | 64 | To learn how to do this, please follow this guide: 65 | - [Signing Commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) 66 | 67 | ### Reviews 68 | 69 | When you create a pull-request, even if you're a repository administrator, you will have to go through code review. You can review the [CODEOWNERS](./CODEOWNERS) file for a list of default assigned members that will review your pull-request, or you can request another repository administrator or organization member to do it for you. 70 | 71 | ## Coding Style 72 | 73 | As a general rule, follow the coding style already set in the file you 74 | are editing, or look at a similar file if you are adding a new one. 75 | 76 | ### Documentation Style 77 | 78 | Any new code should always have documentation comments above it, and your code should also be either self documenting or have it's own comments to explain what it does, but that doesn't mean commenting on the most obvious things like variable assignment. 79 | 80 | If using GitHub co-pilot, please try to remove any of the comments made to generate the code. 81 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # MFDLABS Grid Bot Security Vulnerability Report guidelines 2 | 3 | Thank you for your interest in reporting security vulnerabilities to MFDLABS Grid Team. 4 | There are a few things you should know before you start: 5 | 6 | ## 1. Determing if the vulnerability causes critical security that should not be used by other users. 7 | 8 | Determine if the vulnerability you have found can cause damage to the system or to other users. If so please email it to [ops+code-security-grid-bot@vmminfra.net](mailto:ops+code-security-grid-bot@vmminfra.net) 9 | 10 | ## 2. Collecting as much information as possible about the vulnerability. 11 | 12 | Collect as much information as possible about the vulnerability is important to ensure that the vulnerability is not a false positive. 13 | Images, text, deployments, and other information that can help you identify the vulnerability is helpful. -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | grid-bot.ops.vmminfra.net -------------------------------------------------------------------------------- /docs/assets/theme.css: -------------------------------------------------------------------------------- 1 | [data-md-color-scheme=slate] { 2 | --md-default-bg-color: #1d1d1f; 3 | --md-typeset-a-color: var(--md-accent-fg-color) !important; 4 | } 5 | 6 | .md-typeset a:focus, .md-typeset a:hover { 7 | color: #72b8ff; 8 | } 9 | 10 | .md-footer-copyright::after{ 11 | content: "· Original theme by Elttob"; 12 | } 13 | 14 | :root { 15 | color-scheme: dark; 16 | } 17 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Home" 3 | description: "The home page for the Grid Bot!" 4 | --- 5 | 6 | # Announcement 7 | 8 | As of Tuesday, the 21st of March 2024, Grid Bot's infrastructure was turned off. 9 | 10 | The reasoning behind this was simply put, it became majorily costly. 11 | 12 | This bot ran on this stack of infrastructure on AWS: 13 | 14 | 1 Linux machine, @ 16 cores, 32 GiB -- This hosted the Bot, as well as its underlying dependencies such as Redis, Consul and grid-service-websrv 15 | 1 Client VPN -- This prevented outside access to internal components such as configuration and a job management. 16 | 17 | Overall, this costed upwards of $1000 a month to maintain fully, which did get cost draws but in the end, for the whole year it would nearly cost $11.000. 18 | 19 | ## What now? 20 | 21 | The position of hosting this is open for applications, if people wish to work with me they may open an issue on [GitHub](https://github.com/mfdlabs/grid-bot) or email me [petko@vmminfra.net](mailto:petko@vmminfra.net). You must have infrastructure capabilities to handle the following: 22 | 23 | - Constant throughput from ~31.000 guilds every second. 24 | - Enough storage and compute power to handle clients hosting upwards of 30 shards. 25 | -------------------------------------------------------------------------------- /docs/legal/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Legal 3 | description: The legal documents such as ToS and Privacy Policy 4 | --- 5 | 6 | # Legal 7 | This subdirectory contains documents pertaining the Terms of Use and Privacy Policy involved when you either use or integrate this "Bot" in or into any guild you may own. 8 | 9 | # Terms Of Use 10 | Terms of use are the rules, specifications, and requirements for the use of a product or service. They serve as a contract between the product or service provider and user. 11 | 12 | The phrase is sometimes used interchangeably with “terms of service” or “terms and conditions”. 13 | 14 | # Privacy Policy 15 | A privacy policy is a legal document that explains how a company or website collects, uses, and shares personal information. Privacy policies should outline what personal information is collected, how the information is used, whether the information is shared with third parties, and what rights users have over their data. 16 | -------------------------------------------------------------------------------- /docs/legal/privacy-policy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Privacy Policy 3 | description: This document outlines how we use your data to enhance the experience of this "Bot" 4 | --- 5 | 6 | # Privacy Policy 7 | 8 | To be determined 9 | -------------------------------------------------------------------------------- /docs/legal/tos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Terms of Use 3 | description: This document outlines the terms to be followed by the clients that use this "Bot" 4 | --- 5 | 6 | # Terms of Use 7 | 8 | To be determined 9 | -------------------------------------------------------------------------------- /docs/overrides/assets/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | .md-banner strong { 2 | white-space: nowrap; 3 | } 4 | .md-banner a { 5 | color: white; 6 | font-size: 0.8rem; 7 | } 8 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extrahead %} 4 | 8 | {% if page and page.meta and page.meta.robots %} 9 | 10 | {% else %} 11 | 12 | {% endif %} 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /docs/request-data-deletion.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Request Deletion of your Data 3 | description: Outlines the steps you need to take in order to request the deletion of your Discord user information from the bot's registry. 4 | --- 5 | 6 | # Pre requisitories 7 | 8 | In order to submit a data deletion request, you must do the following: 9 | 10 | 1. You must be the rightful owner of the account in question, you will be asked further down the request to verify your ownership. 11 | 1. You must have a list of all information you think is stored in the bot's registries that you would like deleted. 12 | 1. You must be sure that this is what you want to do, as this operation is irreversible. 13 | 14 | # Submitting the request 15 | 16 | In order to submit the request, you must email [ops+grid-bot-deletion-requests@vmminfra.net](mailto:ops+grid-bot-deletion-requests@vmminfra.net) from the email of the account in question. 17 | Please include all of the data mentioned above, and be ready to be asked for ownership verification. 18 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: "MFDLABS Grid Bot Documentation" 2 | site_url: "https://grid-bot.ops.vmminfra.net/" 3 | repo_name: "mfdlabs/grid-bot" 4 | repo_url: "https://github.com/mfdlabs/grid-bot/issues" 5 | edit_uri: "" 6 | remote_branch: deploy/docs-dev/docs-deploy 7 | 8 | theme: 9 | name: material 10 | custom_dir: docs/overrides 11 | logo: assets/logo.svg 12 | favicon: assets/logo.svg 13 | font: 14 | text: Inter 15 | code: JetBrains Mono 16 | icon: 17 | repo: fontawesome/brands/github 18 | palette: 19 | scheme: slate 20 | primary: black 21 | accent: blue 22 | features: 23 | - content.code.annotate 24 | - navigation.instant 25 | - navigation.tracking 26 | - navigation.tabs 27 | 28 | extra_css: 29 | - assets/theme.css 30 | 31 | plugins: 32 | - search 33 | - minify: 34 | minify_html: true 35 | 36 | markdown_extensions: 37 | - admonition 38 | - attr_list 39 | - def_list 40 | - meta 41 | - pymdownx.betterem 42 | - pymdownx.details 43 | - pymdownx.tasklist: 44 | custom_checkbox: true 45 | - pymdownx.tabbed: 46 | alternate_style: true 47 | - pymdownx.inlinehilite 48 | - pymdownx.keys 49 | - pymdownx.magiclink 50 | - pymdownx.superfences 51 | - pymdownx.highlight: 52 | guess_lang: false 53 | - toc: 54 | permalink: true 55 | - pymdownx.emoji: 56 | emoji_index: !!python/name:materialx.emoji.twemoji 57 | emoji_generator: !!python/name:materialx.emoji.to_svg 58 | 59 | extra: 60 | social: 61 | - icon: material/application-brackets-outline 62 | link: https://discord.com/api/oauth2/authorize?client_id=819791451436482600&permissions=2147798080&response_type=code&scope=bot%20applications.commands%20messages.read 63 | name: Discord Bot Invite Referrer 64 | analytics: 65 | provider: google 66 | property: G-QV4S1YRK2D 67 | generator: false 68 | 69 | copyright: Copyright © 2024 MFDLABS Corporation 70 | -------------------------------------------------------------------------------- /proto/grid_bot.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grid.bot.v1; 4 | 5 | option csharp_namespace = "Grid.Bot.V1"; 6 | 7 | // GridBotAPI defines the gRPC service for checking health 8 | service GridBotAPI { 9 | // CheckHealth checks the health of the service. 10 | // Used by the recovery instance to determine if recovery is needed. 11 | rpc CheckHealth(CheckHealthRequest) returns (CheckHealthResponse); 12 | }; 13 | 14 | // CheckHealthRequest is the request for the CheckHealth RPC. 15 | message CheckHealthRequest { 16 | }; 17 | 18 | // CheckHealthResponse is the response for the CheckHealth RPC. 19 | message CheckHealthResponse { 20 | // latency is the latency of the Discord.Net API. 21 | int32 latency = 1; 22 | 23 | // status is the status of the Discord.Net API. 24 | string status = 2; 25 | 26 | // shards is a list of shard display names. 27 | repeated string shards = 3; 28 | }; 29 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This will enumerate the workspace and remove obj and bin folders 4 | 5 | # The workspace is always one directory up from the script directory 6 | WORKSPACE=$(dirname $(dirname $(realpath $0))) 7 | 8 | echo "Cleaning workspace: $WORKSPACE" 9 | 10 | # Find all obj and bin folders and remove them recursively and log each removal 11 | find $WORKSPACE -type d -name obj -exec rm -rf {} \; -print 12 | find $WORKSPACE -type d -name bin -exec rm -rf {} \; -print -------------------------------------------------------------------------------- /services/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(NoWarn);4014 4 | 5 | 6 | 7 | net8.0 8 | preview 9 | Grid.Bot 10 | 11 | debug;release 12 | 13 | 14 | 15 | false 16 | 17 | 18 | 19 | $(MSBuildThisFileDirectory)..\ 20 | 21 | 22 | 23 | 24 | 25 | MFDLABS 26 | Copyright © $(Company) $([System.DateTime]::Now.ToString(`yyyy`)). All rights reserved. 27 | $(Company);Nikita Petko 28 | 29 | https://github.com/mfdlabs/grid-bot 30 | git 31 | 32 | $([System.DateTime]::Now.ToString(`yyyy.MM.dd`)) 33 | 34 | false 35 | $(IMAGE_TAG) 36 | dev 37 | 38 | 39 | 40 | true 41 | 42 | 43 | 44 | true 45 | 46 | -------------------------------------------------------------------------------- /services/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_Parameter1>$(AssemblyName).Tests.Unit 5 | 6 | 7 | 8 | <_Parameter1>DynamicProxyGenAssembly2 9 | 10 | 11 | -------------------------------------------------------------------------------- /services/grid-bot/.component.yaml: -------------------------------------------------------------------------------- 1 | component: grid-bot 2 | 3 | # This is only used by the build worklow, 4 | # it determines how the component is built 5 | # Docker only relevant when the argument 6 | # 7 | build: 8 | project_file: src/Grid.Bot.csproj 9 | component_directory: ./.deploy 10 | 11 | additional_args: 12 | - -p:IMAGE_TAG=${{ env.NOMAD_VERSION }} 13 | - -p:CI=true 14 | 15 | docker: 16 | docker_file: Dockerfile 17 | image_name: mfdlabs/grid-bot 18 | 19 | deployment: 20 | count: 1 21 | namespace: grid-bot 22 | 23 | job: grid-bot-${{ env.NOMAD_SHORT_ENVIRONMENT }} 24 | 25 | vault_policies: 26 | - vault_secret_grid_settings_read_write 27 | 28 | # Passed to the meta section in Nomad 29 | meta: 30 | ENVIRONMENT: ${{ env.NOMAD_ENVIRONMENT }} 31 | 32 | containers: # Maps to the groups section in Nomad 33 | - image: mfdlabs/grid-bot 34 | resources: 35 | cpu: ${{ env.NOMAD_CPU }} 36 | ram: ${{ env.NOMAD_RAM }} 37 | network: 38 | mode: host 39 | ports: 40 | metrics: 41 | static: 8101 42 | grpc: 43 | static: 5000 44 | http: 45 | static: 8888 46 | services: 47 | - name: ${{ env.NOMAD_ENVIRONMENT }}-grid-bot 48 | port: metrics 49 | tags: 50 | - ${{ env.NOMAD_ENVIRONMENT }} 51 | checks: 52 | - type: http 53 | path: /metrics 54 | 55 | - name: ${{ env.NOMAD_ENVIRONMENT }}-grid-bot-web 56 | port: http 57 | tags: 58 | - ${{ env.NOMAD_ENVIRONMENT }} 59 | - "traefik.enable=true" 60 | - "traefik.http.routers.${{ env.NOMAD_ENVIRONMENT }}-grid-bot-web-http.rule=(HostRegexp(`{host:[a-zA-Z]+}.sitetest4.robloxlabs.com`) || Host(`versioncompatibility.api.sitetest4.robloxlabs.com`))" 61 | - "traefik.http.routers.${{ env.NOMAD_ENVIRONMENT }}-grid-bot-web-http.entrypoints=http" 62 | checks: 63 | - type: http 64 | path: /health 65 | 66 | - name: ${{ env.NOMAD_ENVIRONMENT }}-grid-bot-web-https 67 | port: http 68 | tags: 69 | - ${{ env.NOMAD_ENVIRONMENT }} 70 | - "traefik.enable=true" 71 | - "traefik.http.routers.${{ env.NOMAD_ENVIRONMENT }}-grid-bot-web-https.rule=(HostRegexp(`{host:[a-zA-Z]+}.sitetest4.robloxlabs.com`) || Host(`versioncompatibility.api.sitetest4.robloxlabs.com`))" 72 | - "traefik.http.routers.${{ env.NOMAD_ENVIRONMENT }}-grid-bot-web-https.entrypoints=https" 73 | - "traefik.http.routers.${{ env.NOMAD_ENVIRONMENT }}-grid-bot-web-https.tls=true" 74 | checks: 75 | - type: http 76 | path: /health 77 | 78 | volumes: 79 | - '/var/run/docker.sock:/var/run/docker.sock' 80 | - '/tmp/.X11-unix:/tmp/.X11-unix' 81 | - '/opt/grid/scripts:/opt/grid/scripts' 82 | - '/_/_logs/grid-bot/${{ env.NOMAD_ENVIRONMENT }}:/tmp/mfdlabs/logs' 83 | config_maps: 84 | - destination: secrets/file.env 85 | env: true 86 | on_change: restart 87 | data: | 88 | DISPLAY=:1 89 | DEFAULT_LOG_LEVEL=Information 90 | VAULT_ADDR="http://vault.service.consul:8200" 91 | VAULT_TOKEN="{{ with secret "grid-bot-settings/grid-bot-vault" }}{{ .Data.data.vault_token }}{{ end }}" 92 | -------------------------------------------------------------------------------- /services/grid-bot/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !ssl 3 | -------------------------------------------------------------------------------- /services/grid-bot/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base Image: net8.0 2 | FROM mcr.microsoft.com/dotnet/aspnet:8.0.1-jammy 3 | 4 | WORKDIR /opt/grid 5 | COPY . /opt/grid/ 6 | 7 | COPY ./ssl/global-root-ca.crt /usr/local/share/ca-certificates/global-root-ca.crt 8 | RUN chmod 644 /usr/local/share/ca-certificates/global-root-ca.crt && update-ca-certificates 9 | 10 | RUN mkdir /opt/grid/logs 11 | RUN mkdir /opt/grid/scripts 12 | 13 | CMD ["dotnet", "/opt/grid/Grid.Bot.dll"] 14 | -------------------------------------------------------------------------------- /services/grid-bot/README.md: -------------------------------------------------------------------------------- 1 | # Grid Bot 2 | 3 | This is a Daemon that runs the main code for the Grid Bot. -------------------------------------------------------------------------------- /services/grid-bot/lib/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(NoWarn);4014 4 | 5 | 6 | 7 | net8.0 8 | preview 9 | Grid.Bot 10 | 11 | debug;release 12 | 13 | 14 | 15 | false 16 | 17 | 18 | 19 | MFDLABS 20 | Copyright © $(Company) $([System.DateTime]::Now.ToString(`yyyy`)). All rights reserved. 21 | $(Company);Nikita Petko 22 | 23 | https://github.com/mfdlabs/grid-bot 24 | git 25 | 26 | $([System.DateTime]::Now.ToString(`yyyy.MM.dd`)) 27 | 28 | false 29 | $(IMAGE_TAG) 30 | dev 31 | 32 | 33 | 34 | true 35 | 36 | 37 | 38 | true 39 | 40 | 41 | 42 | $(MSBuildThisFileDirectory)..\..\..\ 43 | 44 | -------------------------------------------------------------------------------- /services/grid-bot/lib/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_Parameter1>$(AssemblyName).Tests.Unit 5 | 6 | 7 | 8 | <_Parameter1>DynamicProxyGenAssembly2 9 | 10 | 11 | -------------------------------------------------------------------------------- /services/grid-bot/lib/commands/Attributes/LockDownCommandAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Commands; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | using Discord.Commands; 9 | 10 | using Utility; 11 | 12 | /// 13 | /// An attribute only allows the command to be executed within the DMs of 14 | /// users within the specified bot role, or within the specified guild (if configured). 15 | /// 16 | /// 17 | /// Construct a new instance of . 18 | /// 19 | /// The . 20 | public class LockDownCommandAttribute(BotRole botRole = BotRole.Administrator) : PreconditionAttribute 21 | { 22 | private readonly BotRole _botRole = botRole; 23 | 24 | /// 25 | /// The marker to indicate that the command should not respond. 26 | /// 27 | public const string MarkerDoNotRespond = "___||DO_NOT_RESPOND||___"; 28 | 29 | /// 30 | public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo commandInfo, IServiceProvider services) 31 | { 32 | var commandsSettings = services.GetRequiredService(); 33 | if (!commandsSettings.EnableLockdownCommands) 34 | return Task.FromResult(PreconditionResult.FromSuccess()); 35 | 36 | if (context.Guild is not null) 37 | return context.Guild.Id == commandsSettings.LockdownGuildId 38 | ? Task.FromResult(PreconditionResult.FromSuccess()) 39 | : Task.FromResult(PreconditionResult.FromError(MarkerDoNotRespond)); 40 | 41 | var adminUtility = services.GetRequiredService(); 42 | 43 | var isInRole = _botRole switch 44 | { 45 | BotRole.Privileged => adminUtility.UserIsPrivilaged(context.User), 46 | BotRole.Administrator => adminUtility.UserIsPrivilaged(context.User), 47 | BotRole.Owner => adminUtility.UserIsOwner(context.User), 48 | _ => throw new ArgumentOutOfRangeException(nameof(_botRole), _botRole, null), 49 | }; 50 | 51 | return isInRole 52 | ? Task.FromResult(PreconditionResult.FromSuccess()) 53 | : Task.FromResult(PreconditionResult.FromError(MarkerDoNotRespond)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /services/grid-bot/lib/commands/Attributes/RequireCommandBotRoleAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Commands; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | using Discord.Commands; 9 | 10 | using Utility; 11 | 12 | /// 13 | /// An attribute that validates if the user has the required bot role. 14 | /// 15 | /// 16 | /// Construct a new instance of . 17 | /// 18 | /// The . 19 | public class RequireBotRoleAttribute(BotRole botRole = BotRole.Privileged) : PreconditionAttribute 20 | { 21 | /// 22 | /// The role. 23 | /// 24 | public BotRole BotRole { get; } = botRole; 25 | 26 | private const string _permissionDeniedText = "You lack permission to execute this command."; 27 | 28 | 29 | /// 30 | public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo commandInfo, IServiceProvider services) 31 | { 32 | var adminUtility = services.GetRequiredService(); 33 | 34 | return BotRole switch 35 | { 36 | BotRole.Privileged => Task.FromResult(!adminUtility.UserIsPrivilaged(context.User) 37 | ? PreconditionResult.FromError(_permissionDeniedText) 38 | : PreconditionResult.FromSuccess()), 39 | BotRole.Administrator => Task.FromResult(!adminUtility.UserIsPrivilaged(context.User) 40 | ? PreconditionResult.FromError(_permissionDeniedText) 41 | : PreconditionResult.FromSuccess()), 42 | BotRole.Owner => Task.FromResult(!adminUtility.UserIsOwner(context.User) 43 | ? PreconditionResult.FromError(_permissionDeniedText) 44 | : PreconditionResult.FromSuccess()), 45 | _ => throw new ArgumentOutOfRangeException(nameof(BotRole), BotRole, null), 46 | }; 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /services/grid-bot/lib/commands/Modules/Commands/Support.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Commands.Public; 2 | 3 | using System; 4 | using System.Reflection; 5 | using System.Diagnostics; 6 | using System.Threading.Tasks; 7 | using System.Runtime.InteropServices; 8 | 9 | using Discord; 10 | 11 | using Discord.Commands; 12 | 13 | using Networking; 14 | 15 | using Extensions; 16 | 17 | /// 18 | /// Interaction handler for the support commands. 19 | /// 20 | /// 21 | /// Construct a new instance of . 22 | /// 23 | /// The . 24 | /// The . 25 | /// The . 26 | /// The . 27 | /// 28 | /// - cannot be null. 29 | /// - cannot be null. 30 | /// - cannot be null. 31 | /// - cannot be null. 32 | /// 33 | [Group("support"), Summary("Commands used for grid-bot-support.")] 34 | public class Support( 35 | GridSettings gridSettings, 36 | GlobalSettings globalSettings, 37 | ILocalIpAddressProvider localIpAddressProvider, 38 | IGridServerFileHelper gridServerFileHelper 39 | ) : ModuleBase 40 | { 41 | private readonly GridSettings _gridSettings = gridSettings ?? throw new ArgumentNullException(nameof(gridSettings)); 42 | private readonly GlobalSettings _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); 43 | private readonly ILocalIpAddressProvider _localIpAddressProvider = localIpAddressProvider ?? throw new ArgumentNullException(nameof(localIpAddressProvider)); 44 | private readonly IGridServerFileHelper _gridServerFileHelper = gridServerFileHelper ?? throw new ArgumentNullException(nameof(gridServerFileHelper)); 45 | 46 | /// 47 | /// Gets informational links for the bot, in a stylish embed. 48 | /// 49 | [Command("info"), Summary("Get information about Grid Bot."), Alias("information", "about")] 50 | public async Task GetGeneralInformationAsync() 51 | { 52 | var entryAssembly = Assembly.GetEntryAssembly(); 53 | var informationalVersion = entryAssembly.GetCustomAttribute().InformationalVersion; 54 | 55 | var gridServerVersion = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 56 | ? FileVersionInfo.GetVersionInfo(_gridServerFileHelper.GetFullyQualifiedGridServerPath()).FileVersion 57 | : _gridSettings.GridServerImageTag; 58 | 59 | var embed = new EmbedBuilder() 60 | .WithTitle("Grid Bot") 61 | .WithDescription("Grid Bot is a Discord bot that provides a variety of features for interacting with Roblox Grid Servers, such as thumbnailing and Luau execution.") 62 | .WithColor(Color.Blue) 63 | .WithFooter("Grid Bot Support") 64 | .WithCurrentTimestamp() 65 | .AddField("Grid Bot Support Guild", _globalSettings.SupportGuildDiscordUrl) 66 | .AddField("Grid Bot Support Hub", _globalSettings.SupportHubGitHubUrl) 67 | .AddField("Grid Bot Documentation", _globalSettings.DocumentationHubUrl) 68 | .AddField("Machine Name", Environment.MachineName) 69 | .AddField("Machine Host", _localIpAddressProvider.GetHostName()) 70 | .AddField("Local IP Address", _localIpAddressProvider.AddressV4) 71 | .AddField("Bot Version", informationalVersion) 72 | .AddField("Grid Server Version", gridServerVersion) 73 | .Build(); 74 | 75 | await this.ReplyWithReferenceAsync(embed: embed); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /services/grid-bot/lib/commands/Modules/Interactions/Support.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Interactions.Public; 2 | 3 | using System; 4 | using System.Reflection; 5 | using System.Diagnostics; 6 | using System.Threading.Tasks; 7 | using System.Runtime.InteropServices; 8 | 9 | using Discord; 10 | using Discord.Interactions; 11 | 12 | using Networking; 13 | 14 | /// 15 | /// Interaction handler for the support commands. 16 | /// 17 | /// 18 | /// Construct a new instance of . 19 | /// 20 | /// The . 21 | /// The . 22 | /// The . 23 | /// The . 24 | /// 25 | /// - cannot be null. 26 | /// - cannot be null. 27 | /// - cannot be null. 28 | /// - cannot be null. 29 | /// 30 | [Group("support", "Commands used for grid-bot-support.")] 31 | [IntegrationType(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall)] 32 | [CommandContextType(InteractionContextType.Guild, InteractionContextType.BotDm, InteractionContextType.PrivateChannel)] 33 | public class Support( 34 | GridSettings gridSettings, 35 | GlobalSettings globalSettings, 36 | ILocalIpAddressProvider localIpAddressProvider, 37 | IGridServerFileHelper gridServerFileHelper 38 | ) : InteractionModuleBase 39 | { 40 | private readonly GridSettings _gridSettings = gridSettings ?? throw new ArgumentNullException(nameof(gridSettings)); 41 | private readonly GlobalSettings _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); 42 | private readonly ILocalIpAddressProvider _localIpAddressProvider = localIpAddressProvider ?? throw new ArgumentNullException(nameof(localIpAddressProvider)); 43 | private readonly IGridServerFileHelper _gridServerFileHelper = gridServerFileHelper ?? throw new ArgumentNullException(nameof(gridServerFileHelper)); 44 | 45 | /// 46 | /// Gets informational links for the bot, in a stylish embed. 47 | /// 48 | [SlashCommand("info", "Get information about Grid Bot.")] 49 | public async Task GetGeneralInformationAsync() 50 | { 51 | var entryAssembly = Assembly.GetEntryAssembly(); 52 | var informationalVersion = entryAssembly.GetCustomAttribute().InformationalVersion; 53 | 54 | var gridServerVersion = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 55 | ? FileVersionInfo.GetVersionInfo(_gridServerFileHelper.GetFullyQualifiedGridServerPath()).FileVersion 56 | : _gridSettings.GridServerImageTag; 57 | 58 | var embed = new EmbedBuilder() 59 | .WithTitle("Grid Bot") 60 | .WithDescription("Grid Bot is a Discord bot that provides a variety of features for interacting with Roblox Grid Servers, such as thumbnailing and Luau execution.") 61 | .WithColor(Color.Blue) 62 | .WithFooter("Grid Bot Support") 63 | .WithCurrentTimestamp() 64 | .AddField("Grid Bot Support Guild", _globalSettings.SupportGuildDiscordUrl) 65 | .AddField("Grid Bot Support Hub", _globalSettings.SupportHubGitHubUrl) 66 | .AddField("Grid Bot Documentation", _globalSettings.DocumentationHubUrl) 67 | .AddField("Machine Name", Environment.MachineName) 68 | .AddField("Machine Host", _localIpAddressProvider.GetHostName()) 69 | .AddField("Local IP Address", _localIpAddressProvider.AddressV4) 70 | .AddField("Bot Version", informationalVersion) 71 | .AddField("Grid Server Version", gridServerVersion) 72 | .Build(); 73 | 74 | await FollowupAsync(embed: embed); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /services/grid-bot/lib/commands/Monitoring/RenderPerformanceCounters.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using Prometheus; 4 | 5 | internal class RenderPerformanceCounters 6 | { 7 | public static readonly Counter TotalRendersBlockedByGlobalFloodChecker = Metrics.CreateCounter( 8 | "renders_blocked_by_global_flood_checker_total", 9 | "The total number of renders blocked by the global flood checker." 10 | ); 11 | public static readonly Counter TotalRendersBlockedByPerUserFloodChecker = Metrics.CreateCounter( 12 | "renders_blocked_by_per_user_flood_checker_total", 13 | "The total number of renders blocked by the per user flood checker.", 14 | "user_id" 15 | ); 16 | public static readonly Counter TotalRenders = Metrics.CreateCounter( 17 | "renders_total", 18 | "The total number of renders.", 19 | "user_name_or_id" 20 | ); 21 | public static readonly Counter TotalRendersViaUsername = Metrics.CreateCounter( 22 | "renders_via_username_total", 23 | "The total number of renders via username." 24 | ); 25 | public static readonly Counter TotalRendersWithInvalidIds = Metrics.CreateCounter( 26 | "renders_with_invalid_ids_total", 27 | "The total number of renders with invalid IDs." 28 | ); 29 | public static readonly Counter TotalRendersAgainstBannedUsers = Metrics.CreateCounter( 30 | "renders_against_banned_users_total", 31 | "The total number of renders against banned users.", 32 | "user_name_or_id" 33 | ); 34 | public static readonly Counter TotalRendersWithErrors = Metrics.CreateCounter( 35 | "renders_with_errors_total", 36 | "The total number of renders with errors." 37 | ); 38 | public static readonly Counter TotalRendersWithRbxThumbnailsErrors = Metrics.CreateCounter( 39 | "renders_with_rbx_thumbnails_errors_total", 40 | "The total number of renders with rbx-thumbnails errors.", 41 | "state" 42 | ); 43 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/commands/Shared.Commands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Shared library for commands used by grid-bot registered dynamically. 4 | 5 | true 6 | 7 | 8 | 9 | $(DefineConstants);WE_LOVE_EM_SLASH_COMMANDS 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /services/grid-bot/lib/events/Shared.Events.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library containg event objects that are dispatched via the Discord gateway. 4 | 5 | true 6 | 7 | 8 | 9 | $(DefineConstants);WE_LOVE_EM_SLASH_COMMANDS;DISCORD_SHARDING_ENABLED;DEBUG_LOGGING_IN_PROD;DEBUG_LOG_WEBSOCKET_CLOSED_EXCEPTIONS 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /services/grid-bot/lib/grpc/Extensions/IServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Grpc; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using System.Collections.Generic; 6 | using System.Security.Authentication; 7 | 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Builder; 10 | using Microsoft.AspNetCore.Server.Kestrel.Core; 11 | 12 | using Microsoft.Extensions.Logging; 13 | using Microsoft.Extensions.DependencyInjection; 14 | 15 | using Discord.WebSocket; 16 | 17 | using Logging; 18 | using Utility; 19 | 20 | /// 21 | /// gRPC extensions for the 22 | /// 23 | public static class IServiceProviderExtensions 24 | { 25 | /// 26 | /// Implement the grid-bot gRPC server into the current service provider. 27 | /// 28 | /// The 29 | /// The application arguments. 30 | public static void UseGrpcServer(this IServiceProvider services, IEnumerable args) 31 | { 32 | var grpcSettings = services.GetRequiredService(); 33 | var logger = new Logger( 34 | name: grpcSettings.GrpcServerLoggerName, 35 | logLevelGetter: () => grpcSettings.GrpcServerLoggerLevel, 36 | logToConsole: true, 37 | logToFileSystem: false 38 | ); 39 | 40 | if (!grpcSettings.GridBotGrpcServerEnabled) 41 | { 42 | logger.Warning("The grid-bot gRPC server is disabled in settings, not starting gRPC server!"); 43 | 44 | return; 45 | } 46 | 47 | var client = services.GetRequiredService(); 48 | 49 | logger.Information("Starting gRPC server on {0}", grpcSettings.GridBotGrpcServerEndpoint); 50 | 51 | var builder = WebApplication.CreateBuilder([.. args]); 52 | 53 | builder.Logging.ClearProviders(); 54 | builder.Logging.AddProvider(new MicrosoftLoggerProvider(logger)); 55 | 56 | builder.Services.AddSingleton(client); 57 | 58 | builder.Services.AddGrpc(); 59 | 60 | if (grpcSettings.GrpcServerUseTls) 61 | { 62 | builder.Services.Configure(options => 63 | { 64 | options.ConfigureEndpointDefaults(listenOptions => 65 | { 66 | listenOptions.Protocols = HttpProtocols.Http2; 67 | 68 | try 69 | { 70 | listenOptions.UseHttps(grpcSettings.GrpcServerCertificatePath, grpcSettings.GrpcServerCertificatePassword, httpsOptions => 71 | { 72 | httpsOptions.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12; 73 | }); 74 | } 75 | catch (Exception ex) 76 | { 77 | logger.Warning("Failed to configure gRPC with HTTPS because: {0}. Will resort to insecure host instead!", ex.Message); 78 | } 79 | }); 80 | }); 81 | 82 | // set urls 83 | } 84 | else 85 | { 86 | builder.Services.Configure(options => 87 | { 88 | options.ConfigureEndpointDefaults(listenOptions => 89 | { 90 | listenOptions.Protocols = HttpProtocols.Http2; 91 | }); 92 | }); 93 | } 94 | 95 | var app = builder.Build(); 96 | 97 | app.MapGrpcService(); 98 | 99 | Task.Factory.StartNew(() => app.Run(grpcSettings.GridBotGrpcServerEndpoint), TaskCreationOptions.LongRunning); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /services/grid-bot/lib/grpc/Grid.Bot.Grpc.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | gRPC server exposed by the mfdlabs grid bot. 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /services/grid-bot/lib/grpc/Implementation/GridBotGrpcServer.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | 3 | namespace Grid.Bot.Grpc; 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | using Prometheus; 10 | 11 | using Discord; 12 | using Discord.WebSocket; 13 | 14 | using V1; 15 | 16 | /// 17 | /// The Grid Bot gRPC server implementation. 18 | /// 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The instance. 23 | /// Thrown when the is null. 24 | public class GridBotGrpcServer(DiscordShardedClient client) : GridBotAPI.GridBotAPIBase 25 | { 26 | private readonly DiscordShardedClient _client = client ?? throw new ArgumentNullException(nameof(client)); 27 | 28 | private static readonly Counter _grpcServerRequestCounter = Metrics.CreateCounter( 29 | "grpc_health_check_requests_total", 30 | "Total number of gRPC health check requests" 31 | ); 32 | 33 | /// 34 | public override Task CheckHealth(CheckHealthRequest request, ServerCallContext context) 35 | { 36 | _grpcServerRequestCounter.Inc(); 37 | 38 | var response = new CheckHealthResponse(); 39 | 40 | if (_client.LoginState == LoginState.LoggedOut || _client.LoginState == LoginState.LoggingOut || _client.LoginState == LoginState.LoggingIn) 41 | return Task.FromResult(response); 42 | 43 | try 44 | { 45 | response.Status = _client.Status.ToString(); 46 | response.Latency = _client.Latency; 47 | response.Shards.AddRange(_client.Shards.Select(x => x.ShardId.ToString()).ToList()); 48 | } 49 | catch (Exception) 50 | { 51 | response.Status = "error"; 52 | response.Latency = 0; 53 | } 54 | 55 | return Task.FromResult(response); 56 | } 57 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/EnvironmentProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | /// 6 | /// Provides the environment name for the settings provider. 7 | /// 8 | public static class EnvironmentProvider 9 | { 10 | private const string _providerEnvironmentNameEnvVar = "ENVIRONMENT"; 11 | private const string _nomadEnvironmentMetadataEnvVar = $"NOMAD_META_{_providerEnvironmentNameEnvVar}"; 12 | 13 | private const string _defaultEnvironmentName = "development"; 14 | private static readonly string _providerEnvironmentName = 15 | Environment.GetEnvironmentVariable(_providerEnvironmentNameEnvVar) 16 | ?? Environment.GetEnvironmentVariable(_nomadEnvironmentMetadataEnvVar) 17 | ?? _defaultEnvironmentName; 18 | 19 | /// 20 | /// Gets the environment name for the settings provider. 21 | /// 22 | public static string EnvironmentName => _providerEnvironmentName; 23 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/BacktraceSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | /// 4 | /// Settings provider for all Backtrace related stuff. 5 | /// 6 | public class BacktraceSettings : BaseSettingsProvider 7 | { 8 | /// 9 | public override string Path => SettingsProvidersDefaults.BacktracePath; 10 | 11 | /// 12 | /// Gets the percentage to upload log files to Backtrace. 13 | /// 14 | public int UploadLogFilesToBacktraceEnabledPercent => GetOrDefault( 15 | nameof(UploadLogFilesToBacktraceEnabledPercent), 16 | 100 17 | ); 18 | 19 | /// 20 | /// Gets the url for Backtrace. 21 | /// 22 | public string BacktraceUrl => GetOrDefault( 23 | nameof(BacktraceUrl), 24 | "http://mfdlabs.sp.backtrace.io:6097" 25 | ); 26 | 27 | /// 28 | /// Gets the backtrace token. 29 | /// 30 | public string BacktraceToken => GetOrDefault( 31 | nameof(BacktraceToken), 32 | "9f55e8c1e2a0bc06f874371f33d7c24e38b88c48f3c3cd853fc95174a13beb9b" 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/BaseSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System.Collections.Generic; 4 | 5 | using Configuration; 6 | 7 | /// 8 | /// Base provider class for grid operations. 9 | /// 10 | public abstract class BaseSettingsProvider : VaultProvider 11 | { 12 | /// 13 | public override string Mount => SettingsProvidersDefaults.MountPath; 14 | 15 | /// 16 | /// Construct a new instance of 17 | /// 18 | protected BaseSettingsProvider() 19 | : base(Logging.Logger.Singleton) 20 | { 21 | } 22 | 23 | /// 24 | /// Gets the raw values associated with this settings provider. 25 | /// 26 | /// Te raw values. 27 | public IDictionary GetRawValues() => _CachedValues; 28 | } 29 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/ClientSettingsSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Settings provider for client-settings 8 | /// 9 | public class ClientSettingsSettings : BaseSettingsProvider 10 | { 11 | /// 12 | public override string Path => SettingsProvidersDefaults.ClientSettingsPath; 13 | 14 | 15 | /// 16 | /// Determines if data access should be via Vault or local JSON files. 17 | /// 18 | public bool ClientSettingsViaVault => GetOrDefault(nameof(ClientSettingsViaVault), false); 19 | 20 | /// 21 | /// Gets the Vault mount for the client settings. 22 | /// 23 | public string ClientSettingsVaultMount => GetOrDefault(nameof(ClientSettingsVaultMount), "client-settings"); 24 | 25 | /// 26 | /// Gets the absolute path to the client settings vault path. 27 | /// 28 | public string ClientSettingsVaultPath => GetOrDefault(nameof(ClientSettingsVaultPath), "/"); 29 | 30 | /// 31 | /// Gets the Vault address for the client settings. 32 | /// 33 | public string ClientSettingsVaultAddress => GetOrDefault(nameof(ClientSettingsVaultAddress), Environment.GetEnvironmentVariable("VAULT_ADDR")); 34 | 35 | /// 36 | /// Gets the Vault token for the client settings. 37 | /// 38 | public string ClientSettingsVaultToken => GetOrDefault(nameof(ClientSettingsVaultToken), Environment.GetEnvironmentVariable("VAULT_TOKEN")); 39 | 40 | /// 41 | /// Gets the refresh interval for the client settings factory. 42 | /// 43 | public TimeSpan ClientSettingsRefreshInterval => GetOrDefault(nameof(ClientSettingsRefreshInterval), TimeSpan.FromSeconds(30)); 44 | 45 | /// 46 | /// Gets the absolute path to the client settings configuration file if is false. 47 | /// 48 | public string ClientSettingsFilePath => GetOrDefault(nameof(ClientSettingsFilePath), "/var/cache/mfdlabs/client-settings.json"); 49 | 50 | /// 51 | /// Resolves the dependency maps for specific application settings. 52 | /// 53 | /// 54 | /// The value is formatted like this: 55 | /// 56 | /// Group1=Group2,Group3 57 | /// Group2=Group4 58 | /// 59 | /// 60 | public Dictionary ClientSettingsApplicationDependencies => GetOrDefault(nameof(ClientSettingsApplicationDependencies), new Dictionary()); 61 | 62 | /// 63 | /// A list of application names that can be read from the API endpoints. 64 | /// 65 | public string[] PermissibleReadApplications 66 | { 67 | get => GetOrDefault(nameof(PermissibleReadApplications), Array.Empty()); 68 | set => Set(nameof(PermissibleReadApplications), value); 69 | } 70 | 71 | /// 72 | /// Gets a command seperated list of API keys that can be used for reading non-permissable applications, 73 | /// as well as executing privalaged commands. 74 | /// 75 | /// If this is empty, it will leave endpoints open!!! 76 | public string[] ClientSettingsApiKeys => GetOrDefault(nameof(ClientSettingsApiKeys), Array.Empty()); 77 | 78 | } 79 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/CommandsSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | /// 6 | /// Settings provider for all commands related stuff. 7 | /// 8 | public class CommandsSettings : BaseSettingsProvider 9 | { 10 | /// 11 | public override string Path => SettingsProvidersDefaults.CommandsPath; 12 | 13 | /// 14 | /// Determines whether or not lockdown commands are enabled. 15 | /// 16 | public bool EnableLockdownCommands => GetOrDefault( 17 | nameof(EnableLockdownCommands), 18 | true 19 | ); 20 | 21 | /// 22 | /// Gets the ID of the guild to use for lockdown commands. 23 | /// 24 | public ulong LockdownGuildId => GetOrDefault( 25 | nameof(LockdownGuildId), 26 | 0UL 27 | ); 28 | 29 | /// 30 | /// Gets the command prefix. 31 | /// 32 | public string Prefix => GetOrDefault( 33 | nameof(Prefix), 34 | ">" 35 | ); 36 | 37 | /// 38 | /// The text to respond with when commands are disabled and the is true. 39 | /// 40 | public string TextCommandsDisabledWarningText => GetOrDefault( 41 | nameof(TextCommandsDisabledWarningText), 42 | string.Empty 43 | ); 44 | 45 | /// 46 | /// Determines whether or not text based commands should be enabled. 47 | /// 48 | /// 49 | /// If this is disabled, the commands module will not be loaded, therefore this cannot 50 | /// be used to dynamically load commands. 51 | /// 52 | /// If you wish to consume commands, then this must be enabled before the last shard 53 | /// within the connection pool has completed its initialization phase. 54 | /// 55 | /// If is false and this is false, then the bot 56 | /// will not respond to any text based commands whatever. Otherwise, it will respond with the text 57 | /// defined in on each subsequent attempt of text 58 | /// usage. 59 | /// 60 | public bool EnableTextCommands => GetOrDefault( 61 | nameof(EnableTextCommands), 62 | true 63 | ); 64 | 65 | /// 66 | /// Determines whether or not the bot should warn the user when commands are disabled. 67 | /// 68 | /// 69 | /// This is only applicable when is false. 70 | /// 71 | public bool ShouldWarnWhenCommandsAreDisabled => GetOrDefault( 72 | nameof(ShouldWarnWhenCommandsAreDisabled), 73 | true 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/ConsulSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | using Configuration; 6 | 7 | using IServiceDiscoverySettings = global::ServiceDiscovery.ISettings; 8 | 9 | /// 10 | /// Settings provider for all Consul related stuff. 11 | /// 12 | public class ConsulSettings : BaseSettingsProvider, IServiceDiscoverySettings 13 | { 14 | /// 15 | public override string Path => SettingsProvidersDefaults.ConsulPath; 16 | 17 | /// 18 | [SettingName("CONSUL_ADDR")] 19 | public string ConsulAddress => GetOrDefault( 20 | "CONSUL_ADDR", // Accom for local ENV var. 21 | "http://127.0.0.1:8500" 22 | ); 23 | 24 | /// 25 | public TimeSpan ConsulBackoffBase => GetOrDefault( 26 | nameof(ConsulBackoffBase), 27 | TimeSpan.FromMilliseconds(1) 28 | ); 29 | 30 | /// 31 | public TimeSpan ConsulLongPollingMaxWaitTime => GetOrDefault( 32 | nameof(ConsulLongPollingMaxWaitTime), 33 | TimeSpan.FromMinutes(5) 34 | ); 35 | 36 | /// 37 | public TimeSpan MaximumConsulBackoff => GetOrDefault( 38 | nameof(MaximumConsulBackoff), 39 | TimeSpan.FromSeconds(30) 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/DiscordRolesSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | /// 6 | /// Settings provider for all Discord roles related stuff. 7 | /// 8 | public class DiscordRolesSettings : BaseSettingsProvider 9 | { 10 | /// 11 | public override string Path => SettingsProvidersDefaults.DiscordRolesPath; 12 | 13 | /// 14 | /// Gets or sets the admin user ids. 15 | /// 16 | public ulong[] AdminUserIds 17 | { 18 | get => GetOrDefault(nameof(AdminUserIds), Array.Empty()); 19 | set => Set(nameof(AdminUserIds), value); 20 | } 21 | 22 | /// 23 | /// Gets or sets the blacklisted user ids. 24 | /// 25 | public ulong[] BlacklistedUserIds 26 | { 27 | get => GetOrDefault(nameof(BlacklistedUserIds), Array.Empty()); 28 | set => Set(nameof(BlacklistedUserIds), value); 29 | } 30 | 31 | /// 32 | /// Gets or sets the higher privilaged user ids. 33 | /// 34 | public ulong[] HigherPrivilagedUserIds 35 | { 36 | get => GetOrDefault(nameof(HigherPrivilagedUserIds), Array.Empty()); 37 | set => Set(nameof(HigherPrivilagedUserIds), value); 38 | } 39 | 40 | /// 41 | /// Gets the bot owner id. 42 | /// 43 | public ulong BotOwnerId => GetOrDefault(nameof(BotOwnerId), default(ulong)); 44 | 45 | /// 46 | /// Gets the ID of the role that is used to create notifications for alerts. 47 | /// 48 | public ulong AlertRoleId => GetOrDefault(nameof(AlertRoleId), default(ulong)); 49 | } 50 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/DiscordSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | using Discord; 6 | using Logging; 7 | 8 | /// 9 | /// Settings provider for all Discord related stuff. 10 | /// 11 | public class DiscordSettings : BaseSettingsProvider 12 | { 13 | /// 14 | public override string Path => SettingsProvidersDefaults.DiscordPath; 15 | 16 | /// 17 | /// Gets the bot token. 18 | /// 19 | /// The setting is required! 20 | public string BotToken => GetOrDefault( 21 | nameof(BotToken), 22 | #if DEBUG 23 | string.Empty 24 | #else 25 | () => throw new InvalidOperationException($"Environment Variable {nameof(BotToken)} is required!") 26 | #endif 27 | ); 28 | 29 | #if DEBUG || DEBUG_LOGGING_IN_PROD 30 | /// 31 | /// Can task cancelled exceptions be loggeD? 32 | /// 33 | public bool DebugAllowTaskCanceledExceptions => GetOrDefault( 34 | nameof(DebugAllowTaskCanceledExceptions), 35 | false 36 | ); 37 | #endif 38 | 39 | #if DEBUG 40 | 41 | /// 42 | /// Gets the ID of the guild to register slash commands in. 43 | /// 44 | /// 45 | /// This is only valid for debug builds. 46 | /// If this is 0, then the bot will not register slash commands. 47 | /// 48 | public ulong DebugGuildId => GetOrDefault( 49 | nameof(DebugGuildId), 50 | 0UL 51 | ); 52 | 53 | /// 54 | /// Determines if the bot should not be enabled. 55 | /// 56 | /// 57 | /// This is only valid for debug builds. 58 | /// If this is true, then the bot will not be enabled. 59 | /// 60 | public bool DebugBotDisabled => GetOrDefault( 61 | nameof(DebugBotDisabled), 62 | false 63 | ); 64 | 65 | #endif 66 | 67 | /// 68 | /// Should discord internals be logged? 69 | /// 70 | public bool ShouldLogDiscordInternals => GetOrDefault( 71 | nameof(ShouldLogDiscordInternals), 72 | true 73 | ); 74 | 75 | /// 76 | /// Gets the of the bot. 77 | /// 78 | public UserStatus BotStatus => GetOrDefault( 79 | nameof(BotStatus), 80 | UserStatus.Online 81 | ); 82 | 83 | /// 84 | /// Gets the status message of the bot. 85 | /// 86 | public string BotStatusMessage => GetOrDefault( 87 | nameof(BotStatusMessage), 88 | string.Empty 89 | ); 90 | 91 | /// 92 | /// Gets the name of the 93 | /// 94 | public string DiscordLoggerName => GetOrDefault( 95 | nameof(DiscordLoggerName), 96 | "discord" 97 | ); 98 | 99 | /// 100 | /// Gets the of the 101 | /// 102 | public LogLevel DiscordLoggerLogLevel => GetOrDefault( 103 | nameof(DiscordLoggerLogLevel), 104 | LogLevel.Information 105 | ); 106 | 107 | /// 108 | /// Should the log to console? 109 | /// 110 | public bool DiscordLoggerLogToConsole => GetOrDefault( 111 | nameof(DiscordLoggerLogToConsole), 112 | true 113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/GlobalSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | using Configuration; 6 | 7 | using Logging; 8 | 9 | /// 10 | /// Settings provider for global entrypoint stuff. 11 | /// 12 | public class GlobalSettings : BaseSettingsProvider 13 | { 14 | /// 15 | public override string Path => SettingsProvidersDefaults.GlobalPath; 16 | 17 | /// 18 | /// Gets the name of the default logger. 19 | /// 20 | public string DefaultLoggerName => GetOrDefault( 21 | nameof(DefaultLoggerName), 22 | "bot" 23 | ); 24 | 25 | /// 26 | /// Gets the log level for the default logger. 27 | /// 28 | public LogLevel DefaultLoggerLevel => GetOrDefault( 29 | nameof(DefaultLoggerLevel), 30 | LogLevel.Information 31 | ); 32 | 33 | /// 34 | /// Gets the port for the metrics server. 35 | /// 36 | public int MetricsPort => GetOrDefault( 37 | nameof(MetricsPort), 38 | 8081 39 | ); 40 | 41 | /// 42 | /// Should the default logger log to console? 43 | /// 44 | public bool DefaultLoggerLogToConsole => GetOrDefault( 45 | nameof(DefaultLoggerLogToConsole), 46 | true 47 | ); 48 | 49 | /// 50 | /// Gets the Discord URL for the primary support guild. 51 | /// 52 | public string SupportGuildDiscordUrl => GetOrDefault( 53 | nameof(SupportGuildDiscordUrl), 54 | "https://discord.gg/hdg2z6bm5c" 55 | ); 56 | 57 | /// 58 | /// Gets the GitHub url for the support hub. 59 | /// 60 | public string SupportHubGitHubUrl => GetOrDefault( 61 | nameof(SupportHubGitHubUrl), 62 | "https://github.com/mfdlabs/grid-bot-support" 63 | ); 64 | 65 | /// 66 | /// Gets the Url for the documentation hub. 67 | /// 68 | public string DocumentationHubUrl => GetOrDefault( 69 | nameof(DocumentationHubUrl), 70 | "https://grid-bot.ops.vmminfra.net" 71 | ); 72 | 73 | /// 74 | /// Is alerting via Discord webhook enabled? 75 | /// 76 | public bool DiscordWebhookAlertingEnabled => GetOrDefault( 77 | nameof(DiscordWebhookAlertingEnabled), 78 | false 79 | ); 80 | 81 | /// 82 | /// Gets the Discord webhook URL for alerts. 83 | /// 84 | public string DiscordWebhookUrl => GetOrDefault( 85 | nameof(DiscordWebhookUrl), 86 | string.Empty 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/GrpcSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | using Logging; 6 | 7 | /// 8 | /// Settings provider for all gRPC Server related stuff. 9 | /// 10 | public class GrpcSettings : BaseSettingsProvider 11 | { 12 | /// 13 | public override string Path => SettingsProvidersDefaults.GrpcPath; 14 | 15 | /// 16 | /// Determines if the grid-bot grpc server should be enabled or not. 17 | /// 18 | public bool GridBotGrpcServerEnabled => GetOrDefault( 19 | nameof(GridBotGrpcServerEnabled), 20 | false 21 | ); 22 | 23 | /// 24 | /// Gets the endpoint for the Grid Bot gRPC server. 25 | /// 26 | public string GridBotGrpcServerEndpoint => GetOrDefault( 27 | nameof(GridBotGrpcServerEndpoint), 28 | "http://+:5000" 29 | ); 30 | 31 | /// 32 | /// ASP.NET Core logger name for the gRPC server. 33 | /// 34 | public string GrpcServerLoggerName => GetOrDefault( 35 | nameof(GrpcServerLoggerName), 36 | "grpc" 37 | ); 38 | 39 | /// 40 | /// ASP.NET Core logger level for the gRPC server. 41 | /// 42 | public LogLevel GrpcServerLoggerLevel => GetOrDefault( 43 | nameof(GrpcServerLoggerLevel), 44 | LogLevel.Information 45 | ); 46 | 47 | /// 48 | /// Determines if the gRPC server should use TLS. 49 | /// 50 | public bool GrpcServerUseTls => GetOrDefault( 51 | nameof(GrpcServerUseTls), 52 | true 53 | ); 54 | 55 | /// 56 | /// Gets the certificate path for the gRPC server. 57 | /// 58 | public string GrpcServerCertificatePath => GetOrDefault( 59 | nameof(GrpcServerCertificatePath), 60 | () => throw new InvalidOperationException($"'{nameof(GrpcServerCertificatePath)}' is required when '{nameof(GrpcServerUseTls)}' is true.") 61 | ); 62 | 63 | /// 64 | /// Gets the certificate password for the gRPC server. 65 | /// 66 | public string GrpcServerCertificatePassword => GetOrDefault( 67 | nameof(GrpcServerCertificatePassword), 68 | () => throw new InvalidOperationException($"'{nameof(GrpcServerCertificatePassword)}' is required when '{nameof(GrpcServerUseTls)}' is true.") 69 | ); 70 | 71 | } 72 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/MaintenanceSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | /// 4 | /// Settings provider for all maintenance related stuff. 5 | /// 6 | public class MaintenanceSettings : BaseSettingsProvider 7 | { 8 | /// 9 | public override string Path => SettingsProvidersDefaults.MaintenancePath; 10 | 11 | /// 12 | /// Is maintenance enabled? 13 | /// 14 | public bool MaintenanceEnabled 15 | { 16 | get => GetOrDefault(nameof(MaintenanceEnabled), false); 17 | set => Set(nameof(MaintenanceEnabled), value); 18 | } 19 | 20 | /// 21 | /// Gets or sets the maintenance status message. 22 | /// 23 | public string MaintenanceStatus 24 | { 25 | get => GetOrDefault(nameof(MaintenanceStatus), string.Empty); 26 | set => Set(nameof(MaintenanceStatus), value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/ScriptsSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | /// 6 | /// Settings provider for all script execution related stuff. 7 | /// 8 | public class ScriptsSettings : BaseSettingsProvider 9 | { 10 | /// 11 | public override string Path => SettingsProvidersDefaults.ScriptsPath; 12 | 13 | /// 14 | /// Gets the max size for a script used by the Execute Script commands in KiB. 15 | /// 16 | public int ScriptExecutionMaxFileSizeKb => GetOrDefault( 17 | nameof(ScriptExecutionMaxFileSizeKb), 18 | 75 19 | ); 20 | 21 | /// 22 | /// Gets the max size for the result file used by the Execute Script command in KiB. 23 | /// 24 | public int ScriptExecutionMaxResultSizeKb => GetOrDefault( 25 | nameof(ScriptExecutionMaxResultSizeKb), 26 | 50 27 | ); 28 | 29 | /// 30 | /// Determines if the LuaVM is enabled. 31 | /// 32 | public bool LuaVMEnabled => GetOrDefault( 33 | nameof(LuaVMEnabled), 34 | true 35 | ); 36 | 37 | /// 38 | /// Gets the percentage to use for logging scripts. 39 | /// 40 | public int ScriptLoggingPercentage => GetOrDefault( 41 | nameof(ScriptLoggingPercentage), 42 | 0 43 | ); 44 | 45 | /// 46 | /// Gets a Discord webhook URL to send script logs to. 47 | /// 48 | public string ScriptLoggingDiscordWebhookUrl => GetOrDefault( 49 | nameof(ScriptLoggingDiscordWebhookUrl), 50 | string.Empty 51 | ); 52 | 53 | /// 54 | /// Gets or sets a list of hashes of scripts that have already been logged and should not be logged again. 55 | /// 56 | public string[] LoggedScriptHashes 57 | { 58 | get => GetOrDefault( 59 | nameof(LoggedScriptHashes), 60 | Array.Empty() 61 | ); 62 | set => Set(nameof(LoggedScriptHashes), value); 63 | } 64 | 65 | /// 66 | /// Gets the interval to persist the logged script hashes. 67 | /// 68 | public TimeSpan LoggedScriptHashesPersistInterval => GetOrDefault( 69 | nameof(LoggedScriptHashesPersistInterval), 70 | TimeSpan.FromMinutes(5) 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/UsersClientSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | /// 4 | /// Settings provider for settings used by the Roblox Users API. 5 | /// 6 | public class UsersClientSettings : BaseSettingsProvider 7 | { 8 | /// 9 | public override string Path => SettingsProvidersDefaults.UsersClientPath; 10 | 11 | /// 12 | /// Gets the base url for the Users ApiSite. 13 | /// 14 | public string UsersApiBaseUrl => GetOrDefault( 15 | nameof(UsersApiBaseUrl), 16 | "https://users.roblox.com" 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Providers/WebSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | using Logging; 7 | 8 | /// 9 | /// Settings provider for all Web Server related stuff. 10 | /// 11 | public class WebSettings : BaseSettingsProvider 12 | { 13 | /// 14 | public override string Path => SettingsProvidersDefaults.WebPath; 15 | 16 | /// 17 | /// Determines if the web server should be enabled. 18 | /// 19 | public bool IsWebServerEnabled => GetOrDefault(nameof(IsWebServerEnabled), true); 20 | 21 | /// 22 | /// Gets the bind address for the web server. 23 | /// 24 | public string WebServerBindAddress => GetOrDefault(nameof(WebServerBindAddress), "http://+:8888"); 25 | 26 | /// 27 | /// Determines if the web server is behind a reverse proxy. 28 | /// 29 | /// 30 | /// If true, then x-forwarded-for and x-forwarded-proto headers will be used to determine the client IP address. 31 | /// 32 | public bool IsWebServerBehindProxy => GetOrDefault(nameof(IsWebServerBehindProxy), false); 33 | 34 | /// 35 | /// Gets the list of allowed proxy networks. 36 | /// 37 | public string[] WebServerAllowedProxyRanges => GetOrDefault(nameof(WebServerAllowedProxyRanges), new[] { "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" }); 38 | 39 | /// 40 | /// Determines if the web server should use TLS. 41 | /// 42 | /// 43 | /// This should be set to true always in post-2017 grid servers 44 | /// if this is the standard web server. 45 | /// 46 | public bool WebServerUseTls => GetOrDefault(nameof(WebServerUseTls), false); 47 | 48 | /// 49 | /// Gets the path to the PFX certificate for the web server. 50 | /// 51 | public string WebServerCertificatePath => GetOrDefault( 52 | nameof(WebServerCertificatePath), 53 | () => throw new InvalidOperationException($"'{nameof(WebServerCertificatePath)}' is required when '{nameof(WebServerUseTls)}' is true.") 54 | ); 55 | 56 | /// 57 | /// Gets the password for the PFX certificate for the web server. 58 | /// 59 | public string WebServerCertificatePassword => GetOrDefault( 60 | nameof(WebServerCertificatePassword), 61 | () => throw new InvalidOperationException($"'{nameof(WebServerCertificatePassword)}' is required when '{nameof(WebServerUseTls)}' is true.") 62 | ); 63 | 64 | /// 65 | /// Gets the ASP.NET Core logger name for the web server. 66 | /// 67 | public string WebServerLoggerName => GetOrDefault(nameof(WebServerLoggerName), "web"); 68 | 69 | /// 70 | /// Gets the ASP.NET Core logger level for the web server. 71 | /// 72 | public LogLevel WebServerLoggerLevel => GetOrDefault(nameof(WebServerLoggerLevel), LogLevel.Information); 73 | } 74 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/SettingsProvidersDefaults.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | internal static class SettingsProvidersDefaults 6 | { 7 | private const string _vaultMountEnvVar = "VAULT_MOUNT"; 8 | 9 | public static string DiscordPath => $"{EnvironmentProvider.EnvironmentName}/discord"; 10 | public static string DiscordRolesPath => $"{EnvironmentProvider.EnvironmentName}/discord-roles"; 11 | public static string AvatarPath => $"{EnvironmentProvider.EnvironmentName}/avatar"; 12 | public static string GridPath => $"{EnvironmentProvider.EnvironmentName}/grid"; 13 | public static string BacktracePath => $"{EnvironmentProvider.EnvironmentName}/backtrace"; 14 | public static string MaintenancePath => $"{EnvironmentProvider.EnvironmentName}/maintenance"; 15 | public static string CommandsPath => $"{EnvironmentProvider.EnvironmentName}/commands"; 16 | public static string FloodCheckerPath => $"{EnvironmentProvider.EnvironmentName}/floodcheckers"; 17 | public static string ConsulPath => $"{EnvironmentProvider.EnvironmentName}/consul"; 18 | public static string UsersClientPath => $"{EnvironmentProvider.EnvironmentName}/users-client"; 19 | public static string ScriptsPath => $"{EnvironmentProvider.EnvironmentName}/scripts"; 20 | public static string ClientSettingsPath => $"{EnvironmentProvider.EnvironmentName}/client-settings"; 21 | public static string GlobalPath => $"{EnvironmentProvider.EnvironmentName}/global"; 22 | public static string WebPath => $"{EnvironmentProvider.EnvironmentName}/web"; 23 | public static string GrpcPath => $"{EnvironmentProvider.EnvironmentName}/grpc"; 24 | 25 | public const string DefaultMountPath = "grid-bot-settings"; 26 | public static string MountPath = Environment.GetEnvironmentVariable(_vaultMountEnvVar) ?? DefaultMountPath; 27 | } 28 | -------------------------------------------------------------------------------- /services/grid-bot/lib/settings/Shared.Settings.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Shared library containing settings used by the grid-bot 4 | 5 | true 6 | $(NoWarn);1591 7 | 8 | 9 | 10 | $(DefineConstants);DEBUG_LOGGING_IN_PROD 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Enums/BotRole.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | /// 4 | /// Bot role. 5 | /// 6 | public enum BotRole 7 | { 8 | /// 9 | /// Default role. Used for everyone. 10 | /// 11 | Default, 12 | 13 | /// 14 | /// A privileged role. Mostly used for testers. 15 | /// 16 | Privileged, 17 | 18 | /// 19 | /// Administrator role. Used for administrators. 20 | /// 21 | Administrator, 22 | 23 | /// 24 | /// Owner role. Used for the owner of the bot. 25 | /// 26 | Owner 27 | } 28 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Enums/FilterType.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | /// 4 | /// Type of a client setting filter. 5 | /// 6 | public enum FilterType 7 | { 8 | /// 9 | /// The filter is filtering places. _PlaceFilter 10 | /// 11 | Place, 12 | 13 | /// 14 | /// The filter is filtering datacenters. _DataCenterFilter 15 | /// 16 | DataCenter 17 | } 18 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Enums/SettingType.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | /// 4 | /// The type of a setting. 5 | /// 6 | public enum SettingType 7 | { 8 | /// 9 | /// String type setting -- default. 10 | /// 11 | String, 12 | 13 | /// 14 | /// Boolean type setting -- a flag. 15 | /// 16 | Bool, 17 | 18 | /// 19 | /// Integer type setting. 20 | /// 21 | Int 22 | } 23 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Extensions; 2 | 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Extension methods for Dictionary. 8 | /// 9 | public static class DictionaryExtensions 10 | { 11 | /// 12 | /// Merges the current dictionary with others, left to right. 13 | /// If a key exists in multiple dictionaries, the value from the last one will be used. 14 | /// 15 | /// The type of the dictionary. 16 | /// The type of the key. 17 | /// The type of the value. 18 | /// The current dictionary. 19 | /// The dictionaries to merge with. 20 | /// A new dictionary containing the merged key-value pairs. 21 | public static T MergeLeft(this T me, params IDictionary[] others) 22 | where T : IDictionary, new() 23 | { 24 | var newMap = new T(); 25 | 26 | foreach (IDictionary src in new List> { me }.Concat(others)) 27 | { 28 | // ^-- echk. Not quite there type-system. 29 | foreach (KeyValuePair p in src) 30 | { 31 | newMap[p.Key] = p.Value; 32 | } 33 | } 34 | 35 | return newMap; 36 | } 37 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Extensions/IAttachmentExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Extensions; 2 | 3 | using System.Text; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | using Discord; 8 | 9 | /// 10 | /// Extension methods for 11 | /// 12 | public static class IAttachmentExtensions 13 | { 14 | /// 15 | /// Gets the data for the 16 | /// 17 | /// The 18 | /// The raw bytes of the 19 | public static async Task GetRawAttachmentBuffer(this IAttachment attachment) 20 | { 21 | using var client = new HttpClient(); 22 | 23 | return await client.GetByteArrayAsync(attachment.Url); 24 | } 25 | 26 | /// 27 | /// Gets the data from the and returns an ASCII string. 28 | /// 29 | /// The 30 | /// The raw string. 31 | public static async Task GetAttachmentContentsAscii(this IAttachment attachment) 32 | => Encoding.ASCII.GetString(await attachment.GetRawAttachmentBuffer()); 33 | } 34 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Extensions/IUserMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Extensions; 2 | 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | 6 | using Discord; 7 | 8 | /// 9 | /// Extension methods for 10 | /// 11 | public static class IUserMessageExtensions 12 | { 13 | /// 14 | /// Sends an inline reply that references a message. 15 | /// 16 | /// The message that is being replied on. 17 | /// The stream of the file to send. 18 | /// The name of the file to send. 19 | /// The message to be sent. 20 | /// Determines whether the message should be read aloud by Discord or not. 21 | /// The to be sent. 22 | /// A array of s to send with this response. Max 10. 23 | /// 24 | /// Specifies if notifications are sent for mentioned users and roles in the message . 25 | /// If , all mentioned roles and users will be notified. 26 | /// 27 | /// The options to be used when sending the request. 28 | /// Determines whether the file should be sent as a spoiler or not. 29 | /// The message components to be included with this message. Used for interactions. 30 | /// A collection of stickers to send with the message. 31 | /// Message flags combined as a bitfield. 32 | /// 33 | /// A task that represents an asynchronous send operation for delivering the message. The task result 34 | /// contains the sent message. 35 | /// 36 | public static Task ReplyWithFileAsync( 37 | this IUserMessage msg, 38 | Stream fileStream, 39 | string fileName, 40 | string text = null, 41 | bool isTTS = false, 42 | Embed embed = null, 43 | AllowedMentions allowedMentions = null, 44 | RequestOptions options = null, 45 | bool isSpoiler = false, 46 | MessageComponent components = null, 47 | ISticker[] stickers = null, 48 | Embed[] embeds = null, 49 | MessageFlags flags = MessageFlags.None 50 | ) 51 | => msg.Channel.SendFileAsync( 52 | fileStream, 53 | fileName, 54 | text, 55 | isTTS, 56 | embed, 57 | options, 58 | isSpoiler, 59 | allowedMentions, 60 | new MessageReference(messageId: msg.Id), 61 | components, 62 | stickers, 63 | embeds, 64 | flags 65 | ); 66 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Extensions/SocketInteractionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Extensions; 2 | 3 | using Discord; 4 | using Discord.WebSocket; 5 | 6 | using Threading.Extensions; 7 | 8 | /// 9 | /// Extension methods for 10 | /// 11 | public static class SocketInteractionExtensions 12 | { 13 | /// 14 | /// Gets the from the , taking private threads into consideration. 15 | /// 16 | /// The current 17 | /// An 18 | public static IMessageChannel GetChannel(this SocketInteraction interaction) 19 | => interaction.Channel ?? interaction.InteractionChannel; 20 | 21 | /// 22 | /// Gets the channel from the , taking private threads into consideration. 23 | /// 24 | /// The current 25 | /// A string version of either or 26 | public static string GetChannelAsString(this SocketInteraction interaction) 27 | => interaction.GetChannel().ToString(); 28 | 29 | /// 30 | /// Gets an for a specific , taking private threads into consideration. 31 | /// 32 | /// 33 | /// 34 | /// 35 | public static IGuild GetGuild(this SocketInteraction interaction, IDiscordClient client) 36 | { 37 | if (interaction.GuildId == null) return null; 38 | 39 | if (interaction.Channel is SocketGuildChannel guildChannel) 40 | return guildChannel.Guild; 41 | 42 | return client.GetGuildAsync(interaction.GuildId.Value).SyncOrDefault(); 43 | } 44 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/BacktraceUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | using Prometheus; 9 | 10 | using Backtrace; 11 | using Backtrace.Model; 12 | using Backtrace.Interfaces; 13 | 14 | using Logging; 15 | using FileSystem; 16 | 17 | /// 18 | /// Utility for interacting with Backtrace. 19 | /// 20 | public class BacktraceUtility : IBacktraceUtility 21 | { 22 | private readonly ILogger _logger; 23 | private readonly BacktraceClient _client; 24 | 25 | private static readonly Counter _uploadExceptionCounter = Metrics.CreateCounter( 26 | "backtrace_exception_upload_total", 27 | "Total number of exceptions uploaded to Backtrace" 28 | ); 29 | 30 | /// 31 | /// Construct a new instance of . 32 | /// 33 | /// The . 34 | /// The . 35 | /// 36 | /// - cannot be null. 37 | /// - cannot be null. 38 | /// 39 | public BacktraceUtility(ILogger logger, BacktraceSettings backtraceSettings) 40 | { 41 | if (backtraceSettings == null) 42 | throw new ArgumentNullException(nameof(backtraceSettings)); 43 | 44 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 45 | 46 | if (!string.IsNullOrEmpty(backtraceSettings.BacktraceUrl)) 47 | { 48 | var bckTraceCreds = new BacktraceCredentials( 49 | backtraceSettings.BacktraceUrl, 50 | backtraceSettings.BacktraceToken 51 | ); 52 | 53 | _client = new BacktraceClient(bckTraceCreds); 54 | } 55 | } 56 | 57 | /// 58 | public IBacktraceClient Client => _client; 59 | 60 | /// 61 | public void UploadException(Exception ex) 62 | { 63 | if (ex == null) 64 | return; 65 | 66 | _uploadExceptionCounter.Inc(); 67 | 68 | Task.Factory.StartNew(() => 69 | { 70 | try 71 | { 72 | Console.WriteLine("Uploading exception to Backtrace..."); 73 | 74 | _client?.Send(ex); 75 | 76 | Console.WriteLine("Exception uploaded to Backtrace."); 77 | } 78 | catch (Exception e) 79 | { 80 | Console.WriteLine("Failed to upload exception to Backtrace: {0}", e); 81 | } 82 | }); 83 | } 84 | 85 | private static bool IsFileLocked(string filePath) 86 | { 87 | try 88 | { 89 | using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None); 90 | 91 | stream.Close(); 92 | } 93 | catch (IOException) 94 | { 95 | return true; 96 | } 97 | 98 | return false; 99 | } 100 | 101 | /// 102 | public BacktraceResult UploadAllLogFiles(bool delete = true) 103 | { 104 | var attachments = from file in Directory.EnumerateFiles(Logger.LogFileBaseDirectory) 105 | // Do not include files that are in use. 106 | where File.Exists(file) && !IsFileLocked(file) 107 | select file; 108 | 109 | if (!attachments.Any()) 110 | { 111 | _logger.Warning("No log files found to upload!"); 112 | 113 | return null; 114 | } 115 | 116 | _logger.Information("Uploading the following log files to Backtrace: {0}", string.Join(", ", from log in attachments select Path.GetFileName(log))); 117 | 118 | var result = _client?.Send("Log files upload", attachmentPaths: attachments.ToList()); 119 | 120 | if (delete) 121 | foreach (var log in attachments) 122 | { 123 | if (log == _logger.FullyQualifiedFileName) continue; 124 | 125 | _logger.Warning("Deleting old log file: {0}", log); 126 | 127 | log.PollDeletion(); 128 | } 129 | 130 | return result; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/ClientSettingsNameHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | using System.Text.RegularExpressions; 5 | 6 | /// 7 | /// Helper for client settings. 8 | /// 9 | public static partial class ClientSettingsNameHelper 10 | { 11 | [GeneratedRegex(@"^([DS])?F(Flag|Log|Int|String)", RegexOptions.Compiled)] 12 | public static partial Regex PrefixedSettingRegex(); 13 | 14 | /// 15 | /// Determines if the setting name is a filtered value setting. 16 | /// 17 | /// The name of the setting. 18 | /// True if the setting is a filtered setting, otherwise false. 19 | public static bool IsFilteredSetting(string name) 20 | => name.EndsWith(FilteredValue.PlaceFilterSuffix) || name.EndsWith(FilteredValue.DataCenterFilterSuffix); 21 | 22 | /// 23 | /// Extracts the filtered setting name and type from the given name. 24 | /// 25 | /// The name of the setting. 26 | /// A tuple containing the name of the setting and its type. 27 | public static (string name, FilterType type) ExtractFilteredSettingName(string name) 28 | { 29 | var type = FilterType.Place; 30 | 31 | if (name.EndsWith(FilteredValue.PlaceFilterSuffix)) 32 | { 33 | name = name[..^FilteredValue.PlaceFilterSuffix.Length]; 34 | } 35 | else if (name.EndsWith(FilteredValue.DataCenterFilterSuffix)) 36 | { 37 | type = FilterType.DataCenter; 38 | name = name[..^FilteredValue.DataCenterFilterSuffix.Length]; 39 | } 40 | 41 | return (name, type); 42 | } 43 | 44 | /// 45 | /// Gets the type of setting from its name. 46 | /// 47 | /// The name of the setting. 48 | /// The type of the setting. 49 | public static SettingType GetSettingTypeFromName(string name) 50 | { 51 | if (!PrefixedSettingRegex().IsMatch(name)) return SettingType.String; 52 | 53 | // F = flag, I = int, S = string, L = log (int) 54 | // e.g: 55 | // FFlagTest 56 | // 012345678 57 | // ^ 58 | // 59 | // DFFlagTest 60 | // 0123456789 61 | // ^ 62 | 63 | var prefix = name[0]; 64 | if (prefix != 'F') 65 | prefix = name[1..][1]; 66 | else 67 | prefix = name[1]; 68 | 69 | return prefix switch 70 | { 71 | 'F' => SettingType.Bool, // FFlag 72 | 'I' or 'L' => SettingType.Int, // FInt, FLog 73 | _ => SettingType.String, // FString 74 | }; 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/FilteredValue.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// Represents a filtered value. 9 | /// 10 | public struct FilteredValue 11 | { 12 | private const char _filterDelimiter = ';'; 13 | 14 | /// 15 | /// Suffix for place filters. 16 | /// 17 | public const string PlaceFilterSuffix = "_PlaceFilter"; 18 | 19 | /// 20 | /// Suffix for datacenter filters. 21 | /// 22 | public const string DataCenterFilterSuffix = "_DataCenterFilter"; 23 | 24 | /// 25 | /// Construct a new instance of 26 | /// 27 | public FilteredValue() 28 | { 29 | } 30 | 31 | /// 32 | /// Get or set the raw value. 33 | /// 34 | public T Value { get; set; } 35 | 36 | /// 37 | /// Gets or sets the name. 38 | /// 39 | public string Name { get; set; } 40 | 41 | /// 42 | /// Gets the type of the setting. 43 | /// 44 | public readonly SettingType Type 45 | { 46 | get 47 | { 48 | return Value switch 49 | { 50 | bool => SettingType.Bool, 51 | long => SettingType.Int, 52 | _ => SettingType.String, 53 | }; 54 | } 55 | } 56 | 57 | /// 58 | /// Gets or sets the type of filter. 59 | /// 60 | public FilterType FilterType { get; set; } 61 | 62 | /// 63 | /// Gets the filtered place IDs or datacenter IDs. 64 | /// 65 | public HashSet FilteredIds { get; private set; } = []; 66 | 67 | /// 68 | /// Implicit conversion of to 69 | /// 70 | /// The current 71 | public static implicit operator T(FilteredValue value) => value.Value; 72 | 73 | /// 74 | /// Converts the string representation of the filtered value to a filtered value. 75 | /// 76 | /// The raw name of the setting, used to determine the type of filter. 77 | /// The string value of the setting. 78 | /// A new filtered value. 79 | public static FilteredValue FromString(string name, string value) 80 | { 81 | if (!ClientSettingsNameHelper.IsFilteredSetting(name)) 82 | throw new ArgumentException($"The setting name does not end with {PlaceFilterSuffix} or {DataCenterFilterSuffix}!", nameof(name)); 83 | 84 | var filterType = name.EndsWith(PlaceFilterSuffix) 85 | ? FilterType.Place 86 | : FilterType.DataCenter; 87 | var settingName = filterType == FilterType.Place 88 | ? name[..^PlaceFilterSuffix.Length] 89 | : name[..^DataCenterFilterSuffix.Length]; 90 | var settingType = ClientSettingsNameHelper.GetSettingTypeFromName(name); 91 | 92 | var entries = value.Split(_filterDelimiter).Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList(); 93 | if (entries.Count == 0) throw new ArgumentException("Value had no entries!", nameof(value)); 94 | 95 | var settingValueRaw = entries.First(); 96 | var filteredIds = entries.Skip(1).Select(long.Parse); 97 | object settingValue = settingType switch 98 | { 99 | SettingType.Bool => bool.Parse(settingValueRaw), 100 | SettingType.Int => long.Parse(settingValueRaw), 101 | _ => settingValueRaw, 102 | }; 103 | 104 | return new FilteredValue 105 | { 106 | Name = settingName, 107 | FilterType = filterType, 108 | FilteredIds = [.. filteredIds], 109 | Value = (T)(object)settingValue.ToString() 110 | }; 111 | } 112 | 113 | /// 114 | /// Convert the filtered value to a string. 115 | /// 116 | /// The new name and the stringified value. 117 | public new readonly (string key, string value) ToString() 118 | { 119 | var name = $"{Name}_{FilterType}Filter"; 120 | ICollection values = [Value.ToString(), ..FilteredIds.Select(x => x.ToString())]; 121 | 122 | return (name, string.Join(_filterDelimiter, values)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/LazyWithRetry.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | 5 | /// 6 | /// Lazy with retry implementation 7 | /// 8 | public class LazyWithRetry 9 | { 10 | private readonly TimeSpan _TimeoutBetweenRetries = TimeSpan.FromSeconds(30); 11 | 12 | private Lazy _Lazy; 13 | 14 | private readonly object _Sync = new(); 15 | private readonly Func _ValueFactory; 16 | private readonly Func _NowGetter; 17 | 18 | private DateTime? _LastExceptionTimeStamp; 19 | 20 | /// 21 | public bool IsValueCreated => _Lazy.IsValueCreated; 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The value factory. 27 | /// The now getter. 28 | /// is . 29 | public LazyWithRetry(Func valueFactory, Func nowGetter = null) 30 | { 31 | _ValueFactory = valueFactory ?? throw new ArgumentNullException(nameof(valueFactory)); 32 | 33 | _Lazy = new Lazy(_ValueFactory); 34 | _NowGetter = nowGetter ?? (() => DateTime.UtcNow); 35 | } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// The value factory. 41 | /// The timeout between retries. 42 | /// The now getter. 43 | public LazyWithRetry(Func valueFactory, TimeSpan timeoutBetweenRetries, Func nowGetter = null) 44 | : this(valueFactory, nowGetter) 45 | { 46 | _TimeoutBetweenRetries = timeoutBetweenRetries; 47 | } 48 | 49 | /// 50 | public T LazyValue 51 | { 52 | get 53 | { 54 | try 55 | { 56 | return _Lazy.Value; 57 | } 58 | catch (Exception) 59 | { 60 | bool isTimeForReset = false; 61 | lock (_Sync) 62 | { 63 | if (_LastExceptionTimeStamp == null) 64 | _LastExceptionTimeStamp = _NowGetter(); 65 | else 66 | { 67 | DateTime t = _NowGetter(); 68 | if (t > _LastExceptionTimeStamp + _TimeoutBetweenRetries) 69 | { 70 | Reset(); 71 | isTimeForReset = true; 72 | } 73 | } 74 | } 75 | if (!isTimeForReset) 76 | throw; 77 | 78 | return _Lazy.Value; 79 | } 80 | } 81 | } 82 | 83 | /// 84 | /// Reset the current lazy value. 85 | /// 86 | public void Reset() 87 | { 88 | _Lazy = new Lazy(_ValueFactory); 89 | _LastExceptionTimeStamp = null; 90 | } 91 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/LoggerFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | 5 | using Discord.WebSocket; 6 | 7 | using Logging; 8 | 9 | using Extensions; 10 | 11 | /// 12 | /// Implementation of . 13 | /// 14 | /// The . 15 | /// cannot be null. 16 | /// 17 | /// 18 | public class LoggerFactory(DiscordShardedClient discordClient) : ILoggerFactory 19 | { 20 | private readonly DiscordShardedClient _discordClient = discordClient ?? throw new ArgumentNullException(nameof(discordClient)); 21 | 22 | /// 23 | public ILogger CreateLogger(SocketInteraction interaction) 24 | { 25 | var name = interaction.User.Username; 26 | var logger = new Logger( 27 | name: interaction.User.Id.ToString(), 28 | logLevelGetter: () => LogLevel.Debug, 29 | logToFileSystem: false 30 | ); 31 | 32 | logger.CustomLogPrefixes.Add(() => interaction.GetChannelAsString()); 33 | logger.CustomLogPrefixes.Add(() => interaction.User.ToString()); 34 | 35 | var guild = interaction.GetGuild(_discordClient); 36 | 37 | // Add guild id if the interaction is from a guild. 38 | if (guild is not null) 39 | logger.CustomLogPrefixes.Add(() => guild.ToString()); 40 | 41 | return logger; 42 | } 43 | 44 | /// 45 | public ILogger CreateLogger(SocketMessage message) 46 | { 47 | var name = message.Author.Username; 48 | var logger = new Logger( 49 | name: message.Author.Id.ToString(), 50 | logLevelGetter: () => LogLevel.Debug, 51 | logToFileSystem: false 52 | ); 53 | 54 | logger.CustomLogPrefixes.Add(() => message.Channel.ToString()); 55 | logger.CustomLogPrefixes.Add(() => message.Author.ToString()); 56 | 57 | // Add guild id if the message is from a guild. 58 | if (message.Channel is SocketGuildChannel guildChannel) 59 | logger.CustomLogPrefixes.Add(() => guildChannel.Guild.ToString()); 60 | 61 | return logger; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/LuaUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Collections.Generic; 7 | using System.Text.RegularExpressions; 8 | 9 | using Newtonsoft.Json; 10 | 11 | using Client; 12 | 13 | /// 14 | /// Utility for interacting with grid-server Lua. 15 | /// 16 | public partial class LuaUtility : ILuaUtility 17 | { 18 | private static readonly Assembly _assembly = Assembly.GetExecutingAssembly(); 19 | private const string _luaVmResource = "Grid.Bot.Lua.LuaVMTemplate.lua"; 20 | 21 | [GeneratedRegex(@"{{(\d{1,2})}}", RegexOptions.IgnoreCase | RegexOptions.Compiled)] 22 | private static partial Regex FormatPartRegex(); 23 | 24 | 25 | private static readonly string _LuaVM; 26 | 27 | static LuaUtility() 28 | { 29 | using var stream = _assembly.GetManifestResourceStream(_luaVmResource); 30 | using var reader = new StreamReader(stream); 31 | 32 | _LuaVM = FixFormatString(reader.ReadToEnd()); 33 | } 34 | 35 | private static string FixFormatString(string input) 36 | { 37 | input = input.Replace("{", "{{"); 38 | input = input.Replace("}", "}}"); 39 | 40 | input = FormatPartRegex().Replace(input, (m) => { return $"{{{m.Groups[1]}}}"; }); 41 | 42 | return input; 43 | } 44 | 45 | /// 46 | public string LuaVMTemplate => _LuaVM; 47 | 48 | /// 49 | public (string result, ReturnMetadata metadata) ParseResult(IEnumerable result) 50 | { 51 | if (result.Count() == 1) 52 | { 53 | // Legacy case, where LuaVM is not enabled. 54 | 55 | var mockMetadata = new ReturnMetadata(); 56 | mockMetadata.Success = true; 57 | 58 | return ((string)Lua.ConvertLua(result.First()), mockMetadata); 59 | } 60 | 61 | return ( 62 | (string)Lua.ConvertLua(result.FirstOrDefault()), 63 | JsonConvert.DeserializeObject((string)Lua.ConvertLua(result.ElementAtOrDefault(1))) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/MicrosoftLogger.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | 5 | using ILogger = Logging.ILogger; 6 | 7 | using IMLogger = Microsoft.Extensions.Logging.ILogger; 8 | using MEventId = Microsoft.Extensions.Logging.EventId; 9 | using MLogLevel = Microsoft.Extensions.Logging.LogLevel; 10 | 11 | /// 12 | /// Implementation of that forwards to our ! 13 | /// 14 | /// Our 15 | /// cannot be null. 16 | public class MicrosoftLogger(ILogger logger) : IMLogger 17 | { 18 | private class NoopDisposable : IDisposable 19 | { 20 | public void Dispose() { } 21 | } 22 | 23 | private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 24 | 25 | /// 26 | public IDisposable BeginScope(TState state) => new NoopDisposable(); 27 | 28 | /// 29 | public bool IsEnabled(MLogLevel logLevel) => true; 30 | 31 | /// 32 | public void Log(MLogLevel logLevel, MEventId eventId, TState state, Exception exception, Func formatter) 33 | { 34 | var message = formatter(state, exception); 35 | 36 | switch (logLevel) 37 | { 38 | case MLogLevel.Trace: 39 | _logger.Verbose(message); 40 | 41 | break; 42 | case MLogLevel.Debug: 43 | _logger.Debug(message); 44 | 45 | break; 46 | case MLogLevel.Information: 47 | _logger.Information(message); 48 | 49 | break; 50 | case MLogLevel.Warning: 51 | _logger.Warning(message); 52 | 53 | break; 54 | case MLogLevel.Error: 55 | case MLogLevel.Critical: 56 | _logger.Error(message); 57 | 58 | break; 59 | default: 60 | _logger.Warning(message); 61 | 62 | break; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/MicrosoftLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | 5 | using ILogger = Logging.ILogger; 6 | 7 | using IMLogger = Microsoft.Extensions.Logging.ILogger; 8 | using IMLoggerProvider = Microsoft.Extensions.Logging.ILoggerProvider; 9 | 10 | /// 11 | /// Provider for the class. 12 | /// 13 | /// Our 14 | /// cannot be null. 15 | public class MicrosoftLoggerProvider(ILogger logger) : IMLoggerProvider 16 | { 17 | private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 18 | 19 | /// 20 | public IMLogger CreateLogger(string categoryName) => new MicrosoftLogger(_logger); 21 | 22 | /// 23 | public void Dispose() { } 24 | } -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Implementation/RbxUsersUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | using Users.Client; 8 | 9 | /// 10 | /// Utility class for interacting with the Roblox Users API. 11 | /// 12 | /// 13 | /// Construct a new instance of . 14 | /// 15 | /// The . 16 | /// cannot be null. 17 | public class RbxUsersUtility(IUsersClient usersClient) : IRbxUsersUtility 18 | { 19 | private readonly IUsersClient _usersClient = usersClient ?? throw new ArgumentNullException(nameof(usersClient)); 20 | 21 | /// 22 | public async Task GetIsUserBannedAsync(long id) 23 | { 24 | try 25 | { 26 | var request = new MultiGetByUserIdRequest 27 | { 28 | ExcludeBannedUsers = false, 29 | UserIds = [id] 30 | }; 31 | 32 | var response = await _usersClient.MultiGetUsersByIdsAsync(request); 33 | return response.Data.Count == 0; 34 | } 35 | catch 36 | { 37 | return false; 38 | } 39 | } 40 | 41 | /// 42 | public async Task GetUserIdByUsernameAsync(string username) 43 | { 44 | var request = new MultiGetByUsernameRequest 45 | { 46 | ExcludeBannedUsers = false, 47 | Usernames = [username] 48 | }; 49 | 50 | try 51 | { 52 | var response = await _usersClient.MultiGetUsersByUsernamesAsync(request); 53 | 54 | return response.Data.FirstOrDefault()?.Id; 55 | } 56 | catch 57 | { 58 | return null; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/IAdminUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using Discord; 4 | 5 | /// 6 | /// Utility class for administration. 7 | /// 8 | public interface IAdminUtility 9 | { 10 | /// 11 | /// Is the specified in the specifed role? 12 | /// 13 | /// The 14 | /// The 15 | /// Returns true if the user's id matches one of any entry for the specified bot role. 16 | bool IsInRole(IUser user, BotRole role = BotRole.Default); 17 | 18 | /// 19 | /// Is the the owner?. 20 | /// 21 | /// The 22 | /// Returns true if the user's id matches the 23 | bool UserIsOwner(IUser user); 24 | 25 | /// 26 | /// Is the an admin? 27 | /// 28 | /// The 29 | /// Returns true if the user's id is in the 30 | bool UserIsAdmin(IUser user); 31 | 32 | /// 33 | /// Is the a higher privilaged user? 34 | /// 35 | /// The 36 | /// Returns true if the user's id is in the 37 | bool UserIsPrivilaged(IUser user); 38 | 39 | /// 40 | /// Is the blacklisted? 41 | /// 42 | /// The 43 | /// Returns true if the user's id is in the 44 | bool UserIsBlacklisted(IUser user); 45 | 46 | /// 47 | /// Set the as privileged. 48 | /// 49 | /// The 50 | void SetUserAsPrivilaged(IUser user); 51 | 52 | /// 53 | /// Set the as normal. 54 | /// 55 | /// The 56 | void SetUserAsNormal(IUser user); 57 | 58 | /// 59 | /// Blacklist the . 60 | /// 61 | /// The 62 | void BlacklistUser(IUser user); 63 | 64 | /// 65 | /// Unblacklist the . 66 | /// 67 | /// The 68 | void UnblacklistUser(IUser user); 69 | 70 | /// 71 | /// Set the as admin. 72 | /// 73 | /// The 74 | void SetUserAsAdmin(IUser user); 75 | } 76 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/IAvatarUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System.IO; 4 | 5 | /// 6 | /// Utility class for rendering avatars. 7 | /// 8 | public interface IAvatarUtility 9 | { 10 | /// 11 | /// Render a user by ID. 12 | /// 13 | /// The user's Id. 14 | /// The place Id to inherit character settings from. 15 | /// The X dimension of the image. 16 | /// The Y dimension of the image. 17 | /// The stream and thumbnail name. 18 | (Stream, string) RenderUser(long userId, long placeId, int sizeX, int sizeY); 19 | } 20 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/IBacktraceUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System; 4 | 5 | using Backtrace.Model; 6 | using Backtrace.Interfaces; 7 | 8 | /// 9 | /// Utility class for Backtrace.NET 10 | /// 11 | public interface IBacktraceUtility 12 | { 13 | /// 14 | /// Gets the Backtrace client. 15 | /// 16 | IBacktraceClient Client { get; } 17 | 18 | /// 19 | /// Upload an exception to Backtrace 20 | /// 21 | /// The 22 | void UploadException(Exception ex); 23 | 24 | /// 25 | /// Upload all log files 26 | /// 27 | /// Should they be deleted? 28 | /// The 29 | BacktraceResult UploadAllLogFiles(bool delete = true); 30 | } 31 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/IDiscordWebhookAlertManager.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | 6 | using Discord; 7 | 8 | /// 9 | /// Handles sending alerts to a Discord webhook. 10 | /// 11 | public interface IDiscordWebhookAlertManager 12 | { 13 | /// 14 | /// Sends an alert to the Discord webhook. 15 | /// 16 | /// The topic of the alert. 17 | /// The message of the alert. 18 | /// The color of the alert. 19 | /// The attachments of the alert. 20 | /// A representing the asynchronous operation. 21 | Task SendAlertAsync(string topic, string message, Color? color, IEnumerable attachments = null); 22 | } 23 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/IFloodCheckerRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using FloodCheckers.Core; 4 | 5 | /// 6 | /// Interface for a flood checker registry. 7 | /// 8 | public interface IFloodCheckerRegistry 9 | { 10 | /// 11 | /// Gets the system flood checker for script executions. 12 | /// 13 | IFloodChecker ScriptExecutionFloodChecker { get; } 14 | 15 | /// 16 | /// Gets the system flood checker for renders. 17 | /// 18 | IFloodChecker RenderFloodChecker { get; } 19 | 20 | /// 21 | /// Get the script execution for the 22 | /// 23 | /// The ID of the 24 | /// The script execution 25 | IFloodChecker GetPerUserScriptExecutionFloodChecker(ulong userId); 26 | 27 | /// 28 | /// Get the render for the 29 | /// 30 | /// The ID of the 31 | /// The render 32 | IFloodChecker GetPerUserRenderFloodChecker(ulong userId); 33 | } 34 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/ILoggerFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using Discord.WebSocket; 4 | 5 | using Logging; 6 | 7 | /// 8 | /// Factory for creating loggers. 9 | /// 10 | public interface ILoggerFactory 11 | { 12 | /// 13 | /// Create a logger for the specified interaction. 14 | /// 15 | /// The interaction. 16 | /// A logger for the specified interaction. 17 | ILogger CreateLogger(SocketInteraction interaction); 18 | 19 | /// 20 | /// Create a logger for the specified message. 21 | /// 22 | /// The message. 23 | /// A logger for the specified message. 24 | ILogger CreateLogger(SocketMessage message); 25 | } 26 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/ILuaUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System.Collections.Generic; 4 | 5 | using Newtonsoft.Json; 6 | 7 | using Client; 8 | 9 | /// 10 | /// Represents the metadata returned by the lua-vm script. 11 | /// 12 | public struct ReturnMetadata 13 | { 14 | /// 15 | /// Is the script a success? 16 | /// 17 | [JsonProperty("success")] 18 | public bool Success; 19 | 20 | /// 21 | /// The total execution time. 22 | /// 23 | [JsonProperty("execution_time")] 24 | public double ExecutionTime; 25 | 26 | /// 27 | /// The optional error message. 28 | /// 29 | [JsonProperty("error_message")] 30 | public string ErrorMessage; 31 | 32 | /// 33 | /// The optional logs. 34 | /// 35 | [JsonProperty("logs")] 36 | public string Logs; 37 | } 38 | 39 | /// 40 | /// Utility class for managing Lua scripts. 41 | /// 42 | public interface ILuaUtility 43 | { 44 | /// 45 | /// The template file for the lua-vm. 46 | /// 47 | string LuaVMTemplate { get; } 48 | 49 | /// 50 | /// Parse the return metadata from the grid-server. 51 | /// 52 | /// The s 53 | /// The result and the 54 | (string result, ReturnMetadata metadata) ParseResult(IEnumerable result); 55 | } 56 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/IRbxUsersUtility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System.Threading.Tasks; 4 | 5 | /// 6 | /// Utility class for interacting with the Roblox Users API. 7 | /// 8 | public interface IRbxUsersUtility 9 | { 10 | /// 11 | /// Gets the banned status of a user. 12 | /// 13 | /// The ID of the Roblox user. 14 | /// True if the user is banned, false otherwise. 15 | Task GetIsUserBannedAsync(long id); 16 | 17 | /// 18 | /// Gets the ID of the specified Roblox User. 19 | /// 20 | /// The name of the user. 21 | /// The ID of the user. 22 | Task GetUserIdByUsernameAsync(string username); 23 | } 24 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Interfaces/IScriptLogger.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Utility; 2 | 3 | using System.Threading.Tasks; 4 | 5 | using Discord; 6 | 7 | using Discord.Commands; 8 | 9 | /// 10 | /// Handles logging the contents of scripts to a Discord webhook. 11 | /// 12 | /// 13 | /// Logs in the following format:

14 | /// {attachment...}
15 | /// {begin_embed}
16 | /// **User:** {userInfo}
17 | /// **Guild:** {guildInfo}
18 | /// **Channel:** {channelInfo}
19 | /// **Script Hash:** {scriptHash}
20 | /// {end_embed}
21 | ///
22 | public interface IScriptLogger 23 | { 24 | /// 25 | /// Logs the contents of a script to a Discord webhook. 26 | /// 27 | /// The script to log. 28 | /// The to use. 29 | /// A representing the asynchronous operation. 30 | Task LogScriptAsync(string script, IInteractionContext context); 31 | 32 | /// 33 | /// Logs the contents of a script to a Discord webhook. 34 | /// 35 | /// The script to log. 36 | /// The to use. 37 | /// A representing the asynchronous operation. 38 | Task LogScriptAsync(string script, ICommandContext context); 39 | } 40 | -------------------------------------------------------------------------------- /services/grid-bot/lib/utility/Shared.Utility.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shared utility classes used by the grid-bot 4 | true 5 | 6 | 7 | 8 | $(DefineConstants);WE_LOVE_EM_SLASH_COMMANDS 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Extensions/HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Extensions; 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.AspNetCore.Http; 8 | 9 | /// 10 | /// Extensions for , and 11 | /// 12 | public static class HttpContextExtensions 13 | { 14 | private const string _apiKeyHeaderName = "x-api-key"; 15 | 16 | /// 17 | /// Writes an error to the response. 18 | /// 19 | /// The 20 | /// The error message 21 | public static async Task WriteRbxError(this HttpResponse response, string error) 22 | { 23 | var errors = new object[1] { new { code = 1, message = error } }; 24 | 25 | await response.WriteAsJsonAsync(new { errors }); 26 | } 27 | 28 | /// 29 | /// Determines if the request has a valid API key in it. 30 | /// 31 | /// The 32 | /// The 33 | /// [true] if the request has a valid API key, otherwise false. 34 | public static bool HasValidApiKey(this HttpRequest request, ClientSettingsSettings settings) 35 | { 36 | if (settings.ClientSettingsApiKeys.Length == 0) return true; 37 | if (!request.Headers.TryGetValue(_apiKeyHeaderName, out var apiKeyHeaderValues)) return false; 38 | 39 | var apiKeyHeader = apiKeyHeaderValues.First(); 40 | return settings.ClientSettingsApiKeys.Contains(apiKeyHeader); 41 | } 42 | 43 | /// 44 | /// Tries to get an int64 from the request query string. 45 | /// 46 | /// This is case-insensitive. 47 | /// The 48 | /// The key to look for 49 | /// The value of the key 50 | /// [true] if the key was found and the value is an integer, otherwise false. 51 | public static bool TryParseInt64FromQuery(this HttpRequest request, string key, out long value) 52 | { 53 | value = 0; 54 | 55 | return request.Query.TryGetValue(key, out var valueString) && long.TryParse(valueString, out value); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Extensions/HttpServerTelemetryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Middleware; 2 | 3 | using System; 4 | 5 | using Microsoft.AspNetCore.Builder; 6 | 7 | /// 8 | /// Extension methods for adding telemetry to the HTTP server. 9 | /// 10 | public static class HttpServerTelemetryExtensions 11 | { 12 | /// 13 | /// Adds telemetry to the HTTP server. 14 | /// 15 | /// The 16 | /// The 17 | public static IApplicationBuilder UseTelemetry(this IApplicationBuilder app) 18 | { 19 | ArgumentNullException.ThrowIfNull(app); 20 | 21 | app.UseMiddleware(); 22 | app.UseMiddleware(); 23 | app.UseMiddleware(); 24 | 25 | return app; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Grid.Bot.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library containing implementation for Web based code in grid-bot. Port of @mfdlabs/grid-service-websrv to C# 4 | 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Middleware/HttpServerConcurrentRequestsMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Middleware; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Microsoft.AspNetCore.Http; 7 | 8 | using Prometheus; 9 | 10 | /// 11 | /// Middleware for counting concurrent requests. 12 | /// 13 | public sealed class HttpServerConcurrentRequestsMiddleware 14 | { 15 | private readonly RequestDelegate _next; 16 | private readonly IGauge _ConcurentRequestsGauge = Metrics.CreateGauge("http_server_concurrent_requests_total", "The number of concurrent requests being processed by the server."); 17 | 18 | /// 19 | /// Construct a new instance of 20 | /// 21 | /// The 22 | /// cannot be null. 23 | public HttpServerConcurrentRequestsMiddleware(RequestDelegate next) 24 | { 25 | _next = next ?? throw new ArgumentNullException(nameof(next)); 26 | } 27 | 28 | /// 29 | /// Invoke the middleware. 30 | /// 31 | /// The 32 | /// An awaitable 33 | public async Task Invoke(HttpContext context) 34 | { 35 | using (_ConcurentRequestsGauge.TrackInProgress()) 36 | await _next(context); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Middleware/HttpServerMiddlewareBase.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Middleware; 2 | 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Routing; 5 | 6 | /// 7 | /// Base middleware for http servers. 8 | /// 9 | public abstract class HttpServerMiddlewareBase 10 | { 11 | /// 12 | /// Unknown route value. 13 | /// 14 | protected const string _UnknownRouteLabelValue = "Unknown"; 15 | 16 | /// 17 | /// Get endpoint label value for metrics. 18 | /// 19 | /// The 20 | /// The endpoint label value or 21 | protected static (string controller, string action) GetControllerAndAction(HttpContext context) 22 | { 23 | var routeData = context.GetRouteData(); 24 | var action = routeData?.Values["action"] as string ?? string.Empty; 25 | var controller = routeData?.Values["controller"] as string ?? string.Empty; 26 | 27 | if (string.IsNullOrWhiteSpace(action) || string.IsNullOrWhiteSpace(controller)) 28 | return (_UnknownRouteLabelValue, _UnknownRouteLabelValue); 29 | 30 | return (controller, action); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Middleware/HttpServerRequestCountMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Middleware; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Microsoft.AspNetCore.Http; 7 | 8 | using Prometheus; 9 | 10 | /// 11 | /// Middleware for counting http server requests. 12 | /// 13 | public sealed class HttpServerRequestCountMiddleware : HttpServerMiddlewareBase 14 | { 15 | private readonly RequestDelegate _next; 16 | private readonly Counter _HttpRequestCounter = Metrics.CreateCounter( 17 | "http_server_requests_total", 18 | "Total number of http requests", 19 | "Method", 20 | "Endpoint" 21 | ); 22 | 23 | /// 24 | /// Construct a new instance of 25 | /// 26 | /// The 27 | /// cannot be null. 28 | public HttpServerRequestCountMiddleware(RequestDelegate next) 29 | { 30 | _next = next ?? throw new ArgumentNullException(nameof(next)); 31 | } 32 | 33 | /// 34 | /// Invoke the middleware. 35 | /// 36 | /// The 37 | /// An awaitable 38 | public async Task Invoke(HttpContext context) 39 | { 40 | var (controller, action) = GetControllerAndAction(context); 41 | var endpoint = controller != _UnknownRouteLabelValue && action != _UnknownRouteLabelValue ? 42 | string.Format("{0}.{1}", controller, action) 43 | : context.Request.Path.Value ?? _UnknownRouteLabelValue; 44 | 45 | _HttpRequestCounter.WithLabels(context.Request.Method, endpoint).Inc(); 46 | 47 | await _next(context); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Middleware/HttpServerRequestLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Middleware; 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.AspNetCore.Http; 8 | 9 | using Logging; 10 | 11 | /// 12 | /// Middleware for logging HTTP requests. 13 | /// 14 | public sealed class HttpServerRequestLoggingMiddleware : HttpServerMiddlewareBase 15 | { 16 | private readonly RequestDelegate _next; 17 | private readonly ILogger _logger; 18 | 19 | /// 20 | /// Construct a new instance of 21 | /// 22 | /// The 23 | /// The 24 | /// 25 | /// - is . 26 | /// - is . 27 | /// 28 | public HttpServerRequestLoggingMiddleware(RequestDelegate next, ILogger logger) 29 | { 30 | _next = next ?? throw new ArgumentNullException(nameof(next)); 31 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 32 | } 33 | 34 | private static string GetEndPointLogEntry(HttpContext context) 35 | => string.Format("\"{0} {1}\"", context.Request.Method, context.Request.Path); 36 | 37 | /// 38 | /// Invoke the middleware. 39 | /// 40 | /// The 41 | /// An awaitable 42 | public async Task Invoke(HttpContext context) 43 | { 44 | var endpointLogEntry = GetEndPointLogEntry(context); 45 | if (string.Compare(context.Request.Method, "GET", StringComparison.CurrentCultureIgnoreCase) == 0) 46 | _logger.Debug("{0} request called: {1}", endpointLogEntry, context.Request.QueryString); 47 | else 48 | _logger.Debug("{0} request called", endpointLogEntry); 49 | 50 | var latency = Stopwatch.StartNew(); 51 | 52 | try 53 | { 54 | await _next(context); 55 | 56 | _logger.Debug("{0} responded in {1} ms", endpointLogEntry, latency.ElapsedMilliseconds); 57 | } 58 | catch (Exception ex) 59 | { 60 | _logger.Error("{0} failed in {1} ms: {2}", endpointLogEntry, latency.ElapsedMilliseconds, ex); 61 | 62 | throw; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Middleware/HttpServerResponseMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Middleware; 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.AspNetCore.Http; 8 | 9 | using Prometheus; 10 | 11 | /// 12 | /// Middleware to handle the response 13 | /// 14 | /// 15 | /// Middleware for counting and recording http response metrics. 16 | /// 17 | public sealed class HttpServerResponseMiddleware : HttpServerMiddlewareBase 18 | { 19 | private readonly RequestDelegate _next; 20 | 21 | private readonly Counter _HttpResponseCounter = Metrics.CreateCounter( 22 | "http_server_response_total", 23 | "Total number of http responses", 24 | "Method", 25 | "Endpoint", 26 | "StatusCode" 27 | ); 28 | private readonly Histogram _RequestDurationHistogram= Metrics.CreateHistogram( 29 | "http_server_request_duration_seconds", 30 | "Duration in seconds each request takes", 31 | "Method", 32 | "Endpoint" 33 | ); 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// The . 39 | /// 40 | /// - cannot be null. 41 | /// 42 | public HttpServerResponseMiddleware(RequestDelegate next) 43 | { 44 | _next = next ?? throw new ArgumentNullException(nameof(next)); 45 | } 46 | 47 | /// 48 | /// Invokes the middleware 49 | /// 50 | /// The . 51 | /// A . 52 | /// cannot be null. 53 | public async Task Invoke(HttpContext context) 54 | { 55 | ArgumentNullException.ThrowIfNull(context); 56 | 57 | var latencyStopwatch = Stopwatch.StartNew(); 58 | var (controller, action) = GetControllerAndAction(context); 59 | var endpoint = controller != _UnknownRouteLabelValue && action != _UnknownRouteLabelValue ? 60 | string.Format("{0}.{1}", controller, action) 61 | : context.Request.Path.Value ?? _UnknownRouteLabelValue; 62 | 63 | try 64 | { 65 | await _next(context).ConfigureAwait(false); 66 | 67 | context.Response.OnCompleted(() => 68 | { 69 | latencyStopwatch.Stop(); 70 | 71 | var statusCode = context.Response.StatusCode; 72 | 73 | if (context.Response.StatusCode >= 200 && context.Response.StatusCode < 300) 74 | _RequestDurationHistogram.WithLabels(context.Request.Method, endpoint).Observe(latencyStopwatch.Elapsed.TotalSeconds); 75 | 76 | _HttpResponseCounter.WithLabels(context.Request.Method, endpoint, context.Response.StatusCode.ToString()).Inc(); 77 | 78 | return Task.CompletedTask; 79 | }); 80 | } 81 | catch (Exception) 82 | { 83 | _HttpResponseCounter.WithLabels(context.Request.Method, endpoint, context.Response.StatusCode.ToString()).Inc(); 84 | 85 | latencyStopwatch.Stop(); 86 | 87 | throw; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Middleware/UnhandledExceptionMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Middleware; 2 | 3 | using System; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.AspNetCore.Http; 8 | 9 | using Logging; 10 | 11 | /// 12 | /// Middleware for logging unhandled exceptions and responding with . 13 | /// 14 | public class UnhandledExceptionMiddleware 15 | { 16 | private readonly RequestDelegate _NextHandler; 17 | private readonly ILogger _Logger; 18 | 19 | /// 20 | /// Initializes a new . 21 | /// 22 | /// A delegate for triggering the next handler. 23 | /// An . 24 | /// 25 | /// - 26 | /// - 27 | /// 28 | public UnhandledExceptionMiddleware(RequestDelegate nextHandler, ILogger logger) 29 | { 30 | _NextHandler = nextHandler ?? throw new ArgumentNullException(nameof(nextHandler)); 31 | _Logger = logger ?? throw new ArgumentNullException(nameof(logger)); 32 | } 33 | 34 | /// 35 | /// The method to invoke the handler. 36 | /// 37 | /// An . 38 | public async Task Invoke(HttpContext context) 39 | { 40 | try 41 | { 42 | await _NextHandler(context); 43 | } 44 | catch (Exception ex) 45 | { 46 | _Logger.Error(ex); 47 | 48 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 49 | 50 | context.Response.ContentType = "text/plain"; 51 | await context.Response.WriteAsync(ex.ToString()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Routes/ClientSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Routes; 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.AspNetCore.Http; 8 | 9 | using Logging; 10 | 11 | using Utility; 12 | using Extensions; 13 | 14 | /// 15 | /// Routes for the client settings API. 16 | /// 17 | public class ClientSettings 18 | { 19 | private const string _applicationNameQueryParameter = "applicationName"; 20 | private const string _invalidAppNameError = "The application name is invalid."; // also used for denied access. 21 | 22 | private readonly ClientSettingsSettings _settings; 23 | private readonly ILogger _logger; 24 | private readonly IClientSettingsFactory _clientSettingsFactory; 25 | 26 | /// 27 | /// Construct a new instance of 28 | /// 29 | /// The 30 | /// The 31 | /// The 32 | /// 33 | /// - cannot be null. 34 | /// - cannot be null. 35 | /// - cannot be null. 36 | /// 37 | public ClientSettings(ClientSettingsSettings settings, ILogger logger, IClientSettingsFactory clientSettingsFactory) 38 | { 39 | _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 40 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 41 | _clientSettingsFactory = clientSettingsFactory ?? throw new ArgumentNullException(nameof(clientSettingsFactory)); 42 | } 43 | 44 | /// 45 | /// Get application settings for the specified application. 46 | /// 47 | /// The 48 | public async Task GetApplicationSettings(HttpContext context) 49 | { 50 | if (!context.Request.Query.TryGetValue(_applicationNameQueryParameter, out var applicationNameValues)) 51 | { 52 | context.Response.StatusCode = 400; 53 | await context.Response.WriteRbxError(_invalidAppNameError); 54 | 55 | return; 56 | } 57 | 58 | var applicationName = applicationNameValues.First(); 59 | 60 | if (!_settings.PermissibleReadApplications.Contains(applicationName) && !context.Request.HasValidApiKey(_settings)) 61 | { 62 | _logger.Warning("User {0} read attempt on non permissible read application ({1})", context.Connection.RemoteIpAddress, applicationName); 63 | 64 | context.Response.StatusCode = 403; 65 | await context.Response.WriteRbxError(_invalidAppNameError); 66 | 67 | return; 68 | } 69 | 70 | var applicationSettings = _clientSettingsFactory.GetSettingsForApplication(applicationName); 71 | if (applicationSettings is null) 72 | { 73 | context.Response.StatusCode = 400; 74 | await context.Response.WriteRbxError(_invalidAppNameError); 75 | 76 | return; 77 | } 78 | 79 | await context.Response.WriteAsJsonAsync(new { applicationSettings }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /services/grid-bot/lib/web/Routes/VersionCompatibility.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Web.Routes; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Microsoft.AspNetCore.Http; 7 | 8 | /// 9 | /// Routes for the versioncompatibility API. 10 | /// 11 | public class VersionCompatibility 12 | { 13 | /// 14 | /// Get allowed MD5 hashes, for now this responds with an empty response. 15 | /// 16 | /// The 17 | public async Task GetAllowedMd5Hashes(HttpContext context) 18 | => await context.Response.WriteAsJsonAsync(new { data = Array.Empty() }); 19 | } 20 | -------------------------------------------------------------------------------- /services/grid-bot/src/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /services/grid-bot/src/Grid.Bot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | $(NoWarn);4014 4 | 5 | 6 | 7 | debug;release 8 | Exe 9 | 10 | True 11 | 12 | 13 | 14 | Primary executable for the MFDLABS grid-bot. 15 | $(DefineConstants);WE_LOVE_EM_SLASH_COMMANDS;DISCORD_SHARDING_ENABLED 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | PreserveNewest 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /services/grid-bot/src/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | /// 8 | /// Main entry point. 9 | /// 10 | public static class Program 11 | { 12 | private static bool AssemblyIsLoaded(string name) 13 | { 14 | try 15 | { 16 | return AppDomain.CurrentDomain.Load(name) != null; 17 | } 18 | // We assume this means that it's already loaded into another evidence 19 | catch (FileLoadException) { return true; } 20 | catch (Exception) { return false; } 21 | } 22 | 23 | /// 24 | /// Main method. 25 | /// 26 | /// The arguments. 27 | public static void Main(string[] args) 28 | { 29 | AppDomain.CurrentDomain.UnhandledException += (_, e) => 30 | { 31 | if (e.ExceptionObject is FileNotFoundException or TypeLoadException) 32 | { 33 | Console.ForegroundColor = ConsoleColor.Yellow; 34 | Console.WriteLine( 35 | "There was an error loading a type or dependency, please review the following error: {0}", 36 | (e.ExceptionObject as Exception)?.Message 37 | ); 38 | Console.ResetColor(); 39 | Console.WriteLine("Press any key to exit..."); 40 | Console.ReadKey(true); 41 | Environment.Exit(1); 42 | return; 43 | } 44 | 45 | Console.ForegroundColor = ConsoleColor.Red; 46 | Console.WriteLine("[URGENT]: Unhandled global exception occurred: {0}", e.ExceptionObject); 47 | Console.ResetColor(); 48 | 49 | if (AssemblyIsLoaded("Backtrace") && AssemblyIsLoaded("Shared.Settings") && AssemblyIsLoaded("Shared.Utility")) 50 | Runner.ReportError(e.ExceptionObject as Exception); 51 | 52 | if (e.ExceptionObject is AggregateException aggregate) 53 | { 54 | if (aggregate.InnerExceptions.Any(x => x is InvalidOperationException)) 55 | { 56 | Console.WriteLine("Press any key to exit..."); 57 | Console.ReadKey(true); 58 | Environment.Exit(1); 59 | } 60 | } 61 | }; 62 | 63 | Runner.Invoke(args); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /services/grid-bot/src/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Trace", 5 | "System": "Trace", 6 | "Microsoft": "Trace" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /services/grid-bot/ssl/global-root-ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEhjCCA26gAwIBAgIUNwszBVXDba6KpHmEdGyiqHBKx/IwDQYJKoZIhvcNAQEL 3 | BQAwgdoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQH 4 | DAtMb3MgQW5nZWxlczEQMA4GA1UECgwHTUZETEFCUzEtMCsGA1UECwwkTUZETEFC 5 | UyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBUZWFtMTcwNQYDVQQDDC5DZXJ0aWZp 6 | Y2F0aW9uIEF1dGhvcml0eSBAIGdsb2JhbC5tZmRsYWJzLmxvY2FsMSYwJAYJKoZI 7 | hvcNAQkBFhdjYUBnbG9iYWwubWZkbGFicy5sb2NhbDAeFw0yMzA4MTIxNjE3MTNa 8 | Fw00MzA4MTIxNjE3MTNaMIHaMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZv 9 | cm5pYTEUMBIGA1UEBwwLTG9zIEFuZ2VsZXMxEDAOBgNVBAoMB01GRExBQlMxLTAr 10 | BgNVBAsMJE1GRExBQlMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgVGVhbTE3MDUG 11 | A1UEAwwuQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgQCBnbG9iYWwubWZkbGFicy5s 12 | b2NhbDEmMCQGCSqGSIb3DQEJARYXY2FAZ2xvYmFsLm1mZGxhYnMubG9jYWwwggEi 13 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWWjUDkRN3z/QYq4hCUW2O7kFS 14 | 5kZioryauUIW8/PQ6u1I6y4ONSFy8BcE6h+MevBlPyD9Q839CaB0WAN9ATF2qeqw 15 | D/aKadri9KqMm6UjkHo8OBn2/lAh6hsfBurUurn40JbNFyOv+qn5CxKUL9AwrITi 16 | arXlww4pZJYcDrei5Hyf0JqGxdIY8VEXXEbhXTJSF3IwfU7bCaLJp7NVy40K9amW 17 | ofX64vvgO154dCBSIDqHixuTXAvx9GGWn5jQu0je1cG8XkWlK4hBEp6paXOTHAyf 18 | nTubHQb93ucP2r3CQlttXq4xwY0ID/EGGOMiNbGT6UkfNMUu/OT/5MZFkOy7AgMB 19 | AAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW 20 | BBQXrJxdwoxyaZJm3qMFzKEqAKr56jANBgkqhkiG9w0BAQsFAAOCAQEAFC/Ao2Vl 21 | fiXNlUPDArPTxOvmyuCOxUWJVYd5VdN9ABPwk4fPGwQOahtngSNBtjOPNhk6hWvo 22 | Il+pMeOBvWiwXJDonaAeMuRN35MCfw/sODLCL2Vc3lkkfoO46uCnMhQbE8SFGWpF 23 | UQFBlH2sQStenpSWnDdHqfPZkolJPKFr/c80j+gD4lLbhkhE5oa48WzZrTvaRhiw 24 | +6kn/ZdU4zfnPbFrJ9tipdGVizO7lxkV94em4wG4d9CcpqzE3GafYEG6pTQUnPaz 25 | Brr4upJkhCoQQbM5Y3jsx7FaUR5TB1KImmMlLZN+/G3lHIYevJP7M440yff+zPoW 26 | UdZFJRi3EgpYsw== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /services/recovery/.component.yaml: -------------------------------------------------------------------------------- 1 | component: grid-bot-recovery 2 | 3 | # This is only used by the build worklow, 4 | # it determines how the component is built 5 | # Docker only relevant when the argument 6 | # 7 | build: 8 | project_file: src/Grid.Bot.Recovery.csproj 9 | component_directory: ./.deploy 10 | 11 | additional_args: 12 | - -p:IMAGE_TAG=${{ env.NOMAD_VERSION }} 13 | - -p:CI=true 14 | 15 | docker: 16 | docker_file: Dockerfile 17 | image_name: mfdlabs/grid-bot-recovery 18 | -------------------------------------------------------------------------------- /services/recovery/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !ssl 3 | -------------------------------------------------------------------------------- /services/recovery/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base Image: net8.0 2 | FROM mcr.microsoft.com/dotnet/aspnet:8.0.1-jammy 3 | 4 | WORKDIR /opt/grid 5 | COPY . /opt/grid/ 6 | 7 | CMD ["dotnet", "/opt/grid/Grid.Bot.Recovery.dll"] 8 | -------------------------------------------------------------------------------- /services/recovery/README.md: -------------------------------------------------------------------------------- 1 | # Grid Bot Recovery 2 | 3 | This is a Daemon sidecar that runs somewhere decentralized from Vault and the actual Grid Bot, it will periodically check to see if the Grid Bot is responding to health check RPC calls and will -------------------------------------------------------------------------------- /services/recovery/grid-bot-recovery.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grid.Bot.Recovery", "src\Grid.Bot.Recovery.csproj", "{A11EDCF5-569F-4596-B87B-63BC5BF71084}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(SolutionProperties) = preSolution 14 | HideSolutionNode = FALSE 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {A11EDCF5-569F-4596-B87B-63BC5BF71084}.Debug|Any CPU.ActiveCfg = debug|Any CPU 18 | {A11EDCF5-569F-4596-B87B-63BC5BF71084}.Debug|Any CPU.Build.0 = debug|Any CPU 19 | {A11EDCF5-569F-4596-B87B-63BC5BF71084}.Release|Any CPU.ActiveCfg = release|Any CPU 20 | {A11EDCF5-569F-4596-B87B-63BC5BF71084}.Release|Any CPU.Build.0 = release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /services/recovery/src/Events/OnLogMessage.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Events; 2 | 3 | using System; 4 | using System.Net.WebSockets; 5 | using System.Threading.Tasks; 6 | 7 | using Discord; 8 | using Discord.Net; 9 | using Discord.WebSocket; 10 | 11 | using Prometheus; 12 | 13 | using Logging; 14 | 15 | /// 16 | /// Event invoked when Discord.Net creates a log message. 17 | /// 18 | public class OnLogMessage 19 | { 20 | private readonly Logger _logger = new( 21 | name: "discord", 22 | logLevelGetter: () => LogLevel.Debug, 23 | logToConsole: true, 24 | logToFileSystem: false 25 | ); 26 | 27 | private readonly Counter _totalLogMessages = Metrics.CreateCounter( 28 | "grid_discord_log_messages_total", 29 | "The total number of log messages.", 30 | "log_severity" 31 | ); 32 | 33 | /// 34 | /// Invoke the event handler. 35 | /// 36 | /// The 37 | public Task Invoke(LogMessage message) 38 | { 39 | _totalLogMessages.WithLabels(message.Severity.ToString()).Inc(); 40 | 41 | if (message.Exception != null) 42 | { 43 | if (message.Exception is GatewayReconnectException) 44 | return Task.CompletedTask; 45 | 46 | // Closed web socket exceptions are expected when the bot is shutting down. 47 | if (message.Exception.InnerException is WebSocketException) 48 | return Task.CompletedTask; 49 | 50 | if (message.Exception is WebSocketClosedException || message.Exception.InnerException is WebSocketClosedException) 51 | return Task.CompletedTask; 52 | 53 | if (message.Exception is TaskCanceledException) 54 | return Task.CompletedTask; 55 | 56 | _logger.Error( 57 | "Source = {0}, Message = {1}, Exception = {2}", 58 | message.Source, 59 | message.Message, 60 | message.Exception.ToString() 61 | ); 62 | 63 | return Task.CompletedTask; 64 | } 65 | 66 | switch (message) 67 | { 68 | case { Severity: LogSeverity.Warning }: 69 | _logger.Warning("{0}: {1}", message.Source, message.Message); 70 | break; 71 | case { Severity: LogSeverity.Debug }: 72 | _logger.Debug("{0}: {1}", message.Source, message.Message); 73 | break; 74 | case { Severity: LogSeverity.Info }: 75 | _logger.Information("{0}: {1}", message.Source, message.Message); 76 | break; 77 | case { Severity: LogSeverity.Verbose }: 78 | _logger.Debug("{0}: {1}", message.Source, message.Message); 79 | break; 80 | case { Severity: LogSeverity.Error | LogSeverity.Critical }: 81 | _logger.Error("{0}: {1}", message.Source, message.Message); 82 | break; 83 | default: 84 | throw new ArgumentOutOfRangeException(); 85 | } 86 | 87 | return Task.CompletedTask; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /services/recovery/src/Events/OnMessage.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Events; 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Text.RegularExpressions; 7 | 8 | #if DEBUG 9 | using System.Reflection; 10 | #endif 11 | 12 | using Discord; 13 | using Discord.Commands; 14 | using Discord.WebSocket; 15 | 16 | using Logging; 17 | 18 | using Prometheus; 19 | 20 | /// 21 | /// Event handler for messages. 22 | /// 23 | /// 24 | /// Construct a new instance of . 25 | /// 26 | /// The . 27 | /// The . 28 | /// 29 | /// - cannot be null. 30 | /// - cannot be null. 31 | /// 32 | public partial class OnMessage( 33 | ISettings settings, 34 | ILogger logger 35 | ) 36 | { 37 | // language=regex 38 | private const string _allowedCommandRegex = @"^[a-zA-Z-]*$"; 39 | 40 | [GeneratedRegex(_allowedCommandRegex)] 41 | private static partial Regex GetAllowedCommandRegex(); 42 | 43 | private readonly ISettings _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 44 | private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 45 | 46 | private readonly Counter _totalMessagesProcessed = Metrics.CreateCounter( 47 | "grid_messages_processed_total", 48 | "The total number of messages processed." 49 | ); 50 | 51 | /// 52 | /// Invoke the event handler. 53 | /// 54 | /// The 55 | public async Task Invoke(SocketMessage rawMessage) 56 | { 57 | if (rawMessage is not SocketUserMessage message) return; 58 | if (message.Author.IsBot) return; 59 | 60 | _totalMessagesProcessed.Inc(); 61 | 62 | int argPos = 0; 63 | 64 | if (!message.HasStringPrefix(_settings.BotPrefix, ref argPos, StringComparison.OrdinalIgnoreCase)) return; 65 | 66 | // Get the name of the command that was used. 67 | var commandName = message.Content.Split(' ')[0]; 68 | if (string.IsNullOrEmpty(commandName)) return; 69 | 70 | commandName = commandName[argPos..]; 71 | if (string.IsNullOrEmpty(commandName)) return; 72 | if (!GetAllowedCommandRegex().IsMatch(commandName)) return; 73 | if (!_settings.PreviousPhaseCommands.Contains(commandName.ToLowerInvariant())) return; 74 | 75 | _logger.Warning( 76 | "User tried to use previous phase command '{0}'.", 77 | commandName 78 | ); 79 | 80 | #if DEBUG 81 | 82 | var entryAssembly = Assembly.GetEntryAssembly(); 83 | var informationalVersion = entryAssembly?.GetCustomAttribute()?.InformationalVersion; 84 | 85 | if (string.IsNullOrEmpty(informationalVersion)) 86 | informationalVersion = entryAssembly?.GetName().Version?.ToString(); 87 | 88 | if (!string.IsNullOrEmpty(informationalVersion)) 89 | await message.Channel.SendMessageAsync($"Debug build running version {informationalVersion}."); 90 | 91 | #endif 92 | 93 | var failureMessage = _settings.MaintenanceStatusMessage; 94 | 95 | var embed = new EmbedBuilder() 96 | .WithTitle("Maintenance Enabled") 97 | .WithColor(Color.Red) 98 | .WithCurrentTimestamp(); 99 | 100 | if (!string.IsNullOrEmpty(failureMessage)) 101 | embed.WithDescription(failureMessage); 102 | 103 | await message.ReplyAsync("Maintenance is currently enabled, please try again later.", embeds: [embed.Build()]); 104 | 105 | return; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /services/recovery/src/Events/OnReady.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Events; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Discord; 7 | using Discord.WebSocket; 8 | 9 | using Logging; 10 | 11 | using Threading; 12 | 13 | /// 14 | /// Event handler to be invoked when a shard is ready, 15 | /// 16 | /// 17 | /// Construct a new instance of . 18 | /// 19 | /// The . 20 | /// The . 21 | /// The . 22 | /// The . 23 | /// The . 24 | /// 25 | /// - cannot be null. 26 | /// - cannot be null. 27 | /// - cannot be null. 28 | /// - cannot be null. 29 | /// - cannot be null. 30 | /// 31 | public class OnShardReady( 32 | ISettings settings, 33 | ILogger logger, 34 | DiscordShardedClient client, 35 | OnMessage onMessageEvent, 36 | OnInteraction onInteractionEvent 37 | ) 38 | { 39 | private Atomic _shardCount = 0; // needs to be atomic due to the race situation here. 40 | 41 | private readonly ISettings _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 42 | private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 43 | private readonly DiscordShardedClient _client = client ?? throw new ArgumentNullException(nameof(client)); 44 | 45 | private readonly OnMessage _onMessageEvent = onMessageEvent ?? throw new ArgumentNullException(nameof(onMessageEvent)); 46 | private readonly OnInteraction _onInteractionEvent = onInteractionEvent ?? throw new ArgumentNullException(nameof(onInteractionEvent)); 47 | 48 | private static string GetStatusText(string updateText) 49 | => string.IsNullOrEmpty(updateText) ? "Maintenance is enabled" : $"Maintenance is enabled: {updateText}"; 50 | 51 | /// 52 | /// Invoe the event handler. 53 | /// 54 | /// The client for the shard. 55 | public Task Invoke(DiscordSocketClient shard) 56 | { 57 | _shardCount++; 58 | 59 | _logger.Debug( 60 | "Shard '{0}' ready as '{0}#{1}'", 61 | shard.ShardId, 62 | _client.CurrentUser.Username, 63 | _client.CurrentUser.Discriminator 64 | ); 65 | 66 | if (_shardCount == _client.Shards.Count) 67 | { 68 | _shardCount = 0; 69 | 70 | _logger.Debug("Final shard ready!"); 71 | 72 | _client.MessageReceived += _onMessageEvent.Invoke; 73 | _client.InteractionCreated += _onInteractionEvent.Invoke; 74 | 75 | var text = _settings.MaintenanceStatusMessage; 76 | 77 | _client.SetStatusAsync(UserStatus.DoNotDisturb); 78 | _client.SetGameAsync(GetStatusText(text)); 79 | 80 | } 81 | 82 | return Task.CompletedTask; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /services/recovery/src/Events/OnSlashCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot.Events; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using System.Collections.Generic; 6 | 7 | using Discord; 8 | using Discord.WebSocket; 9 | 10 | using Prometheus; 11 | 12 | using Logging; 13 | 14 | /// 15 | /// Event handler for interactions. 16 | /// 17 | /// 18 | /// Construct a new instance of . 19 | /// 20 | /// The . 21 | /// The . 22 | /// 23 | /// - cannot be null. 24 | /// - cannot be null. 25 | /// 26 | public class OnInteraction( 27 | ISettings settings, 28 | ILogger logger 29 | ) 30 | { 31 | private readonly ISettings _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 32 | private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 33 | 34 | private readonly Counter _totalInteractionsProcessed = Metrics.CreateCounter( 35 | "grid_interactions_processed_total", 36 | "The total number of interactions processed.", 37 | "interaction_type" 38 | ); 39 | 40 | /// 41 | /// Invoke the event handler. 42 | /// 43 | /// The . 44 | public async Task Invoke(SocketInteraction interaction) 45 | { 46 | if (interaction.User.IsBot) return; 47 | 48 | _totalInteractionsProcessed.WithLabels( 49 | interaction.Type.ToString() 50 | ).Inc(); 51 | 52 | _logger.Information( 53 | "User tried to use interaction '{0}'", 54 | interaction.ToString() 55 | ); 56 | 57 | await interaction.DeferAsync(); 58 | 59 | var failureMessage = _settings.MaintenanceStatusMessage; 60 | 61 | var builder = new EmbedBuilder() 62 | .WithTitle("Maintenance Enabled") 63 | .WithColor(Color.Red) 64 | .WithCurrentTimestamp(); 65 | 66 | var embeds = new List(); 67 | 68 | if (!string.IsNullOrEmpty(failureMessage)) 69 | { 70 | builder.WithDescription(failureMessage); 71 | 72 | embeds.Add(builder.Build()); 73 | } 74 | 75 | await interaction.FollowupAsync("Maintenance is currently enabled, please try again later.", embeds: embeds.ToArray()); 76 | 77 | return; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /services/recovery/src/Grid.Bot.Recovery.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | $(NoWarn);4014 4 | 5 | 6 | 7 | debug;release 8 | Exe 9 | 10 | True 11 | 12 | 13 | 14 | Recovery executable for the MFDLABS grid-bot. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | all 45 | runtime; build; native; contentfiles; analyzers; buildtransitive 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /services/recovery/src/Implementation/BotManager.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Discord; 7 | using Discord.WebSocket; 8 | 9 | using Logging; 10 | 11 | using Events; 12 | 13 | /// 14 | /// Manager for the bot. 15 | /// 16 | public class BotManager : IBotManager 17 | { 18 | private readonly ISettings _settings; 19 | private readonly ILogger _logger; 20 | private readonly DiscordShardedClient _client; 21 | 22 | private readonly OnShardReady _onReady; 23 | private readonly OnLogMessage _onLogMessage; 24 | 25 | /// 26 | /// Construct a new instance of . 27 | /// 28 | /// The . 29 | /// The . 30 | /// The . 31 | /// The . 32 | /// The . 33 | /// 34 | /// - cannot be null. 35 | /// - cannot be null. 36 | /// - cannot be null. 37 | /// - cannot be null. 38 | /// 39 | public BotManager( 40 | ISettings settings, 41 | ILogger logger, 42 | DiscordShardedClient client, 43 | OnShardReady onReady, 44 | OnLogMessage onLogMessage 45 | ) 46 | { 47 | _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 48 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 49 | _client = client ?? throw new ArgumentNullException(nameof(client)); 50 | _onReady = onReady ?? throw new ArgumentNullException(nameof(onReady)); 51 | _onLogMessage = onLogMessage ?? throw new ArgumentNullException(nameof(onLogMessage)); 52 | 53 | _client.ShardReady += _onReady.Invoke; 54 | _client.Log += _onLogMessage.Invoke; 55 | } 56 | 57 | /// 58 | /// Start the bot. 59 | /// 60 | /// A representing the asynchronous operation. 61 | public async Task StartAsync() 62 | { 63 | try 64 | { 65 | await _client.LoginAsync(TokenType.Bot, _settings.BotToken); 66 | await _client.StartAsync(); 67 | } 68 | catch (Exception ex) 69 | { 70 | _logger.Error("Error starting bot: {0}", ex.Message); 71 | } 72 | } 73 | 74 | /// 75 | /// Stop the bot. 76 | /// 77 | /// A representing the asynchronous operation. 78 | public async Task StopAsync() 79 | { 80 | try 81 | { 82 | await _client.StopAsync(); 83 | await _client.LogoutAsync(); 84 | } 85 | catch (Exception ex) 86 | { 87 | _logger.Error("Error stopping bot: {0}", ex.Message); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /services/recovery/src/Implementation/DiscordWebhookAlertManager.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | using System.Text; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | using System.Collections.Generic; 9 | 10 | using Discord; 11 | 12 | using Newtonsoft.Json; 13 | 14 | using Logging; 15 | using Networking; 16 | 17 | /// 18 | /// Handles sending alerts to a Discord webhook. 19 | /// 20 | /// 21 | /// 22 | /// Creates a new instance of the class. 23 | /// 24 | /// The to use. 25 | /// The to use. 26 | /// The to use. 27 | /// The to use. 28 | /// /// 29 | /// - cannot be null. 30 | /// - cannot be null. 31 | /// - cannot be null. 32 | /// - cannot be null. 33 | /// 34 | /// 35 | public class DiscordWebhookAlertManager( 36 | ILocalIpAddressProvider localIpAddressProvider, 37 | IHttpClientFactory httpClientFactory, 38 | ISettings settings, 39 | ILogger logger 40 | ) : IDiscordWebhookAlertManager 41 | { 42 | private readonly ILocalIpAddressProvider _localIpAddressProvider = localIpAddressProvider ?? throw new ArgumentNullException(nameof(localIpAddressProvider)); 43 | private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); 44 | private readonly ISettings _settings = settings ?? throw new ArgumentNullException(nameof(settings)); 45 | private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 46 | 47 | /// 48 | public async Task SendAlertAsync(string topic, string message, Color? color, IEnumerable attachments = null) 49 | { 50 | if (string.IsNullOrWhiteSpace(topic)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(topic)); 51 | if (string.IsNullOrWhiteSpace(message)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); 52 | 53 | color ??= Color.Red; 54 | 55 | // username based off machine info 56 | var username = $"Grid Bot Recovery {Environment.MachineName} ({_localIpAddressProvider.AddressV4} / {_localIpAddressProvider.AddressV6})"; 57 | 58 | var content = string.Empty; 59 | if (_settings.AlertRoleId != default(ulong)) 60 | content = $"<@&{_settings.AlertRoleId}>"; 61 | 62 | using var client = _httpClientFactory.CreateClient(); 63 | var url = _settings.DiscordWebhookUrl; 64 | var payload = new 65 | { 66 | username, 67 | content, 68 | embeds = new[] 69 | { 70 | new 71 | { 72 | title = topic, 73 | description = message, 74 | color = color?.RawValue, 75 | timestamp = DateTime.UtcNow.ToString("o") 76 | } 77 | } 78 | }; 79 | 80 | var multipartContent = new MultipartFormDataContent(); 81 | 82 | var json = JsonConvert.SerializeObject(payload); 83 | 84 | multipartContent.Add(new StringContent(json, Encoding.UTF8, "application/json"), "payload_json"); 85 | 86 | if (attachments?.Any() ?? false) 87 | foreach (var attachment in attachments) 88 | multipartContent.Add(new StreamContent(attachment.Stream), attachment.FileName, attachment.FileName); 89 | 90 | try 91 | { 92 | await client.PostAsync(url, multipartContent); 93 | } 94 | catch (Exception ex) 95 | { 96 | _logger.Error("Error sending alert: {0}", ex.Message); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /services/recovery/src/Implementation/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | using Logging; 6 | using Configuration; 7 | 8 | /// 9 | /// Settings for the recovery service. 10 | /// 11 | public class Settings : EnvironmentProvider, ISettings 12 | { 13 | /// 14 | public string GridBotEndpoint => GetOrDefault( 15 | nameof(GridBotEndpoint), 16 | () => throw new ApplicationException($"{nameof(GridBotEndpoint)} is required.") 17 | ); 18 | 19 | /// 20 | public string BotToken => GetOrDefault( 21 | nameof(BotToken), 22 | () => throw new ApplicationException($"{nameof(BotToken)} is required.") 23 | ); 24 | 25 | /// 26 | public string MaintenanceStatusMessage => GetOrDefault( 27 | nameof(MaintenanceStatusMessage), 28 | "Service experiencing issues, please try again later." 29 | ); 30 | 31 | /// 32 | public string BotPrefix => GetOrDefault(nameof(BotPrefix), ">"); 33 | 34 | /// 35 | public string[] PreviousPhaseCommands => GetOrDefault(nameof(PreviousPhaseCommands), Array.Empty()); 36 | 37 | /// 38 | public string DefaultLoggerName => GetOrDefault(nameof(DefaultLoggerName), "recovery"); 39 | 40 | /// 41 | public LogLevel DefaultLoggerLevel => GetOrDefault(nameof(DefaultLoggerLevel), LogLevel.Information); 42 | 43 | /// 44 | public int MetricsServerPort => GetOrDefault(nameof(MetricsServerPort), 8080); 45 | 46 | /// 47 | public bool StandaloneMode => GetOrDefault(nameof(StandaloneMode), false); 48 | 49 | /// 50 | public TimeSpan BotCheckWorkerDelay => GetOrDefault(nameof(BotCheckWorkerDelay), TimeSpan.FromSeconds(15)); 51 | 52 | /// 53 | public int MaxContinuousFailures => GetOrDefault(nameof(MaxContinuousFailures), 2); 54 | 55 | /// 56 | public ulong AlertRoleId => GetOrDefault(nameof(AlertRoleId), default(ulong)); 57 | 58 | /// 59 | public string DiscordWebhookUrl => GetOrDefault( 60 | nameof(DiscordWebhookUrl), 61 | () => throw new ApplicationException($"{nameof(DiscordWebhookUrl)} is required.") 62 | ); 63 | 64 | /// 65 | public bool GrpcClientUseTls => GetOrDefault(nameof(GrpcClientUseTls), false); 66 | } 67 | -------------------------------------------------------------------------------- /services/recovery/src/Interfaces/IBotManager.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System.Threading.Tasks; 4 | 5 | /// 6 | /// Manager for the bot. 7 | /// 8 | public interface IBotManager 9 | { 10 | /// 11 | /// Start the bot. 12 | /// 13 | Task StartAsync(); 14 | 15 | /// 16 | /// Stop the bot. 17 | /// 18 | Task StopAsync(); 19 | } 20 | -------------------------------------------------------------------------------- /services/recovery/src/Interfaces/IDiscordWebhookAlertManager.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | 6 | using Discord; 7 | 8 | /// 9 | /// Handles sending alerts to a Discord webhook. 10 | /// 11 | public interface IDiscordWebhookAlertManager 12 | { 13 | /// 14 | /// Sends an alert to the Discord webhook. 15 | /// 16 | /// The topic of the alert. 17 | /// The message of the alert. 18 | /// The color of the alert. 19 | /// The attachments of the alert. 20 | /// A representing the asynchronous operation. 21 | Task SendAlertAsync(string topic, string message, Color? color = null, IEnumerable attachments = null); 22 | } 23 | -------------------------------------------------------------------------------- /services/recovery/src/Interfaces/ISettings.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | 5 | using Logging; 6 | 7 | /// 8 | /// Settings for the recovery service. 9 | /// 10 | public interface ISettings 11 | { 12 | /// 13 | /// Gets the endpoint for the gRPC grid-bot service. 14 | /// 15 | string GridBotEndpoint { get; } 16 | 17 | /// 18 | /// Gets the token for the bot. 19 | /// 20 | string BotToken { get; } 21 | 22 | /// 23 | /// Gets the maintenance status message. 24 | /// 25 | string MaintenanceStatusMessage { get; } 26 | 27 | /// 28 | /// Gets the bot prefix (previous phase commands). 29 | /// 30 | string BotPrefix { get; } 31 | 32 | /// 33 | /// Gets the list of previous phase commands. 34 | /// 35 | string[] PreviousPhaseCommands { get; } 36 | 37 | /// 38 | /// Default logger name. 39 | /// 40 | string DefaultLoggerName { get; } 41 | 42 | /// 43 | /// Default logger level. 44 | /// 45 | LogLevel DefaultLoggerLevel { get; } 46 | 47 | /// 48 | /// Gets the metrics server port. 49 | /// 50 | int MetricsServerPort { get; } 51 | 52 | /// 53 | /// Determines if standalone mode is enabled. 54 | /// 55 | bool StandaloneMode { get; } 56 | 57 | /// 58 | /// Gets the delay between each bot check worker run. 59 | /// 60 | TimeSpan BotCheckWorkerDelay { get; } 61 | 62 | /// 63 | /// Gets the max amount of continuous failures before the bot check worker enables maintenance mode. 64 | /// 65 | int MaxContinuousFailures { get; } 66 | 67 | /// 68 | /// Gets role Id for the alert role. 69 | /// 70 | ulong AlertRoleId { get; } 71 | 72 | /// 73 | /// Gets the Discord webhook URL for alerts. 74 | /// 75 | string DiscordWebhookUrl { get; } 76 | 77 | /// 78 | /// Determines if the gRPC client should use TLS. 79 | /// 80 | bool GrpcClientUseTls { get; } 81 | } 82 | -------------------------------------------------------------------------------- /services/recovery/src/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Grid.Bot; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | /// 8 | /// Main entry point. 9 | /// 10 | public static class Program 11 | { 12 | private static bool AssemblyIsLoaded(string name) 13 | { 14 | try 15 | { 16 | return AppDomain.CurrentDomain.Load(name) != null; 17 | } 18 | // We assume this means that it's already loaded into another evidence 19 | catch (FileLoadException) { return true; } 20 | catch (Exception) { return false; } 21 | } 22 | 23 | /// 24 | /// Main method. 25 | /// 26 | /// The arguments. 27 | public static void Main(string[] args) 28 | { 29 | AppDomain.CurrentDomain.UnhandledException += (_, e) => 30 | { 31 | if (e.ExceptionObject is FileNotFoundException or TypeLoadException) 32 | { 33 | Console.ForegroundColor = ConsoleColor.Yellow; 34 | Console.WriteLine( 35 | "There was an error loading a type or dependency, please review the following error: {0}", 36 | (e.ExceptionObject as Exception)?.Message 37 | ); 38 | Console.ResetColor(); 39 | Console.WriteLine("Press any key to exit..."); 40 | Console.ReadKey(true); 41 | Environment.Exit(1); 42 | return; 43 | } 44 | 45 | Console.ForegroundColor = ConsoleColor.Red; 46 | Console.WriteLine("[URGENT]: Unhandled global exception occurred: {0}", e.ExceptionObject); 47 | Console.ResetColor(); 48 | 49 | if (e.ExceptionObject is AggregateException aggregate) 50 | { 51 | if (aggregate.InnerExceptions.Any(x => x is InvalidOperationException)) 52 | { 53 | Console.WriteLine("Press any key to exit..."); 54 | Console.ReadKey(true); 55 | Environment.Exit(1); 56 | } 57 | } 58 | }; 59 | 60 | Runner.Invoke(args); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /targets/git-metadata.targets: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 25 | 27 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------