├── .env.example ├── .github ├── dependabot.yml ├── labeler.yml └── workflows │ ├── codeql-analysis.yml │ ├── commands.yaml │ ├── label.yml │ ├── nodejs.yml │ └── translations.yml ├── .gitignore ├── .gitmodules ├── .npmrc ├── LICENSE.md ├── PRIVACY.md ├── README.md ├── bot.js ├── cmds ├── eval.js ├── get.js ├── help.js ├── info.js ├── invite.js ├── link.js ├── minecraft │ ├── bug.js │ ├── command.js │ ├── commands.json │ └── syntax.js ├── patreon.js ├── pause.js ├── rcscript.js ├── say.js ├── settings.js ├── stop.js ├── test.js ├── verification.js ├── verify.js └── wiki │ ├── diff.js │ ├── discussion.js │ ├── functions.js │ ├── general.js │ ├── overview.js │ ├── random.js │ ├── search.js │ ├── special_page.js │ └── user.js ├── dashboard ├── beta │ └── example.js ├── functions.js ├── functions │ ├── rcscript.js │ ├── settings.js │ ├── user.js │ └── verification.js ├── globals.js ├── guilds.js ├── i18n.js ├── i18n │ ├── allLangs.json │ ├── bn.json │ ├── de.json │ ├── el.json │ ├── en.json │ ├── es.json │ ├── et.json │ ├── fr.json │ ├── hi.json │ ├── id.json │ ├── it.json │ ├── ja.json │ ├── ko.json │ ├── lzh.json │ ├── nl.json │ ├── pl.json │ ├── pt-br.json │ ├── ru.json │ ├── sr.json │ ├── sv.json │ ├── th.json │ ├── tr.json │ ├── uk.json │ ├── vi.json │ ├── zh-hans.json │ └── zh-hant.json ├── index.html ├── index.js ├── login.html ├── oauth.js ├── src │ ├── channel.svg │ ├── discord.svg │ ├── icon.png │ ├── index.css │ ├── index.js │ ├── lang.js │ ├── language.svg │ └── settings.svg └── util.js ├── database.js ├── functions ├── global_block.js ├── helpserver.js ├── helpsetup.js ├── parse_page.js ├── phabricator.js ├── rcscript_buttons.js └── verify.js ├── i18n ├── _new.json ├── allLangs.json ├── bn.json ├── de.json ├── el.json ├── en.json ├── es.json ├── et.json ├── fr.json ├── hi.json ├── id.json ├── it.json ├── ja.json ├── ko.json ├── lzh.json ├── nl.json ├── pl.json ├── pt-br.json ├── ru.json ├── sr.json ├── sv.json ├── th.json ├── tr.json ├── uk.json ├── vi.json ├── widgets │ ├── bn.png │ ├── de.png │ ├── el.png │ ├── en.png │ ├── es.png │ ├── fr.png │ ├── hi.png │ ├── id.png │ ├── it.png │ ├── ja.png │ ├── ko.png │ ├── lzh.png │ ├── nl.png │ ├── pl.png │ ├── pt-br.png │ ├── ru.png │ ├── sr.png │ ├── sv.png │ ├── th.png │ ├── tr.png │ ├── uk.png │ ├── vi.png │ ├── zh-hans.png │ └── zh-hant.png ├── zh-hans.json └── zh-hant.json ├── interactions ├── admin.js ├── commands │ ├── admin.json │ ├── diff.json │ ├── inline.json │ ├── interwiki.json │ ├── overview.json │ ├── patreon.json │ ├── random.json │ ├── user.json │ ├── verify.json │ └── wiki.json ├── diff.js ├── i18n │ ├── bn.json │ ├── de.json │ ├── el.json │ ├── en.json │ ├── es-ES.json │ ├── et.json │ ├── fr.json │ ├── hi.json │ ├── id.json │ ├── it.json │ ├── ja.json │ ├── ko.json │ ├── lzh.json │ ├── nl.json │ ├── pl.json │ ├── pt-BR.json │ ├── ru.json │ ├── sr.json │ ├── sv-SE.json │ ├── th.json │ ├── tr.json │ ├── uk.json │ ├── vi.json │ ├── zh-CN.json │ └── zh-TW.json ├── inline.js ├── interwiki.js ├── overview.js ├── patreon.js ├── random.js ├── user.js ├── verify.js ├── verify_again.js └── wiki.js ├── main.js ├── package-lock.json ├── package.json └── util ├── database.js ├── default.json ├── defaults.js ├── edit_diff.js ├── extract_desc.js ├── functions.js ├── globals.js ├── i18n.js ├── logging.js ├── newMessage.js └── wiki.js /.env.example: -------------------------------------------------------------------------------- 1 | # Example .env file for selfhosting the bot 2 | # Don't add your .env file to your github repository, these settings are used to access the bot 3 | 4 | 5 | # Your bot token to login your bot 6 | token="" 7 | # Your bots user ID 8 | bot="461189216198590464" 9 | # Your bots client secret for the dashboard 10 | secret="" 11 | # Optional: Your bots return uri for the dashboard 12 | dashboard="http://localhost:8080/oauth" 13 | # Your user ID for owner only commands 14 | owner="243478237710123009" 15 | # Command prefix for the bot 16 | prefix="!wiki " 17 | # Channel where to change the patreon settings 18 | channel="464098946894004224" 19 | # Invite link to the bot help Discord 20 | invite="https://discord.gg/v77RTk5" 21 | # Link to the patreon page for the bot 22 | patreon="https://www.patreon.com/WikiBot" 23 | # Optional: API token for phabricator.wikimedia.org 24 | phabricator_wikimedia="" 25 | # Optional: API token for phabricator.miraheze.org 26 | phabricator_miraheze="" 27 | # Optional: Client ID for Wikimedia OAuth2 consumer 28 | oauth_wikimedia="" 29 | # Optional: Client secret for Wikimedia OAuth2 consumer 30 | oauth_wikimedia_secret="" 31 | # Optional: Client ID for Miraheze OAuth2 consumer 32 | oauth_miraheze="" 33 | # Optional: Client secret for Miraheze OAuth2 consumer 34 | oauth_miraheze_secret="" 35 | # Optional: Path to a log file for usage statistics 36 | usagelog="" 37 | # Optional: Set to anything to reveal guild IDs to the wiki in the X-Origin-Guild header for API requests. 38 | x_origin_guild="" 39 | 40 | # Optional: Authorization header for sending RcGcDw buttons interactions. 41 | buttons_token="" 42 | # Optional: URL to send RcGcDw buttons interactions to. 43 | buttons_url="http://localhost:8800/interactions" 44 | 45 | # Variables for your PostgreSQL server 46 | PGHOST="localhost" 47 | PGUSER="postgres" 48 | PGDATABASE="postgres" 49 | PGPASSWORD="postgres" 50 | PGPORT="5432" 51 | # PGSSL="true" -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | # Maintain dependencies for npm 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | # Maintain dependencies for RcGcDb and RcGcDw buttons 14 | - package-ecosystem: "gitsubmodule" 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Add 'translation' label to any change to i18n files 2 | translation: 3 | - changed-files: 4 | - any-glob-to-any-file: 5 | - 'i18n/*.json' 6 | - 'dashboard/i18n/*.json' 7 | - 'interactions/i18n/*.json' 8 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '39 12 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | if: ${{ github.head_ref != 'translations' }} 28 | permissions: 29 | actions: read 30 | contents: read 31 | security-events: write 32 | 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | language: [ 'javascript' ] 37 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 38 | # Learn more: 39 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v3 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v3 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 https://git.io/JvXDl 62 | 63 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 64 | # and modify them (or add more) to build your code if your project 65 | # uses a compiled language 66 | 67 | #- run: | 68 | # make bootstrap 69 | # make release 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v3 73 | -------------------------------------------------------------------------------- /.github/workflows/commands.yaml: -------------------------------------------------------------------------------- 1 | name: Update slash command localization 2 | on: 3 | push: 4 | branches: [ master ] 5 | paths: 6 | - 'interactions/i18n/*.json' 7 | jobs: 8 | translations: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Load Repository 12 | uses: actions/checkout@v4 13 | with: 14 | token: ${{ secrets.WIKIBOT_TOKEN }} 15 | - name: Update slash commands 16 | run: | 17 | validlang=("en" "ja" "fr" "zh-TW" "no" "ro" "da" "ko" "nl" "ru" "uk" "el" "sv-SE" "hr" "pl" "tr" "vi" "hi" "zh-CN" "es-ES" "hu" "en-GB" "pt-BR" "de" "lt" "en-US" "cs" "fi" "bg" "th" "it") 18 | for language in interactions/i18n/*.json 19 | do 20 | language=$(basename $language .json) 21 | if [[ ! " ${validlang[*]} " =~ " ${language} " ]] 22 | then 23 | echo $language 24 | continue 25 | fi 26 | for file in `jq -r 'keys[]' interactions/i18n/$language.json` 27 | do 28 | if [[ "$file" =~ ^_ ]] 29 | then 30 | continue 31 | fi 32 | echo $file 33 | for key in `jq -r ".$file | keys[]" interactions/i18n/$language.json` 34 | do 35 | echo $key 36 | content="`jq \".$file.\\\"$key\\\"\" interactions/i18n/$language.json`" 37 | if ! [[ "$key" =~ description$ || "$key" =~ choices\[[0-9]+\]\.name$ ]] 38 | then 39 | content=${content,,} 40 | fi 41 | echo $content 42 | if [[ "$language" = "en" ]] 43 | then 44 | if [[ "$key" =~ description$ || "$key" =~ choices\[[0-9]+\]\.name$ ]] 45 | then 46 | echo "`jq \".$key=$content\" interactions/commands/$file.json --tab`" > interactions/commands/$file.json 47 | fi 48 | echo "`jq \".${key}_localizations.\\\"en-GB\\\"=$content\" interactions/commands/$file.json --tab`" > interactions/commands/$file.json 49 | echo "`jq \".${key}_localizations.\\\"en-US\\\"=$content\" interactions/commands/$file.json --tab`" > interactions/commands/$file.json 50 | else 51 | echo "`jq \".${key}_localizations.\\\"$language\\\"=$content\" interactions/commands/$file.json --tab`" > interactions/commands/$file.json 52 | fi 53 | done 54 | done 55 | done 56 | - name: Commit changes 57 | uses: EndBug/add-and-commit@v9 58 | with: 59 | author_name: WikiBot-bot 60 | author_email: 69196528+WikiBot-bot@users.noreply.github.com 61 | committer_name: WikiBot-bot 62 | committer_email: 69196528+WikiBot-bot@users.noreply.github.com 63 | message: "Update slash command localization" 64 | add: "interactions/commands/*.json" 65 | github_token: ${{ secrets.WIKIBOT_TOKEN }} 66 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | name: "Label pull requests" 2 | on: 3 | - pull_request_target 4 | 5 | jobs: 6 | labeler: 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/labeler@v5 13 | with: 14 | repo-token: "${{ secrets.WIKIBOT_TOKEN }}" 15 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | workflow_dispatch: 6 | jobs: 7 | runner-job: 8 | runs-on: ubuntu-latest 9 | if: ${{ github.head_ref != 'translations' }} 10 | services: 11 | postgres: 12 | image: postgres 13 | env: 14 | POSTGRES_PASSWORD: postgres 15 | options: >- 16 | --health-cmd pg_isready 17 | --health-interval 10s 18 | --health-timeout 5s 19 | --health-retries 5 20 | ports: 21 | - 5432:5432 22 | strategy: 23 | matrix: 24 | node-version: [23.6.1] 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | - run: npm i -g npm@latest 32 | - run: npm ci 33 | - run: npm run build --if-present 34 | - run: sed -i -e 's/""/"${{secrets.DISCORD_TOKEN}}"/g' .env 35 | - run: sed -i -e 's/""/"${{secrets.DISCORD_SECRET}}"/g' .env 36 | - run: sed -i -e 's/"!wiki "/"!test "/g' .env 37 | - run: npm test -- --timeout:60 38 | timeout-minutes: 5 39 | -------------------------------------------------------------------------------- /.github/workflows/translations.yml: -------------------------------------------------------------------------------- 1 | name: Update translation widgets 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | languages: 6 | description: 'Languages to update translation widgets for.' 7 | required: true 8 | default: 'i18n/*.json' 9 | jobs: 10 | translations: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Load Repository 14 | uses: actions/checkout@v4 15 | with: 16 | token: ${{ secrets.WIKIBOT_TOKEN }} 17 | - name: Update Widgets 18 | run: | 19 | sudo apt update 20 | sudo apt-get install inkscape 21 | for language in ${{ github.event.inputs.languages }} 22 | do 23 | language=$(basename $language .json) 24 | if [[ "$language" =~ [^[:lower:]-] ]] 25 | then 26 | echo "$language" is not a translation 27 | else 28 | wget https://translate.wikibot.de/widget/wiki-bot/discord/$language/svg-badge.svg 29 | convert -background none svg-badge.svg i18n/widgets/$language.png 30 | rm svg-badge.svg 31 | fi 32 | done 33 | - name: Commit changes 34 | uses: EndBug/add-and-commit@v9 35 | with: 36 | author_name: WikiBot-bot 37 | author_email: 69196528+WikiBot-bot@users.noreply.github.com 38 | committer_name: WikiBot-bot 39 | committer_email: 69196528+WikiBot-bot@users.noreply.github.com 40 | message: "Update translation widgets" 41 | add: "i18n/widgets/*.png" 42 | github_token: ${{ secrets.WIKIBOT_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.env 3 | *.db 4 | *.sql 5 | *.log 6 | *.code-workspace 7 | *.bat -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "RcGcDb"] 2 | path = RcGcDb 3 | url = https://gitlab.com/chicken-riders/RcGcDb.git 4 | branch = master 5 | [submodule "rcgcdw-buttons"] 6 | path = RcGcDw_buttons 7 | url = https://github.com/Markus-Rost/rcgcdw-buttons.git 8 | branch = master 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, MarkusRost 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | This privacy policy only refers to the Discord bots `Wiki-Bot#2998` (ID: `461189216198590464`) and `Wiki-Bot (Test)#4262` (ID: `432468082175246351`). 3 | 4 | ## Private data 5 | The bot does not collect, save or share any private data. 6 | 7 | ## Other data 8 | The bot needs to collect and save some data in order to function properly: 9 | * **Wiki Accounts**: Wiki accounts connected using [OAuth2](https://www.mediawiki.org/wiki/Extension:OAuth) are saved for easier verification. Using the [Dashboard](https://settings.wikibot.de/user), users can disconnect their accounts again or disable saving the connection completely. 10 | * **Settings**: Modified guild settings are saved as long as the bot is a member of that guild and deleted shortly after the bot leaves the guild. 11 | * **Supporters**: The bot maintains a list of translators and [Patreon supporters](https://www.patreon.com/WikiBot) to provide some extra functionality for them. 12 | * **Commands**: Commands are logged together with the guild id or user id for up to one week for debugging purposes. 13 | 14 | The bot does not share any data with third parties. 15 | 16 | ## Contact 17 | `MarkusRost#8278` on the ["Help with Wiki-Bot" Discord](https://discord.gg/v77RTk5). 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wiki-Bot[Translation status](#translations)[Node.js CI](https://github.com/Markus-Rost/discord-wiki-bot/actions) 2 | [Wiki-Bot](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot+applications.commands) 3 | 4 | **Wiki-Bot** is a bot for [Discord](https://discord.com/) with the purpose to easily link and search [MediaWiki](https://www.mediawiki.org/wiki/MediaWiki) sites like [Wikipedia](https://www.wikipedia.org/) and [Fandom](https://www.fandom.com/) wikis. **Wiki-Bot** shows short descriptions and additional info about pages and is able to resolve redirects and follow interwiki links. 5 | 6 | **Wiki-Bot** has translations for Bengali, German, English, Spanish, French, Hindi, Korean, Polish, Brazilian Portuguese, Russian, Swedish, Turkish, Simplified Chinese and Traditional Chinese. 7 | 8 | [Use this link to invite **Wiki-Bot** to your Discord server.](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot+applications.commands) 9 | 10 | [Change the server settings for **Wiki-Bot** using the dashboard.](https://settings.wikibot.de/) 11 | 12 | Support server: [https://discord.gg/v77RTk5](https://discord.gg/v77RTk5) 13 | 14 | #### Table of Contents 15 | * [Setup](#setup) 16 | * [Commands](#commands) 17 | * [Admin](#admin) 18 | * [User Verification](#user-verification) 19 | * [Recent Changes Webhook](#recent-changes-webhook) 20 | 21 | ## Setup 22 | After [inviting](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot+applications.commands) **Wiki-Bot** to your server you need to set the wiki you want to search by default. You do this with the `!wiki settings` command or by using the [dashboard](https://settings.wikibot.de/). 23 | * Change the wiki with `!wiki settings wiki ` 24 | * Example: `!wiki settings wiki https://minecraft.wiki/` 25 | * Change the language with `!wiki settings lang ` 26 | * Example: `!wiki settings lang German` 27 | 28 | ## Commands 29 | For a full list with all commands use `!wiki help` 30 | 31 | | Command | Description | 32 | | ------- | ----------- | 33 | | `!wiki ` | **Wiki-Bot** will answer with a link to a matching article in the wiki. | 34 | | `!wiki ! ` | **Wiki-Bot** will answer with a link to a matching article in the named Wikipedia: `https://.wikipedia.org/w/` | 35 | | `!wiki ? ` | **Wiki-Bot** will answer with a link to a matching article in the named Fandom wiki: `https://.fandom.com/` | 36 | | `!wiki !! ` | **Wiki-Bot** will answer with a link to a matching article in the named MediaWiki project. Example: `!wiki !!en.wikipedia.org Cookie` | 37 | | `!wiki User:` | **Wiki-Bot** will show some information about the user. | 38 | | `!wiki diff []` | **Wiki-Bot** will answer with a link to the diff in the wiki. | 39 | | `!wiki diff ` | **Wiki-Bot** will answer with a link to the last diff on the article in the wiki. | 40 | | `!wiki random` | **Wiki-Bot** will answer with a link to a random page in the wiki. | 41 | | `!wiki overview` | **Wiki-Bot** will show some information and statistics about the wiki. | 42 | | `!wiki discussion ` | **Wiki-Bot** will answer with a link to a matching discussion thread in the Fandom wiki. | 43 | | `!wiki discussion post ` | **Wiki-Bot** will answer with a link to a matching discussion post in the Fandom wiki. | 44 | | `!wiki info` | **Wiki-Bot** will introduce himself. | 45 | | `!wiki help` | **Wiki-Bot** will list all the commands that he understands. | 46 | | `!wiki help ` | **Wiki-Bot** will explain the command. | 47 | | `!wiki help admin` | **Wiki-Bot** will list all administrator commands. | 48 | | `!wiki test` | If **Wiki-Bot** is active, he will answer! Otherwise not. | 49 | 50 | If you got an unwanted response, you can react with 🗑️ (`:wastebasket:`) to his message and **Wiki-Bot** will delete it. 51 | 52 | ### Admin 53 | For a full list with all administrator commands use `!wiki help admin` 54 | 55 | | Command | Description | 56 | | ------- | ----------- | 57 | | `!wiki help admin` | **Wiki-Bot** will list all administrator commands. | 58 | | `!wiki settings` | **Wiki-Bot** will change the settings for the server. | 59 | | `!wiki verification` | **Wiki-Bot** will change the wiki verifications used by the `!wiki verify` command. | 60 | | `!wiki rcscript` | **Wiki-Bot** will change the recent changes webhook. | 61 | | `!wiki pause @Wiki-Bot` | **Wiki-Bot** will ignore all commands on this server, except a few admin commands. | 62 | 63 | Administators can also use the [dashboard](https://settings.wikibot.de/) to change the bot settings. 64 | 65 | ## User Verification 66 | Using the `!wiki verify ` command, users are able to verify themselves as a specific wiki user by using the Discord field on their wiki profile. If the user matches and user verifications are set up on the server, **Wiki-Bot** will give them the roles for all verification entries they matched. 67 | 68 | Using the `!wiki verification` command, admins can add up to 10 verification entries on a server. Every verification entry allows for multiple restrictions on when a user should match the verification. 69 | * Channel to use the `!wiki verify` command in. 70 | * Role to get when matching the verification entry. 71 | * Required edit count on the wiki to match the verification entry. 72 | * Required user group to be a member of on the wiki to match the verification entry. 73 | * Required account age in days to match the verification entry. 74 | * Whether the Discord users nickname should be set to their wiki username when they match the verification entry. 75 | 76 | See the [admin commands](#admin) or `!wiki help verification` on how to change the wiki verification entries on the server. 77 | 78 | ## Recent Changes Webhook 79 | **Wiki-Bot** is able to run a recent changes webhook based on [RcGcDw](https://gitlab.com/chicken-riders/RcGcDw) by using the `!wiki rcscript` command. The recent changes can be displayed in compact text messages with inline links or embed messages with edit tags and category changes. 80 | 81 | Requirements to add a recent changes webhook: 82 | * The wiki needs to run on [MediaWiki 1.30](https://www.mediawiki.org/wiki/MediaWiki_1.30) or higher. 83 | * The system message `MediaWiki:Custom-RcGcDw` needs to be set to the Discord server id. 84 | 85 | ## Translations 86 | [Translation status](https://translate.wikibot.de/engage/wiki-bot/?utm_source=widget) 87 | 99 | ## Other 100 | The **Wiki-Bot** logo has been AI generated using [Midjourney](https://www.midjourney.com/home/) and cleaned up by [Shaun Ryken](https://shaunryken.art/). 101 | 102 | [Privacy Policy](PRIVACY.md#privacy-policy) 103 | -------------------------------------------------------------------------------- /cmds/info.js: -------------------------------------------------------------------------------- 1 | import help_server from '../functions/helpserver.js'; 2 | 3 | /** 4 | * Processes the "info" command. 5 | * @param {import('../util/i18n.js').default} lang - The user language. 6 | * @param {import('discord.js').Message} msg - The Discord message. 7 | * @param {String[]} args - The command arguments. 8 | * @param {String} line - The command as plain text. 9 | * @param {import('../util/wiki.js').default} wiki - The wiki for the message. 10 | */ 11 | export default function cmd_info(lang, msg, args, line, wiki) { 12 | if ( args.join('') ) this.LINK(lang, msg, line, wiki); 13 | else { 14 | msg.sendChannel( lang.get('general.disclaimer', '*MarkusRost*') + '\n<' + process.env.patreon + '>' ); 15 | help_server(lang, msg); 16 | this.invite(lang, msg, args, line, wiki); 17 | } 18 | } 19 | 20 | export const cmdData = { 21 | name: 'info', 22 | everyone: true, 23 | pause: false, 24 | owner: false, 25 | run: cmd_info 26 | }; -------------------------------------------------------------------------------- /cmds/invite.js: -------------------------------------------------------------------------------- 1 | import { OAuth2Scopes } from 'discord.js'; 2 | import { defaultPermissions } from '../util/defaults.js'; 3 | 4 | /** 5 | * Processes the "invite" command. 6 | * @param {import('../util/i18n.js').default} lang - The user language. 7 | * @param {import('discord.js').Message} msg - The Discord message. 8 | * @param {String[]} args - The command arguments. 9 | * @param {String} line - The command as plain text. 10 | * @param {import('../util/wiki.js').default} wiki - The wiki for the message. 11 | */ 12 | export default function cmd_invite(lang, msg, args, line, wiki) { 13 | if ( args.join('') ) { 14 | this.LINK(lang, msg, line, wiki); 15 | } 16 | else { 17 | let invite = msg.client.generateInvite({ 18 | scopes: [ 19 | OAuth2Scopes.Bot, 20 | OAuth2Scopes.ApplicationsCommands 21 | ], 22 | permissions: defaultPermissions 23 | }); 24 | let text = lang.get('invite.bot') + '\n<' + invite + '>'; 25 | if ( msg.client.application.id === '461189216198590464' ) { 26 | text += '\n' + lang.get('invite.directory') + '\nhttps://discord.com/discovery/applications/' + msg.client.application.id; 27 | } 28 | msg.sendChannel( text ); 29 | } 30 | } 31 | 32 | export const cmdData = { 33 | name: 'invite', 34 | everyone: true, 35 | pause: false, 36 | owner: false, 37 | run: cmd_invite 38 | }; -------------------------------------------------------------------------------- /cmds/link.js: -------------------------------------------------------------------------------- 1 | import help_setup from '../functions/helpsetup.js'; 2 | import phabricator, { phabricatorSites } from '../functions/phabricator.js'; 3 | import check_wiki from './wiki/general.js'; 4 | import { isMessage, canShowEmbed } from '../util/functions.js'; 5 | 6 | /** 7 | * Processes the wiki linking command. 8 | * @param {import('../util/i18n.js').default} lang - The user language. 9 | * @param {import('discord.js').Message} msg - The Discord message. 10 | * @param {String} title - The page title. 11 | * @param {import('../util/wiki.js').default} wiki - The wiki for the page. 12 | * @param {String} [cmd] - The command at this point. 13 | */ 14 | export default function cmd_link(lang, msg, title, wiki, cmd = '') { 15 | if ( msg.wikiWhitelist.length && !msg.wikiWhitelist.includes( wiki.href ) ) return msg.sendChannel(lang.get('general.whitelist')); 16 | if ( msg.isAdmin() && msg.defaultSettings ) help_setup(lang, msg); 17 | var spoiler = ''; 18 | if ( /^\|\|(?:(?!\|\|).)+\|\|$/.test(title) ) { 19 | title = title.substring(2, title.length - 2); 20 | spoiler = '||'; 21 | } 22 | var noEmbed = !canShowEmbed(msg); 23 | if ( /^<[^<>]+>$/.test(title) ) { 24 | title = title.substring(1, title.length - 1); 25 | noEmbed = true; 26 | } 27 | msg.reactEmoji(WB_EMOJI.waiting).then( reaction => { 28 | ( phabricatorSites.has(wiki.hostname) 29 | ? phabricator(lang, msg, wiki, new URL('/' + title, wiki), spoiler, noEmbed) 30 | : check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler, noEmbed) 31 | )?.then( result => { 32 | if ( !result || isMessage(result) ) return result; 33 | if ( result.message ) { 34 | if ( Array.isArray(result.message) ) result.message.forEach( content => msg.sendChannel(content) ); 35 | else if ( result.reaction === WB_EMOJI.error ) msg.sendChannelError(result.message); 36 | else if ( result.reaction === 'reply' ) msg.replyMsg(result.message, true); 37 | else msg.sendChannel(result.message).then( message => { 38 | if ( result.reaction === WB_EMOJI.warning && message ) message.reactEmoji(WB_EMOJI.warning); 39 | return message; 40 | } ); 41 | } 42 | else if ( result.reaction ) { 43 | msg.reactEmoji(result.reaction); 44 | } 45 | if ( reaction ) reaction.removeEmoji(); 46 | } ); 47 | } ); 48 | } 49 | 50 | export const cmdData = { 51 | name: 'LINK', 52 | everyone: true, 53 | pause: false, 54 | owner: true, 55 | run: cmd_link 56 | }; -------------------------------------------------------------------------------- /cmds/minecraft/command.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Processes Minecraft commands. 3 | * @param {import('../../util/i18n.js').default} lang - The user language. 4 | * @param {import('discord.js').Message|import('discord.js').ChatInputCommandInteraction} msg - The Discord message. 5 | * @param {import('../../util/wiki.js').default} wiki - The wiki. 6 | * @param {String[]} args - The command arguments. 7 | * @param {String} title - The page title. 8 | * @param {String} cmd - The command at this point. 9 | * @param {import('discord.js').MessageReaction} reaction - The reaction on the message. 10 | * @param {String} spoiler - If the response is in a spoiler. 11 | * @param {Boolean} noEmbed - If the response should be without an embed. 12 | * @returns {Promise<{reaction?: WB_EMOJI, message?: String|import('discord.js').MessageOptions}>} 13 | */ 14 | function minecraft_command(lang, msg, wiki, args, title, cmd, reaction, spoiler, noEmbed) { 15 | if ( args.join('') ) { 16 | if ( args[0].startsWith( '/' ) ) return this.SYNTAX(lang, msg, wiki, args[0].substring(1), args.slice(1), title, cmd, reaction, spoiler, noEmbed); 17 | return this.SYNTAX(lang, msg, wiki, args[0], args.slice(1), title, cmd, reaction, spoiler, noEmbed); 18 | } 19 | msg.notMinecraft = true; 20 | return this.WIKI(lang, msg, title, wiki, cmd, reaction, spoiler, noEmbed); 21 | } 22 | 23 | export default { 24 | name: 'command', 25 | run: minecraft_command 26 | }; -------------------------------------------------------------------------------- /cmds/minecraft/syntax.js: -------------------------------------------------------------------------------- 1 | import commands from './commands.json' with { type: 'json' }; 2 | import { got, splitMessage } from '../../util/functions.js'; 3 | import Wiki from '../../util/wiki.js'; 4 | 5 | /** 6 | * Sends a Minecraft command. 7 | * @param {import('../../util/i18n.js').default} lang - The user language. 8 | * @param {import('discord.js').Message|import('discord.js').ChatInputCommandInteraction} msg - The Discord message. 9 | * @param {import('../../util/wiki.js').default} wiki - The wiki. 10 | * @param {String} mccmd - The Minecraft command argument. 11 | * @param {String[]} args - The command arguments. 12 | * @param {String} title - The page title. 13 | * @param {String} cmd - The command at this point. 14 | * @param {import('discord.js').MessageReaction} reaction - The reaction on the message. 15 | * @param {String} spoiler - If the response is in a spoiler. 16 | * @param {Boolean} noEmbed - If the response should be without an embed. 17 | * @returns {Promise<{reaction?: WB_EMOJI, message?: String|import('discord.js').MessageOptions}>} 18 | */ 19 | function minecraft_syntax(lang, msg, wiki, mccmd, args, title, cmd, reaction, spoiler, noEmbed) { 20 | mccmd = mccmd.toLowerCase(); 21 | var aliasCmd = ( commands.aliases[mccmd] || mccmd ); 22 | var cmdpage = commands.wikis[wiki.href]; 23 | if ( commands.list.hasOwnProperty(aliasCmd) ) { 24 | var cmdSyntaxMap = commands.list[aliasCmd].map( command => { 25 | var cmdargs = command.split(' '); 26 | if ( cmdargs[0].startsWith( '/' ) ) cmdargs = cmdargs.slice(1); 27 | var argmatches = cmdargs.map( (arg, i) => { 28 | if ( arg === args[i] ) return true; 29 | } ); 30 | var matchCount = 0; 31 | argmatches.forEach( match => { 32 | if ( match ) matchCount++; 33 | } ); 34 | return [argmatches.lastIndexOf(true),matchCount]; 35 | } ); 36 | var lastIndex = Math.max(...cmdSyntaxMap.map( command => command[0] )); 37 | var matchCount = Math.max(...cmdSyntaxMap.filter( command => command[0] === lastIndex ).map( command => command[1] )); 38 | var cmdSyntax = commands.list[aliasCmd].filter( (command, i) => ( lastIndex === -1 || cmdSyntaxMap[i][0] === lastIndex ) && cmdSyntaxMap[i][1] === matchCount ).join('\n').replaceAllSafe( '/' + aliasCmd, '/' + mccmd ); 39 | return got.get( wiki + ( cmdpage.endsWith( '/' ) ? 'api.php?action=query&redirects=true&converttitles=true&titles=%1F' + encodeURIComponent( cmdpage + aliasCmd ) : 'api.php?action=parse&redirects=true&prop=sections&page=' + encodeURIComponent( cmdpage ) ) + '&format=json', { 40 | context: { 41 | guildId: msg.guildId 42 | } 43 | } ).then( response => { 44 | var body = response.body; 45 | if ( body?.warnings ) log_warning(body.warnings); 46 | if ( response.statusCode !== 200 || !( body?.query?.pages || body?.parse?.sections?.length ) ) { 47 | console.log( '- ' + response.statusCode + ': Error while getting the command page: ' + body?.error?.info ); 48 | } 49 | else if ( cmdpage.endsWith( '/' ) ) { 50 | if ( body.query.pages['-1'] ) { 51 | wiki = new Wiki('https://minecraft.wiki/'); 52 | cmdpage = 'Commands/'; 53 | } 54 | else { 55 | cmdpage = Object.values(body.query.pages)[0].title; 56 | aliasCmd = ( body.query.redirects?.[0]?.tofragment || '' ); 57 | } 58 | } 59 | else { 60 | cmdpage = body.parse.title; 61 | if ( !body.parse.sections.some( section => section.anchor === aliasCmd ) ) { 62 | if ( body.parse.sections.some( section => section.anchor === mccmd ) ) { 63 | aliasCmd = mccmd; 64 | } 65 | else { 66 | wiki = new Wiki('https://minecraft.wiki/'); 67 | cmdpage = 'Commands/'; 68 | } 69 | } 70 | } 71 | }, error => { 72 | console.log( '- Error while getting the command page: ' + error ); 73 | } ).then( () => { 74 | return {message: splitMessage( spoiler + '```pf\n' + cmdSyntax + '```<' + wiki.toLink(( cmdpage.endsWith( '/' ) ? cmdpage + aliasCmd : cmdpage ), '', ( cmdpage.endsWith( '/' ) ? '' : aliasCmd )) + '>' + spoiler, {maxLength: 2000, prepend: spoiler + '```pf\n', append: '```' + spoiler} )}; 75 | } ); 76 | } 77 | msg.notMinecraft = true; 78 | return this.WIKI(lang, msg, title, wiki, cmd, reaction, spoiler, noEmbed); 79 | } 80 | 81 | export default { 82 | name: 'SYNTAX', 83 | run: minecraft_syntax 84 | }; -------------------------------------------------------------------------------- /cmds/pause.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Processes the "pause" command. 3 | * @param {import('../util/i18n.js').default} lang - The user language. 4 | * @param {import('discord.js').Message} msg - The Discord message. 5 | * @param {String[]} args - The command arguments. 6 | * @param {String} line - The command as plain text. 7 | * @param {import('../util/wiki.js').default} wiki - The wiki for the message. 8 | */ 9 | export default function cmd_pause(lang, msg, args, line, wiki) { 10 | if ( msg.inGuild() && args.join(' ').split('\n')[0].isMention(msg.guild) && ( msg.isAdmin() || msg.isOwner() ) ) { 11 | if ( pausedGuilds.has(msg.guildId) ) { 12 | pausedGuilds.delete(msg.guildId); 13 | console.log( '- Pause ended.' ); 14 | msg.replyMsg( lang.get('pause.off'), true ); 15 | } else { 16 | msg.replyMsg( lang.get('pause.on'), true ); 17 | console.log( '- Pause started.' ); 18 | pausedGuilds.add(msg.guildId); 19 | } 20 | } else if ( !msg.inGuild() || !pausedGuilds.has(msg.guildId) ) { 21 | this.LINK(lang, msg, line, wiki); 22 | } 23 | } 24 | 25 | export const cmdData = { 26 | name: 'pause', 27 | everyone: true, 28 | pause: true, 29 | owner: true, 30 | run: cmd_pause 31 | }; -------------------------------------------------------------------------------- /cmds/say.js: -------------------------------------------------------------------------------- 1 | import { PermissionFlagsBits } from 'discord.js'; 2 | 3 | /** 4 | * Processes the "say" command. 5 | * @param {import('../util/i18n.js').default} lang - The user language. 6 | * @param {import('discord.js').Message} msg - The Discord message. 7 | * @param {String[]} args - The command arguments. 8 | * @param {String} line - The command as plain text. 9 | * @param {import('../util/wiki.js').default} wiki - The wiki for the message. 10 | */ 11 | export default function cmd_say(lang, msg, args, line, wiki) { 12 | var text = args.join(' '); 13 | var imgs = []; 14 | if ( msg.uploadFiles() ) imgs = msg.attachments.map( function(img) { 15 | return {attachment:img.url,name:img.filename}; 16 | } ); 17 | if ( text.includes( '${' ) ) { 18 | try { 19 | text = eval( '`' + text + '`' ); 20 | } catch ( error ) { 21 | log_error(error); 22 | } 23 | } 24 | if ( text.trim() || imgs.length ) { 25 | let allowedMentions = {parse:['users']}; 26 | if ( msg.member.permissions.has(PermissionFlagsBits.MentionEveryone) ) allowedMentions.parse = ['users','roles','everyone']; 27 | else allowedMentions.roles = msg.guild.roles.cache.filter( role => role.mentionable ).map( role => role.id ).slice(0, 100); 28 | msg.channel.send( { 29 | content: text, 30 | files: imgs, 31 | allowedMentions, 32 | reply: { 33 | messageReference: msg.reference?.messageId 34 | } 35 | } ).then( () => msg.delete().catch(log_error), error => { 36 | log_error(error); 37 | msg.reactEmoji(WB_EMOJI.error, true); 38 | } ); 39 | } else if ( !pausedGuilds.has(msg.guildId) ) this.LINK(lang, msg, line, wiki); 40 | } 41 | 42 | export const cmdData = { 43 | name: 'say', 44 | everyone: false, 45 | pause: false, 46 | owner: true, 47 | run: cmd_say 48 | }; -------------------------------------------------------------------------------- /cmds/stop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Processes the "stop" command. 3 | * @param {import('../util/i18n.js').default} lang - The user language. 4 | * @param {import('discord.js').Message} msg - The Discord message. 5 | * @param {String[]} args - The command arguments. 6 | * @param {String} line - The command as plain text. 7 | * @param {import('../util/wiki.js').default} wiki - The wiki for the message. 8 | * @async 9 | */ 10 | export default async function cmd_stop(lang, msg, args, line, wiki) { 11 | if ( args[0] === 'force' && args.slice(1).join(' ').split('\n')[0].isMention(msg.guild) ) { 12 | await msg.replyMsg( 'I\'ll destroy myself now!', true ); 13 | await msg.client.shard.send('SIGKILL'); 14 | } else if ( args.join(' ').split('\n')[0].isMention(msg.guild) ) { 15 | await msg.replyMsg( 'I\'ll restart myself now!', true ); 16 | console.log( '\n- Restarting all shards!\n\n' ); 17 | await msg.client.shard.respawnAll( { 18 | shardDelay: 5_000, 19 | respawnDelay: 500, 20 | timeout: 60_000 21 | } ); 22 | } else if ( !msg.inGuild() || !pausedGuilds.has(msg.guildId) ) { 23 | this.LINK(lang, msg, line, wiki); 24 | } 25 | } 26 | 27 | export const cmdData = { 28 | name: 'stop', 29 | everyone: false, 30 | pause: false, 31 | owner: true, 32 | run: cmd_stop 33 | }; -------------------------------------------------------------------------------- /cmds/test.js: -------------------------------------------------------------------------------- 1 | import { EmbedBuilder, Status } from 'discord.js'; 2 | import help_setup from '../functions/helpsetup.js'; 3 | import { got, toMarkdown } from '../util/functions.js'; 4 | import logging from '../util/logging.js'; 5 | 6 | /** 7 | * Processes the "test" command. 8 | * @param {import('../util/i18n.js').default} lang - The user language. 9 | * @param {import('discord.js').Message} msg - The Discord message. 10 | * @param {String[]} args - The command arguments. 11 | * @param {String} line - The command as plain text. 12 | * @param {import('../util/wiki.js').default} wiki - The wiki for the message. 13 | */ 14 | export default function cmd_test(lang, msg, args, line, wiki) { 15 | if ( args.join('') ) { 16 | if ( !msg.inGuild() || !pausedGuilds.has(msg.guildId) ) this?.LINK?.(lang, msg, line, wiki); 17 | } 18 | else if ( !msg.inGuild() || !pausedGuilds.has(msg.guildId) ) { 19 | if ( msg.isAdmin() && msg.defaultSettings ) help_setup(lang, msg); 20 | let textList = lang.get('test.text').filter( text => text.trim() ); 21 | var text = ( textList[Math.floor(Math.random() * ( textList.length * 5 ))] || lang.get('test.text.0') ); 22 | if ( process.env.READONLY ) text = lang.get('general.readonly') + '\n' + process.env.invite; 23 | console.log( '- Test[' + process.env.SHARDS + ']: Fully functioning!' ); 24 | msg.replyMsg( text ).then( message => { 25 | if ( !message ) return; 26 | var discordPing = message.createdTimestamp - msg.createdTimestamp; 27 | if ( discordPing > 1_000 ) text = lang.get('test.slow') + ' 🐌\n' + process.env.invite; 28 | var embed = new EmbedBuilder().setTitle( lang.get('test.time') ).setFooter( {text: 'Shard: ' + process.env.SHARDS} ).addFields( {name: 'Discord', value: discordPing.toLocaleString(lang.get('dateformat')) + 'ms'} ); 29 | var now = Date.now(); 30 | got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&format=json', { 31 | timeout: { 32 | request: 10_000 33 | }, 34 | context: { 35 | guildId: msg.guildId 36 | } 37 | } ).then( response => { 38 | var then = Date.now(); 39 | var body = response.body; 40 | if ( body?.warnings ) log_warning(body.warnings); 41 | var ping = ( then - now ).toLocaleString(lang.get('dateformat')) + 'ms'; 42 | if ( body?.query?.general ) wiki.updateWiki(body.query.general); 43 | var notice = []; 44 | if ( response.statusCode !== 200 || !body?.query?.general ) { 45 | if ( wiki.noWiki(response.url, response.statusCode) ) { 46 | console.log( '- This wiki doesn\'t exist!' ); 47 | ping += ' <:unknown_wiki:505887262077353984>'; 48 | } 49 | else { 50 | console.log( '- ' + response.statusCode + ': Error while reaching the wiki: ' + body?.error?.info ); 51 | ping += ' <:error:505887261200613376>'; 52 | if ( body?.error?.code === 'readapidenied' || body?.error?.info === 'You need read permission to use this module.' ) { 53 | notice.push(lang.get('settings.wikiinvalid_private')); 54 | } 55 | } 56 | } 57 | else if ( msg.isAdmin() || msg.isOwner() ) { 58 | logging(wiki, msg.guildId, 'test'); 59 | if ( body.query.general.generator.replace( /^MediaWiki 1\.(\d\d).*$/, '$1' ) < 30 ) { 60 | console.log( '- This wiki is using ' + body.query.general.generator + '.' ); 61 | notice.push(lang.get('test.MediaWiki', '[MediaWiki 1.30]()', body.query.general.generator)); 62 | } 63 | } 64 | else logging(wiki, msg.guildId, 'test'); 65 | embed.addFields( {name: wiki.toLink(), value: ping} ); 66 | if ( notice.length ) embed.addFields( {name: lang.get('test.notice'), value: notice.join('\n')} ); 67 | if ( body?.query?.general?.readonly !== undefined ) { 68 | if ( body.query.general.readonlyreason ) { 69 | embed.addFields( {name: lang.get('overview.readonly'), value: toMarkdown(body.query.general.readonlyreason, wiki, '', true)} ); 70 | } 71 | else embed.addFields( {name: '\u200b', value: '**' + lang.get('overview.readonly') + '**'} ); 72 | } 73 | }, error => { 74 | var then = Date.now(); 75 | var ping = ( then - now ).toLocaleString(lang.get('dateformat')) + 'ms'; 76 | if ( wiki.noWiki(error.message) ) { 77 | console.log( '- This wiki doesn\'t exist!' ); 78 | ping += ' <:unknown_wiki:505887262077353984>'; 79 | } 80 | else { 81 | console.log( '- Error while reaching the wiki: ' + error ); 82 | ping += ' <:error:505887261200613376>'; 83 | } 84 | embed.addFields( {name: wiki.toLink(), value: ping} ); 85 | } ).finally( () => { 86 | if ( msg.isOwner() ) return msg.client.shard.broadcastEval( discordClient => { 87 | return { 88 | status: [ 89 | discordClient.ws.status, 90 | ...( discordClient.ws.shards.size ? ( discordClient.ws.shards.every( shard => { 91 | return ( shard.status === shard.manager.status ); 92 | } ) ? [] : discordClient.ws.shards.map( shard => { 93 | return shard.status; 94 | } ) ) : ['[]'] ) 95 | ], 96 | guilds: discordClient.guilds.cache.size 97 | }; 98 | } ).then( values => { 99 | embed.addFields( {name: 'Guilds', value: values.reduce( (acc, val) => acc + val.guilds, 0 ).toLocaleString(lang.get('dateformat'))} ); 100 | let shardData = []; 101 | let lastShardData = {}; 102 | values.forEach( (value, id) => { 103 | let status = value.status.map( wsStatus => Status[wsStatus] ?? wsStatus ).join(' '); 104 | if ( status === lastShardData.status ) return lastShardData.count++; 105 | shardData.push(lastShardData = {id, status, count: 0}); 106 | } ); 107 | return '```less\n' + shardData.map( value => { 108 | return '[' + value.id + ( value.count ? '-' + ( value.id + value.count ) : '' ) + ']: ' + value.status; 109 | } ).join('\n') + '\n```'; 110 | }, error => { 111 | return '```js\n' + error + '\n```'; 112 | } ).then( shards => { 113 | embed.addFields( {name: 'Shards', value: shards} ); 114 | message.edit( {content: text, embeds: [embed]} ).catch(log_error); 115 | } ); 116 | message.edit( {content: text, embeds: [embed]} ).catch(log_error); 117 | } ); 118 | } ); 119 | } 120 | else { 121 | console.log( '- Test: Paused!' ); 122 | msg.replyMsg( lang.get('test.pause'), true ); 123 | } 124 | } 125 | 126 | export const cmdData = { 127 | name: 'test', 128 | everyone: true, 129 | pause: true, 130 | owner: false, 131 | run: cmd_test 132 | }; 133 | -------------------------------------------------------------------------------- /cmds/wiki/functions.js: -------------------------------------------------------------------------------- 1 | export {default as diff} from './diff.js'; 2 | export {default as discussion} from './discussion.js'; 3 | export {default as overview} from './overview.js'; 4 | export {default as random} from './random.js'; 5 | export {default as search} from './search.js'; 6 | export {default as special_page} from './special_page.js'; 7 | export {default as user} from './user.js'; 8 | export {default as test} from '../test.js'; -------------------------------------------------------------------------------- /cmds/wiki/search.js: -------------------------------------------------------------------------------- 1 | import { EmbedBuilder } from 'discord.js'; 2 | import { botLimits } from '../../util/defaults.js'; 3 | import { got, escapeFormatting, splitMessage } from '../../util/functions.js'; 4 | 5 | const {search: searchLimit} = botLimits; 6 | 7 | /** 8 | * Searches a wiki. 9 | * @param {import('../../util/i18n.js').default} lang - The user language. 10 | * @param {import('discord.js').Message|import('discord.js').ChatInputCommandInteraction} msg - The Discord message. 11 | * @param {String} searchterm - The searchterm. 12 | * @param {import('../../util/wiki.js').default} wiki - The wiki for the search. 13 | * @param {Object} query - The siteinfo from the wiki. 14 | * @param {import('discord.js').MessageReaction} reaction - The reaction on the message. 15 | * @param {String} spoiler - If the response is in a spoiler. 16 | * @param {Boolean} noEmbed - If the response should be without an embed. 17 | * @returns {Promise<{reaction?: WB_EMOJI, message?: String|import('discord.js').MessageOptions}>} 18 | */ 19 | export default function mw_search(lang, msg, searchterm, wiki, query, reaction, spoiler, noEmbed) { 20 | if ( searchterm.length > 250 ) { 21 | searchterm = searchterm.substring(0, 250).trim(); 22 | msg?.fetchReply?.().then( message => message?.reactEmoji?.(WB_EMOJI.warning), log_error ); 23 | msg?.reactEmoji?.(WB_EMOJI.warning); 24 | } 25 | var pagelink = wiki.toLink('Special:Search', {search:searchterm,fulltext:1}); 26 | var resultText = '<' + pagelink + '>'; 27 | var embed = null; 28 | if ( !noEmbed ) embed = new EmbedBuilder().setAuthor( {name: query.general.sitename} ).setTitle( '`' + searchterm + '`' ).setURL( pagelink ); 29 | else resultText += '\n\n**`' + searchterm + '`**'; 30 | var querypage = ( Object.values(( query.pages || {} ))?.[0] || {title:'',ns:0,invalid:''} ); 31 | var limit = searchLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )]; 32 | return got.get( wiki + 'api.php?action=query&titles=Special:Search&list=search&srinfo=totalhits&srprop=redirecttitle|sectiontitle&srnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + wiki.namespaces.content.map( ns => ns.id ).join('|') + '&srlimit=' + limit + '&srsearch=' + encodeURIComponent( searchterm ) + '&format=json', { 33 | context: { 34 | guildId: msg.guildId 35 | } 36 | } ).then( response => { 37 | var body = response.body; 38 | if ( body?.warnings ) log_warning(body.warnings); 39 | if ( response.statusCode !== 200 || !body?.query?.search || body.batchcomplete === undefined ) { 40 | return console.log( '- ' + response.statusCode + ': Error while getting the search results: ' + body?.error?.info ); 41 | } 42 | if ( body.query.search.length < limit ) { 43 | return got.get( wiki + 'api.php?action=query&list=search&srwhat=text&srinfo=totalhits&srprop=redirecttitle|sectiontitle&srnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + wiki.namespaces.content.map( ns => ns.id ).join('|') + '&srlimit=' + limit + '&srsearch=' + encodeURIComponent( searchterm ) + '&format=json', { 44 | context: { 45 | guildId: msg.guildId 46 | } 47 | } ).then( tresponse => { 48 | var tbody = tresponse.body; 49 | if ( tbody?.warnings ) log_warning(tbody.warnings); 50 | if ( tresponse.statusCode !== 200 || !tbody?.query?.search || tbody.batchcomplete === undefined ) { 51 | return console.log( '- ' + tresponse.statusCode + ': Error while getting the text search results: ' + tbody?.error?.info ); 52 | } 53 | body.query.search.push(...tbody.query.search.filter( tresult => { 54 | return !body.query.search.some( result => result.pageid === tresult.pageid ); 55 | } ).slice(0, limit - body.query.search.length)); 56 | if ( body.query.searchinfo && tbody.query.searchinfo ) body.query.searchinfo.totalhits += tbody.query.searchinfo.totalhits; 57 | }, error => { 58 | console.log( '- Error while getting the text search results: ' + error ); 59 | } ).then( () => { 60 | return body; 61 | } ); 62 | } 63 | return body; 64 | } ).then( body => { 65 | if ( !body?.query?.search ) return; 66 | if ( body.query.pages?.['-1']?.title ) { 67 | pagelink = wiki.toLink(body.query.pages['-1'].title, {search:searchterm,fulltext:1}); 68 | resultText = '<' + pagelink + '>'; 69 | if ( !noEmbed ) embed.setURL( pagelink ); 70 | else resultText += '\n\n**`' + searchterm + '`**'; 71 | } 72 | var hasExactMatch = false; 73 | var description = []; 74 | body.query.search.forEach( result => { 75 | let text = '• '; 76 | let bold = ''; 77 | if ( result.title.replace( /[_-]/g, ' ' ).toLowerCase() === querypage.title.replaceAll( '-', ' ' ).toLowerCase() ) { 78 | bold = '**'; 79 | hasExactMatch = true; 80 | if ( query.redirects?.[0] ) { 81 | if ( query.redirects[0].tofragment && !result.sectiontitle ) { 82 | result.sectiontitle = query.redirects[0].tofragment; 83 | } 84 | if ( !result.redirecttitle ) result.redirecttitle = query.redirects[0].from; 85 | } 86 | } 87 | text += bold; 88 | text += '[' + escapeFormatting(result.title) + '](<' + wiki.toLink(result.title, '', '', true) + '>)'; 89 | if ( result.sectiontitle ) { 90 | text += ' § [' + escapeFormatting(result.sectiontitle) + '](<' + wiki.toLink(result.title, '', result.sectiontitle, true) + '>)'; 91 | } 92 | if ( result.redirecttitle ) { 93 | text += ' (⤷ [' + escapeFormatting(result.redirecttitle) + '](<' + wiki.toLink(result.redirecttitle, 'redirect=no', '', true) + '>))'; 94 | } 95 | text += bold; 96 | description.push( text ); 97 | } ); 98 | if ( !hasExactMatch ) { 99 | if ( query.interwiki?.[0] ) { 100 | let text = '• **⤷ '; 101 | text += '__[' + escapeFormatting(query.interwiki[0].title) + '](<' + query.interwiki[0].url + '>)__'; 102 | if ( query.redirects?.[0] ) { 103 | text += ' (⤷ [' + escapeFormatting(query.redirects[0].from) + '](<' + wiki.toLink(query.redirects[0].from, 'redirect=no', '', true) + '>))'; 104 | } 105 | text += '**'; 106 | description.unshift( text ); 107 | } 108 | else if ( querypage.invalid === undefined && ( querypage.missing === undefined || querypage.known !== undefined ) ) { 109 | let text = '• **'; 110 | text += '[' + escapeFormatting(querypage.title) + '](<' + wiki.toLink(querypage.title, '', '', true) + '>)'; 111 | if ( query.redirects?.[0] ) { 112 | if ( query.redirects[0].tofragment ) { 113 | text += ' § [' + escapeFormatting(query.redirects[0].tofragment) + '](<' + wiki.toLink(querypage.title, '', query.redirects[0].tofragment, true) + '>)'; 114 | } 115 | text += ' (⤷ [' + escapeFormatting(query.redirects[0].from) + '](<' + wiki.toLink(query.redirects[0].from, 'redirect=no', '', true) + '>))'; 116 | } 117 | text += '**'; 118 | description.unshift( text ); 119 | } 120 | } 121 | var footer = ''; 122 | if ( body.query.searchinfo ) { 123 | footer = lang.get('search.results', body.query.searchinfo.totalhits.toLocaleString(lang.get('dateformat')), body.query.searchinfo.totalhits); 124 | } 125 | if ( !noEmbed ) { 126 | if ( description.length ) embed.setDescription( splitMessage( description.join('\n') )[0] ); 127 | if ( footer ) embed.setFooter( {text: footer} ); 128 | } 129 | else { 130 | if ( description.length ) resultText += '\n' + splitMessage( description.join('\n'), {maxLength: 1990 - resultText.length - footer.length} )[0]; 131 | if ( footer ) resultText += '\n' + footer; 132 | } 133 | }, error => { 134 | console.log( '- Error while getting the search results.' + error ); 135 | } ).then( () => { 136 | return {message: { 137 | content: '🔍 ' + spoiler + resultText + spoiler, 138 | embeds: [embed] 139 | }}; 140 | } ); 141 | } -------------------------------------------------------------------------------- /dashboard/beta/example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Let a user change something 3 | * @param {import('http').ServerResponse} res - The server response 4 | * @param {import('cheerio').CheerioAPI} $ - The response body 5 | * @param {import('../util.js').Guild} guild - The current guild 6 | * @param {String[]} args - The url parts 7 | * @param {import('../i18n.js').default} dashboardLang - The user language 8 | */ 9 | function dashboard_get(res, $, guild, args, dashboardLang) { 10 | let body = $.html(); 11 | res.writeHead(200, {'Content-Length': Buffer.byteLength(body)}); 12 | res.write( body ); 13 | return res.end(); 14 | } 15 | 16 | /** 17 | * Change something 18 | * @param {Function} res - The server response 19 | * @param {import('../util.js').Settings} userSettings - The settings of the user 20 | * @param {String} guild - The id of the guild 21 | * @param {String|Number} type - The setting to change 22 | * @param {Object} settings - The new settings 23 | */ 24 | function update_post(res, userSettings, guild, type, settings) { 25 | return res('/', 'savefail'); 26 | } 27 | 28 | export default { 29 | type: null, // 'settings', 'verification', 'rcscript' 30 | name: 'example', 31 | data: { 32 | show: 'none', // 'none', 'patreon', 'public' 33 | access: 'none', // 'none', 'patreon', 'public' 34 | form: dashboard_get, 35 | post: update_post 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /dashboard/functions.js: -------------------------------------------------------------------------------- 1 | import { readdir } from 'node:fs'; 2 | import { get as rcscript_get, post as rcscript_post } from './functions/rcscript.js'; 3 | import { get as settings_get, post as settings_post } from './functions/settings.js'; 4 | import { get as user_get, post as user_post } from './functions/user.js'; 5 | import { get as verification_get, post as verification_post } from './functions/verification.js'; 6 | 7 | export const forms = { 8 | rcscript: rcscript_get, 9 | settings: settings_get, 10 | user: user_get, 11 | verification: verification_get 12 | }; 13 | export const posts = { 14 | rcscript: rcscript_post, 15 | settings: settings_post, 16 | user: user_post, 17 | verification: verification_post 18 | }; 19 | 20 | /** 21 | * @typedef PageData 22 | * @property {'public'|'patreon'|'none'} show 23 | * @property {'public'|'patreon'|'none'} access 24 | * @property {Function} form 25 | * @property {Function} post 26 | */ 27 | 28 | /** @type {Map<'settings'|'verification'|'rcscript', Map>} */ 29 | export const beta = new Map([ 30 | ['settings', new Map()], 31 | ['verification', new Map()], 32 | ['rcscript', new Map()] 33 | ]); 34 | readdir( './dashboard/beta', (error, files) => { 35 | if ( error ) return error; 36 | files.filter( file => file.endsWith('.js') ).forEach( file => { 37 | import('./beta/' + file).then( ({default: command}) => { 38 | beta.get(command.type)?.set(command.name, command.data); 39 | } ); 40 | } ); 41 | } ); -------------------------------------------------------------------------------- /dashboard/functions/user.js: -------------------------------------------------------------------------------- 1 | import { db, enabledOAuth2, createNotice } from '../util.js'; 2 | 3 | /** 4 | * Let a user change settings 5 | * @param {import('http').ServerResponse} res - The server response 6 | * @param {import('cheerio').CheerioAPI} $ - The response body 7 | * @param {import('../util.js').User} user - The current user 8 | * @param {import('../i18n.js').default} dashboardLang - The user language 9 | * @param {String} csrfToken - The csrf token for the session 10 | */ 11 | function dashboard_user(res, $, user, dashboardLang, csrfToken) { 12 | db.query( 'SELECT site, token FROM oauthusers WHERE userid = $1', [user.id] ).then( ({rows}) => { 13 | $('

').text(dashboardLang.get('oauth.desc')).appendTo('#text .description'); 14 | $('.channel#user-oauth').addClass('selected'); 15 | $('

').append( 16 | $('

').text(dashboardLang.get('oauth.form.default')), 17 | ...enabledOAuth2.map( oauthSite => { 18 | let row = rows.find( row => row.site === oauthSite.id ); 19 | let buttons = $('
'); 20 | if ( row ) { 21 | if ( row.token === null ) buttons.append( 22 | $('').append( 23 | $('').addClass('wb-oauth-enabled').attr('name', 'oauth_enable_' + oauthSite.id).val(dashboardLang.get('oauth.form.enable')) 24 | ), 25 | $('').append( 26 | $('').addClass('wb-oauth-connected').attr('name', 'oauth_connect_' + oauthSite.id).val(dashboardLang.get('oauth.form.connect')) 27 | ) 28 | ); 29 | else buttons.append( 30 | $('').append( 31 | $('').addClass('wb-oauth-disabled').attr('name', 'oauth_disable_' + oauthSite.id).val(dashboardLang.get('oauth.form.disable')) 32 | ), 33 | $('').append( 34 | $('').addClass('wb-oauth-unconnected').attr('name', 'oauth_disconnect_' + oauthSite.id).val(dashboardLang.get('oauth.form.disconnect')) 35 | ) 36 | ); 37 | } 38 | else buttons.append( 39 | $('').append( 40 | $('').addClass('wb-oauth-disabled').attr('name', 'oauth_disable_' + oauthSite.id).val(dashboardLang.get('oauth.form.disable')) 41 | ), 42 | $('').append( 43 | $('').addClass('wb-oauth-connected').attr('name', 'oauth_connect_' + oauthSite.id).val(dashboardLang.get('oauth.form.connect')) 44 | ) 45 | ); 46 | return $('
').addClass('wb-oauth-site').attr('id', 'oauth-' + oauthSite.id).append( 47 | $('
').append( 48 | $('').append( 49 | $('').attr('href', oauthSite.manage).text(oauthSite.name) 50 | ), 51 | $('
').append( 52 | $('').text(dashboardLang.get('oauth.form.current')), 53 | ( row ? ( row.token === null ? 54 | $('').addClass('wb-oauth-disabled').text(dashboardLang.get('oauth.form.disabled')) 55 | : 56 | $('').addClass('wb-oauth-connected').text(dashboardLang.get('oauth.form.connected')) 57 | ) : 58 | $('').addClass('wb-oauth-unconnected').text(dashboardLang.get('oauth.form.unconnected')) 59 | ) 60 | ), 61 | buttons 62 | ) 63 | ) 64 | } ), 65 | $('').attr('name', 'csrfToken').val(csrfToken) 66 | ).attr('action', '/user').appendTo('#text'); 67 | }, dberror => { 68 | console.log( '- Dashboard: Error while getting the OAuth2 info: ' + dberror ); 69 | createNotice($, 'error', dashboardLang); 70 | $('

').text(dashboardLang.get('oauth.failed')).appendTo('#text .description'); 71 | } ).then( () => { 72 | let body = $.html(); 73 | res.writeHead(200, {'Content-Length': Buffer.byteLength(body)}); 74 | res.write( body ); 75 | return res.end(); 76 | } ); 77 | } 78 | 79 | /** 80 | * Change settings 81 | * @param {Function} res - The server response 82 | * @param {String} user_id - The current user 83 | * @param {String} type - The setting to change 84 | * @param {String} oauth_id - The OAuth2 site to change 85 | */ 86 | function update_user(res, user_id, type, oauth_id) { 87 | if ( !['connect', 'disconnect', 'disable', 'enable'].includes( type ) || !enabledOAuth2.some( oauthSite => oauthSite.id === oauth_id ) ) { 88 | return res('/user', 'savefail'); 89 | } 90 | if ( type === 'disconnect' || type === 'enable' ) return db.query( 'DELETE FROM oauthusers WHERE userid = $1 AND site = $2', [user_id, oauth_id] ).then( () => { 91 | if ( type === 'disconnect' ) console.log( '- Dashboard: Successfully disconnected ' + user_id + ' from ' + oauth_id + '.' ); 92 | else console.log( '- Dashboard: Successfully enabled ' + oauth_id + ' for ' + user_id + '.' ); 93 | return res('/user', 'save'); 94 | }, dberror => { 95 | if ( type === 'disconnect' ) console.log( '- Dashboard: Error while disconnecting ' + user_id + ' from ' + oauth_id + ': ' + dberror ); 96 | else console.log( '- Dashboard: Error while enabling ' + oauth_id + ' for ' + user_id + ': ' + dberror ); 97 | return res('/user', 'savefail'); 98 | } ); 99 | return db.query( 'SELECT FROM oauthusers WHERE userid = $1 AND site = $2', [user_id, oauth_id] ).then( ({rows:[row]}) => { 100 | if ( type === 'disable' ) { 101 | let sql = 'INSERT INTO oauthusers(userid, site, token) VALUES ($1, $2, $3)'; 102 | if ( row ) sql = 'UPDATE oauthusers SET token = $3 WHERE userid = $1 AND site = $2'; 103 | return db.query( sql, [user_id, oauth_id, null] ).then( () => { 104 | console.log( '- Dashboard: Successfully disabled ' + oauth_id + ' for ' + user_id + '.' ); 105 | return res('/user', 'save'); 106 | }, dberror => { 107 | console.log( '- Dashboard: Error while disabling ' + oauth_id + ' for ' + user_id + ': ' + dberror ); 108 | return res('/user', 'savefail'); 109 | } ); 110 | } 111 | if ( type !== 'connect' ) return res('/user', 'savefail'); 112 | var oauthSite = enabledOAuth2.find( oauthSite => oauthSite.id === oauth_id ); 113 | if ( row ) db.query( 'DELETE FROM oauthusers WHERE userid = $1 AND site = $2', [user_id, oauth_id] ).then( () => { 114 | console.log( '- Dashboard: Successfully disconnected ' + user_id + ' from ' + oauth_id + ' for reconnection.' ); 115 | }, dberror => { 116 | console.log( '- Dashboard: Error while disconnecting ' + user_id + ' from ' + oauth_id + ' for reconnection: ' + dberror ); 117 | } ); 118 | let oauthURL = oauthSite.url + 'rest.php/oauth2/authorize?' + new URLSearchParams({ 119 | response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href, 120 | client_id: process.env['oauth_' + oauthSite.id], state: oauthSite.id 121 | }).toString(); 122 | return res(oauthURL, 'REDIRECT'); 123 | }, dberror => { 124 | console.log( '- Dashboard: Error while getting the OAuth2 info on ' + oauth_id + ' for ' + user_id + ': ' + dberror ); 125 | return res('/user', 'savefail'); 126 | } ); 127 | } 128 | 129 | export { 130 | dashboard_user as get, 131 | update_user as post 132 | }; -------------------------------------------------------------------------------- /dashboard/globals.js: -------------------------------------------------------------------------------- 1 | globalThis.isDebug = ( process.argv[2] === 'debug' ); -------------------------------------------------------------------------------- /dashboard/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from './i18n/allLangs.json' with { type: 'json' }; 2 | import { defaultSettings } from '../util/defaults.js'; 3 | import { escapeText } from './util.js'; 4 | 5 | await Promise.all( 6 | Object.keys(i18n.allLangs.names).map( async lang => i18n[lang] = ( await import(`./i18n/${lang}.json`, {with: {type: 'json'}}) ).default ) 7 | ); 8 | 9 | /** 10 | * A language. 11 | * @class 12 | */ 13 | export default class Lang { 14 | /** 15 | * Creates a new language. 16 | * @param {String[]} [langs] - The language code. 17 | * @constructs Lang 18 | */ 19 | constructor(...langs) { 20 | this.lang = ( langs.filter( lang => { 21 | if ( typeof lang !== 'string' ) return false; 22 | return i18n.allLangs.map.hasOwnProperty(lang.toLowerCase()); 23 | } ).map( lang => { 24 | return i18n.allLangs.map[lang.toLowerCase()]; 25 | } )[0] || defaultSettings.lang ); 26 | this.fallback = ( i18n?.[this.lang]?.fallback.slice() || [defaultSettings.lang] ).filter( fb => fb.trim() ); 27 | this.fromCookie = []; 28 | } 29 | 30 | /** 31 | * Get a localized message. 32 | * @param {String} message - Name of the message. 33 | * @param {Boolean} escaped - If the message should be HTML escaped. 34 | * @param {(String|import('cheerio').CheerioAPI)[]} args - Arguments for the message. 35 | * @returns {String} 36 | */ 37 | get(message = '', escaped = false, ...args) { 38 | let keys = ( message.length ? message.split('.') : [] ); 39 | let lang = this.lang; 40 | let text = i18n?.[lang]; 41 | let fallback = 0; 42 | for ( let n = 0; n < keys.length; n++ ) { 43 | if ( text ) { 44 | text = text?.[keys[n]]; 45 | if ( typeof text === 'string' ) text = text.trim(); 46 | } 47 | if ( !text ) { 48 | if ( fallback < this.fallback.length ) { 49 | lang = this.fallback[fallback]; 50 | fallback++; 51 | text = i18n?.[lang]; 52 | n = -1; 53 | } 54 | else { 55 | n = keys.length; 56 | } 57 | } 58 | } 59 | if ( typeof text === 'string' ) { 60 | if ( escaped ) text = escapeText(text); 61 | args.forEach( (arg, i) => { 62 | if ( escaped && typeof arg !== 'string' ) { 63 | text = text.replaceSafe( new RegExp( `\\[([^\\]]+)\\]\\(\\$${i + 1}\\)`, 'g' ), (m, linkText) => { 64 | return arg.html(linkText); 65 | } ); 66 | } 67 | text = text.replaceAllSafe( `$${i + 1}`, arg ); 68 | } ); 69 | if ( text.includes( 'PLURAL:' ) ) text = text.replace( /{{\s*PLURAL:\s*[+-]?(\d+)\s*\|\s*([^\{\}]*?)\s*}}/g, (m, number, cases) => { 70 | return plural(lang, parseInt(number, 10), cases.split(/\s*\|\s*/)); 71 | } ); 72 | } 73 | return ( text || '⧼' + message + ( isDebug && args.length ? ': ' + args.join(', ') : '' ) + '⧽' ); 74 | } 75 | 76 | /** 77 | * Get a localized message with all fallback languages. 78 | * @param {String} message - Name of the message. 79 | * @returns {Object[]} 80 | */ 81 | getWithFallback(message = '') { 82 | return [this.lang, ...this.fallback].map( lang => { 83 | let keys = ( message.length ? message.split('.') : [] ); 84 | let text = i18n?.[lang]; 85 | for ( let n = 0; n < keys.length; n++ ) { 86 | if ( text ) text = text?.[keys[n]]; 87 | if ( !text ) n = keys.length; 88 | } 89 | return text; 90 | } ); 91 | } 92 | 93 | /** 94 | * Get names for all languages. 95 | * @static 96 | */ 97 | static allLangs() { 98 | return i18n.allLangs; 99 | } 100 | } 101 | 102 | /** 103 | * Parse plural text. 104 | * @param {String} lang - The language code. 105 | * @param {Number} number - The amount. 106 | * @param {String[]} args - The possible text. 107 | * @returns {String} 108 | */ 109 | function plural(lang, number, args) { 110 | // https://translatewiki.net/wiki/Plural/Mediawiki_plural_rules 111 | var text = args[args.length - 1]; 112 | switch ( lang ) { 113 | case 'fr': 114 | case 'hi': 115 | if ( number <= 1 ) text = getArg(args, 0); 116 | else text = getArg(args, 1); 117 | break; 118 | case 'pl': 119 | if ( number === 1 ) text = getArg(args, 0); 120 | else if ( [2,3,4].includes( number % 10 ) && ![12,13,14].includes( number % 100 ) ) { 121 | text = getArg(args, 1); 122 | } 123 | else text = getArg(args, 2); 124 | break; 125 | case 'ru': 126 | case 'sr': 127 | case 'uk': 128 | if ( args.length > 2 ) { 129 | if ( number % 10 === 1 && number % 100 !== 11 ) text = getArg(args, 0); 130 | else if ( [2,3,4].includes( number % 10 ) && ![12,13,14].includes( number % 100 ) ) { 131 | text = getArg(args, 1); 132 | } 133 | else text = getArg(args, 2); 134 | } 135 | else { 136 | if ( number === 1 ) text = getArg(args, 0); 137 | else text = getArg(args, 1); 138 | } 139 | break; 140 | case 'bn': 141 | case 'de': 142 | case 'el': 143 | case 'en': 144 | case 'es': 145 | case 'id': 146 | case 'it': 147 | case 'ja': 148 | case 'ko': 149 | case 'lzh': 150 | case 'nl': 151 | case 'pt-br': 152 | case 'th': 153 | case 'sv': 154 | case 'tr': 155 | case 'vi': 156 | case 'zh-hans': 157 | case 'zh-hant': 158 | default: 159 | if ( number === 1 ) text = getArg(args, 0); 160 | else text = getArg(args, 1); 161 | } 162 | return text; 163 | } 164 | 165 | /** 166 | * Get text option. 167 | * @param {String[]} args - The list of options. 168 | * @param {Number} index - The preferred option. 169 | * @returns {String} 170 | */ 171 | function getArg(args, index) { 172 | return ( args.length > index ? args[index] : args[args.length - 1] ); 173 | } 174 | 175 | export const allLangs = Lang.allLangs; -------------------------------------------------------------------------------- /dashboard/i18n/allLangs.json: -------------------------------------------------------------------------------- 1 | { 2 | "allLangs": { 3 | "names": { 4 | "en": "English (en)", 5 | "bn": "বাংলা (bn)", 6 | "de": "Deutsch (de)", 7 | "el": "Ελληνικά (el)", 8 | "es": "Español (es)", 9 | "fr": "Français (fr)", 10 | "hi": "हिन्दी (hi)", 11 | "id": "Bahasa Indonesia (id)", 12 | "it": "Italiano (it)", 13 | "ja": "日本語 (ja)", 14 | "ko": "한국어 (ko)", 15 | "pl": "Polski (pl)", 16 | "pt-br": "Português do Brasil (pt-br)", 17 | "ru": "Русский (ru)", 18 | "tr": "Türkçe (tr)", 19 | "vi": "Tiếng Việt (vi)", 20 | "zh-hans": "简体中文 (zh-hans)", 21 | "zh-hant": "繁體中文 (zh-hant)" 22 | }, 23 | "map": { 24 | "en": "en", 25 | "en-gb": "en", 26 | "en-us": "en", 27 | "eng": "en", 28 | "english": "en", 29 | "english (en)": "en", 30 | "bn": "bn", 31 | "bengali": "bn", 32 | "বাংলা": "bn", 33 | "বাংলা (bn)": "bn", 34 | "de": "de", 35 | "german": "de", 36 | "deutsch": "de", 37 | "deutsch (de)": "de", 38 | "el": "el", 39 | "greek": "el", 40 | "ελληνικά": "el", 41 | "ελληνικά (el)": "el", 42 | "es": "es", 43 | "es-es": "es", 44 | "spanish": "es", 45 | "español": "es", 46 | "español (es)": "es", 47 | "fr": "fr", 48 | "french": "fr", 49 | "français": "fr", 50 | "français (fr)": "fr", 51 | "hi": "hi", 52 | "hindi": "hi", 53 | "हिन्दी": "hi", 54 | "हिन्दी (hi)": "hi", 55 | "id": "id", 56 | "indonesian":"id", 57 | "bahasa indonesia": "id", 58 | "bahasa indonesia (id)": "id", 59 | "it": "it", 60 | "italian":"it", 61 | "italiano": "it", 62 | "italiano (it)": "it", 63 | "ja": "ja", 64 | "japanese": "ja", 65 | "日本語": "ja", 66 | "日本語 (ja)": "ja", 67 | "ko": "ko", 68 | "korean": "ko", 69 | "한국어": "ko", 70 | "한국어 (ko)": "ko", 71 | "pl": "pl", 72 | "polish": "pl", 73 | "polski": "pl", 74 | "polski (pl)": "pl", 75 | "pt": "pt-br", 76 | "portuguese": "pt-br", 77 | "português": "pt-br", 78 | "português (pt)": "pt-br", 79 | "pt-br": "pt-br", 80 | "brazilian portuguese": "pt-br", 81 | "português do brasil": "pt-br", 82 | "português do brasil (pt-br)": "pt-br", 83 | "ru": "ru", 84 | "russian": "ru", 85 | "русский": "ru", 86 | "русский (ru)": "ru", 87 | "tr": "tr", 88 | "turkish": "tr", 89 | "turkçe": "tr", 90 | "turkçe (tr)": "tr", 91 | "vi": "vi", 92 | "vietnamese": "vi", 93 | "tiếng việt": "vi", 94 | "tiếng việt (vi)": "vi", 95 | "zh": "zh-hans", 96 | "chinese": "zh-hans", 97 | "中文": "zh-hans", 98 | "汉语": "zh-hans", 99 | "漢語": "zh-hant", 100 | "zh-hans": "zh-hans", 101 | "zh-cn": "zh-hans", 102 | "chinese (simplified)": "zh-hans", 103 | "simplified chinese": "zh-hans", 104 | "简体中文": "zh-hans", 105 | "简体中文 (zh-hans)": "zh-hans", 106 | "zh-hant": "zh-hant", 107 | "zh-tw": "zh-hant", 108 | "chinese (traditional)": "zh-hant", 109 | "traditional chinese": "zh-hant", 110 | "chinese (taiwan)": "zh-hant", 111 | "繁體中文": "zh-hant", 112 | "繁體中文 (zh-hant)": "zh-hant" 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /dashboard/i18n/et.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "en", 4 | " ", 5 | " ", 6 | " ", 7 | " " 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /dashboard/i18n/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "en", 4 | " ", 5 | " ", 6 | " ", 7 | " " 8 | ], 9 | "general": { 10 | "botlist": { 11 | "text": "봇 리스트에 투표해서 다른 사람들이 Wiki-Bot을 찾도록 도와주세요!", 12 | "title": "봇 목록" 13 | }, 14 | "delete": "삭제", 15 | "invite": "Wiki-Bot 초대", 16 | "language": "언어 변경", 17 | "login": "로그인", 18 | "logout": "로그아웃", 19 | "rcscript": "최근 바뀜", 20 | "refresh": "서버 목록 새로고침", 21 | "save": "저장", 22 | "selector": "서버 선택기", 23 | "settings": "설정", 24 | "support": "지원 서버", 25 | "theme-dark": "다크 테마 사용", 26 | "theme-light": "밝은 테마 사용", 27 | "title": "Wiki-Bot 설정", 28 | "verification": "인증", 29 | "welcome": "

Wiki-Bot 대시보드에 오신 것을 환영합니다.

\n

Wiki-Bot은 디스코드 서버와 미디어위키 위키를 통합하기 위한 디스코드 봇입니다. 위키링크를 연결하고, 위키 사용자 계정을 인증하고, 위키의 최근 바뀜을 전달하는 것 외에 여러 작업을 수행할 수 있습니다. [더 많은 정보]

\n

이 사이트에서 서버 관리하기 권한을 가진 서버의 봇 설정을 관리할 수 있습니다. 시작하려면 아래 버튼을 이용하여 디스코드 계정으로 로그인하세요.

" 30 | }, 31 | "indexjs": { 32 | "invalid": { 33 | "note_http": "제공된 웹사이트는 유효한 TLS/SSL 인증서를 제공하고 있지 않습니다! 보안을 위해 HTTPS를 이용하는 위키만 추가할 수 있습니다.", 34 | "note_private": "제공된 위키는 비공개입니다!", 35 | "note_timeout": "제공된 링크가 너무 오래 응답하지 않습니다!", 36 | "text": "URL에서 유효한 미디어위키 사이트로 연결할 수 없었습니다!", 37 | "title": "무효한 위키입니다!" 38 | }, 39 | "outdated": { 40 | "text": "최근 바뀜 웹훅은 최소 미디어위키 1.30 이상을 사용하는 위키여야 합니다!", 41 | "title": "오래된 미디어위키 버전!" 42 | }, 43 | "prefix": { 44 | "backslash": "접두어는 백슬래시 (_)를 포함하면 안 됩니다!", 45 | "code": "접두어는 마크다운 코드를 포함하면 안 됩니다!", 46 | "space": "접두어는 스페이스를 포함하면 안 됩니다!" 47 | }, 48 | "sysmessage": { 49 | "text": "$1 문서는 서버 아이디 $2 와 일치해야 합니다.", 50 | "title": "시스템 메시지가 일치하지 않습니다!" 51 | }, 52 | "valid": { 53 | "MediaWiki": "경고: 완전한 기능 동작을 위해 최소 $1 이(가) 필요합니다.", 54 | "title": "이 위키는 유효하고 사용할 수 있습니다!" 55 | } 56 | }, 57 | "notice": { 58 | "error": { 59 | "text": "알 수 없는 오류가 발생했습니다, 다시 시도해 주세요.", 60 | "title": "알 수 없는 오류!" 61 | }, 62 | "invalidusergroup": { 63 | "text": "사용자 권한 이름이 너무 길었거나 너무 많은 사용자 권한을 제공했습니다.", 64 | "title": "유효하지 않은 사용자 권한!" 65 | }, 66 | "loginfail": { 67 | "text": "로그인 중에 오류가 발생했습니다, 다시 시도해 주세요.", 68 | "title": "로그인 실패!" 69 | }, 70 | "logout": { 71 | "text": "성공적으로 로그아웃되었습니다. 설정을 변경하려면 다시 로그인해야 합니다.", 72 | "title": "성공적으로 로그아웃했습니다!" 73 | }, 74 | "missingperm": { 75 | "text": "사용자 또는 Wiki-Bot에 $1 권한이 없어서 이 기능을 설정할 수 없습니다.", 76 | "title": "권한 부족!" 77 | }, 78 | "mwversion": { 79 | "text": "미디어위키 1.30 이상이 필요하지만, $2 위키에서 $1 버전을 발견했습니다.", 80 | "title": "오래된 미디어위키 버전!" 81 | }, 82 | "nochange": { 83 | "text": "설정이 현재 기본 설정과 일치합니다.", 84 | "title": "저장 실패!" 85 | }, 86 | "nosettings": { 87 | "note": "설정 변경.", 88 | "text": "서버 설정을 먼저 정의해 주세요.", 89 | "title": "서버가 설정되지 않았습니다!" 90 | }, 91 | "readonly": { 92 | "text": "현재 설정을 볼 수는 있지만, 변경할 수는 없습니다.", 93 | "title": "데이터베이스 읽기 전용!" 94 | }, 95 | "refresh": { 96 | "text": "서버 리스트를 새로 불러왔습니다.", 97 | "title": "새로고침 성공!" 98 | }, 99 | "refreshfail": { 100 | "text": "서버 목록을 새로 고칠 수 없었습니다, 다시 시도해 주세요.", 101 | "title": "새로고침 실패!" 102 | }, 103 | "save": { 104 | "text": "설정이 업데이트되었습니다.", 105 | "title": "설정 저장됨!" 106 | }, 107 | "savefail": { 108 | "note_http": "제공된 웹사이트에는 유효한 TLS/SSL 인증서를 제공하고 있지 않습니다! 보안을 위해 HTTPS를 이용하는 위키만 추가할 수 있습니다.", 109 | "note_private": "제공된 위키는 비공개입니다!", 110 | "note_timeout": "제공된 링크가 너무 오래 응답하지 않습니다!", 111 | "text": "설정을 저장할 수 없었습니다, 다시 시도해 주세요.", 112 | "title": "저장 실패!" 113 | }, 114 | "sysmessage": { 115 | "text": "$1 페이지가 서버 ID $2 와(과) 일치해야 합니다.", 116 | "title": "시스템 메시지가 일치하지 않습니다!" 117 | }, 118 | "unauthorized": { 119 | "text": "설정을 변경하기 전에 로그인해 주세요.", 120 | "title": "로그인하지 않음!" 121 | }, 122 | "webhookfail": { 123 | "note": "디스코드 웹훅을 변경할 수 없었습니다!", 124 | "text": "설정의 일부만 업데이트했습니다.", 125 | "title": "설정을 일부만 저장했습니다!" 126 | }, 127 | "wikiblocked": { 128 | "note": "이유:", 129 | "text": "$1 위키는 최근 바뀜 웹훅으로 추가되지 못하도록 차단되어 있습니다.", 130 | "title": "위키 차단됨!" 131 | } 132 | }, 133 | "rcscript": { 134 | "desc": "$1 서버의 최근 바뀜 웹훅입니다:", 135 | "explanation": "

최근 바뀜 웹훅

\n

Wiki-Bot은 RcGcDw 기반 최근 바뀜 웹훅을 지원합니다. 최근 바뀜 내용은 인라인 링크를 포함한 압축 텍스트 모드로 발송되거나, 편집 태그와 분류 변경을 포함하는 임베드 메시지로 발송될 수 있습니다.

\n

최근 바뀜 웹훅을 추가하기 위한 조건:

\n
    \n
  • 위키가 미디어위키 1.30 또는 그 이상 버전을 지원해야 합니다.
  • \n
  • MediaWiki:Custom-RcGcDw 시스템 메시지가 디스코드 서버 ID 로 설정되어야 합니다.
  • \n
", 136 | "form": { 137 | "channel": "채널:", 138 | "confirm": "정말로 최근 바뀜 웹훅을 삭제할 건가요?", 139 | "display": "표시 모드:", 140 | "display_compact": "인라인 링크가 포함된 압축된 글 메시지를 표시합니다.", 141 | "display_diff": "사진 미리보기와 편집 차이가 포함된 메시지 임베드를 표시합니다.", 142 | "display_embed": "편집 태그와 분류 변경이 포함된 메시지 임베드를 표시합니다.", 143 | "display_image": "사진 미리보기가 포함된 메시지 임베드를 표시합니다.", 144 | "entry": "최근 바뀜 웹훅 #$1", 145 | "feeds": "피드 기반 변경:", 146 | "feeds_only": "피드 기반 변경 전용:", 147 | "lang": "언어:", 148 | "new": "새 최근 바뀜 웹훅", 149 | "select_channel": "-- 채널을 선택하세요 --", 150 | "wiki": "위키:", 151 | "wiki_check": "위키 검사" 152 | }, 153 | "new": "새 웹훅" 154 | }, 155 | "selector": { 156 | "switch": "계정 변경", 157 | "title": "서버 선택기", 158 | "with": "Wiki-Bot이 있는 서버", 159 | "without": "Wiki-Bot이 없는 서버" 160 | }, 161 | "settings": { 162 | "desc": "$1 서버의 설정입니다:", 163 | "failed": "설정을 불러오지 못했습니다!", 164 | "form": { 165 | "channel": "채널:", 166 | "confirm": "채널 덮어쓰기를 삭제할까요?", 167 | "default": "서버-단위 설정", 168 | "inline": "인라인 명령어:", 169 | "lang": "언어:", 170 | "new": "새 채널 덮어쓰기", 171 | "overwrite": "$1 설정", 172 | "prefix": "접두어:", 173 | "prefix_space": "접두어 끝에 공백이 붙습니까?:", 174 | "role": "최소 역할:", 175 | "select_channel": "-- 채널을 선택하세요 --", 176 | "wiki": "기본 위키:", 177 | "wiki_check": "위키 검사" 178 | }, 179 | "new": "새 채널 덮어쓰기" 180 | }, 181 | "verification": { 182 | "desc": "$1 서버의 인증입니다:", 183 | "explanation": "

사용자 인증

\n

verify <위키 사용자이름>명령어를 이용해, 사용자가 위키 프로필의 디스코드 태그와 일치한다는 것을 증명할 수 있습니다. 사용자 계정이 일치하고, 사용자 인증이 서버에 설정되어 있는 경우, Wiki-Bot이 해당하는 인증 역할을 부여합니다.

\n

각각의 인증 항목을 통해 인증 조건을 세부설정할 수 있습니다:

\n
    \n
  • verify 명령어를 사용할 수 있는 채널.
  • \n
  • 인증이 성공하면 얻을 역할.
  • \n
  • 인증이 성공하기 위해 필요한 위키 편집 횟수.
  • \n
  • 인증이 성공하기 위해 필요한 사용자 권한.
  • \n
  • 인증이 성공하기 위해 필요한 계정 생성 이후 지난 시간.
  • \n
  • 인증이 성공하면 디스코드 닉네임을 위키 계정과 일치하도록 변경할지 여부
  • \n
", 184 | "form": { 185 | "accountage": "계정 기간 (일 기준으로):", 186 | "channel": "채널:", 187 | "confirm": "정말 이 인증을 삭제하시겠어요?", 188 | "editcount": "최소 편집 횟수:", 189 | "entry": "인증 #$1", 190 | "flag_logall": "인증 실패를 기록할 채널:", 191 | "flag_private": "응답을 개인 메시지로 할 지 여부:", 192 | "logging": "기록 채널:", 193 | "match": "요구조건 불충족 공지:", 194 | "match_placeholder": "디스코드 태그가 일치하지만 역할을 받기 위한 조건을 충족하지 않습니다.", 195 | "more": "더 추가", 196 | "new": "새 인증", 197 | "notice": "인증 공지", 198 | "postcount": "최소 게시글 갯수:", 199 | "postcount_and": "편집 횟수와 게시글 횟수를 모두 확인합니다.", 200 | "postcount_both": "편집 횟수와 게시글 횟수를 통합하여 확인합니다.", 201 | "postcount_fandom": "팬덤 위키에서만:", 202 | "postcount_or": "편집 횟수 또는 게시글 횟수 중 하나만 확인합니다.", 203 | "rename": "이름 변경:", 204 | "role": "역할:", 205 | "role_add": "추가", 206 | "role_remove": "삭제", 207 | "select_channel": "-- 채널을 선택하세요 --", 208 | "select_role": "-- 역할을 선택하세요 --", 209 | "success": "성공 공지:", 210 | "success_placeholder": "인증이 성공하면 표시할 마크다운 본문.", 211 | "usergroup": "위키 사용자 권한:", 212 | "usergroup_and": "모든 사용자 권한 강제:" 213 | }, 214 | "new": "새 인증", 215 | "notice": "인증 공지" 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /dashboard/i18n/lzh.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "zh-hant", 4 | "zh-hans", 5 | "en", 6 | " ", 7 | " " 8 | ] 9 | } -------------------------------------------------------------------------------- /dashboard/i18n/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "en", 4 | " ", 5 | " ", 6 | " ", 7 | " " 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /dashboard/i18n/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "en", 4 | " ", 5 | " ", 6 | " ", 7 | " " 8 | ] 9 | } -------------------------------------------------------------------------------- /dashboard/i18n/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "en", 4 | " ", 5 | " ", 6 | " ", 7 | " " 8 | ], 9 | "general": { 10 | "botlist": { 11 | "text": "Hjälp andra att hitta Wiki-Bot genom att rösta på den i dessa bot-listor:", 12 | "title": "Bot-listor" 13 | }, 14 | "delete": "Radera", 15 | "invite": "Bjud in Wiki-Bot", 16 | "language": "Ändra Språk", 17 | "login": "Logga in", 18 | "logout": "Logga ut", 19 | "rcscript": "Senaste ändringarna", 20 | "refresh": "Uppdatera serverlistan", 21 | "save": "Spara", 22 | "selector": "Serverväljare", 23 | "settings": "Inställningar", 24 | "support": "Support-server", 25 | "theme-dark": "Använd mörkt tema", 26 | "theme-light": "Använd ljust tema", 27 | "title": "Wiki Bot-inställningar", 28 | "verification": "Verifieringar" 29 | }, 30 | "indexjs": { 31 | "invalid": { 32 | "title": "Ogiltig wiki!" 33 | }, 34 | "outdated": { 35 | "text": "Webhooken för de senaste ändringarna kräver minst MediaWiki 1.30!", 36 | "title": "En gammal MediaWiki version används!" 37 | }, 38 | "prefix": { 39 | "backslash": "Prefixet får inte innehålla snedstreck!", 40 | "code": "Prefixet får inte innehålla kodmarkering!", 41 | "space": "Prefixet får inte innehålla mellanslag!" 42 | }, 43 | "sysmessage": { 44 | "text": "Sidan $1 måste matcha server-id:t $2.", 45 | "title": "Systemmeddelandet matchar inte!" 46 | }, 47 | "valid": { 48 | "MediaWiki": "Varning: Kräver minst $1 för full funktionalitet.", 49 | "title": "Wikin är giltig och kan användas!" 50 | } 51 | }, 52 | "notice": { 53 | "error": { 54 | "text": "Ett okänt fel uppstod, försök igen.", 55 | "title": "Okänt fel!" 56 | } 57 | }, 58 | "rcscript": { 59 | "form": { 60 | "lang": "Språk:" 61 | } 62 | }, 63 | "settings": { 64 | "form": { 65 | "lang": "Språk:" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /dashboard/i18n/th.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "en", 4 | " ", 5 | " ", 6 | " ", 7 | " " 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /dashboard/i18n/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": [ 3 | "en", 4 | " ", 5 | " ", 6 | " ", 7 | " " 8 | ] 9 | } -------------------------------------------------------------------------------- /dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wiki-Bot Settings 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 121 | 122 | -------------------------------------------------------------------------------- /dashboard/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login – Wiki-Bot Settings 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |

Welcome on Wiki-Bot Dashboard.

24 |

Wiki-Bot is a Discord bot made to bring Discord servers and MediaWiki wikis together. It helps with linking wiki pages, verifying wiki users, informing about latest changes on the wiki and more. [More information]

25 |

Here you can change different bot settings for servers you have Manage Server permission on. To begin, you will have to authenticate your Discord account which you can do with this button:

26 |
27 | 41 |
42 |

Enjoy a little story?

43 |

A long time ago, when the world was still figuring out itself, a new faction was born, Gamepedia its name. The kingdom grew quickly, people wanted their own place in the kingdom, it was said the kingdom was a better place than any other kingdoms, which were ridden with very old infrastructure, the taxes enforced by their kings were too high and the kings not too merciful. On the other side, the new kingdom was blossoming, people from all over the world wanted to live there, have their own place there and to do that, they joined existing guilds of people who share their interests. The king here really cared about his most devoted citizens giving them tax exemption status and helping all of them so each of the guilds in the kingdom can prosper and spread the good word about the kingdom.

44 |

Soon enough the first bigger guilds were joining the kingdom, seeing the greatness of it they joined the kingdom along with their huge tracts. The momentum of growth became a sign of change, a change for the better future. One of the first great King's advisors was Wyn. She was passionate and very talented in all fields needed to manage the kingdom. She enthusiastically welcomed new guilds and made sure there is nothing on their way to be a fully functioning guilds on Gamepedia.

45 |

At first the biggest guilds in the kingdom included a guild which consisted of people who devoted their lives to punching the trees with their bare fists, …

46 | [Read more] 47 |
48 |
49 |
50 | 123 | 124 | -------------------------------------------------------------------------------- /dashboard/src/channel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dashboard/src/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /dashboard/src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/dashboard/src/icon.png -------------------------------------------------------------------------------- /dashboard/src/lang.js: -------------------------------------------------------------------------------- 1 | var currentTheme = ( document.cookie.split('; ').find( cookie => { 2 | return cookie.split('=')[0] === 'theme' && /^"(?:light|dark)"$/.test(( cookie.split('=')[1] || '' )); 3 | } ) || 'dark' ).replace( /^theme="(light|dark)"$/, '$1' ); 4 | var lightTheme = document.getElementById('theme-light'); 5 | var darkTheme = document.getElementById('theme-dark'); 6 | lightTheme.onclick = function() { 7 | document.cookie = 'theme="light"; Path=/; Max-Age=31536000'; 8 | document.documentElement.classList.add('theme-light'); 9 | lightTheme.setAttribute('style', 'display: none;'); 10 | darkTheme.removeAttribute('style'); 11 | }; 12 | darkTheme.onclick = function() { 13 | document.cookie = 'theme="dark"; Path=/; Max-Age=31536000'; 14 | document.documentElement.classList.remove('theme-light'); 15 | darkTheme.setAttribute('style', 'display: none;'); 16 | lightTheme.removeAttribute('style'); 17 | }; 18 | document.getElementById('theme-separator').removeAttribute('style'); 19 | if ( currentTheme === 'light' ) { 20 | darkTheme.removeAttribute('style'); 21 | document.documentElement.classList.add('theme-light'); 22 | } 23 | else { 24 | lightTheme.removeAttribute('style'); 25 | document.documentElement.classList.remove('theme-light'); 26 | } 27 | 28 | var channellist = document.getElementById('channellist'); 29 | var langSelector = document.createElement('div'); 30 | langSelector.id = 'lang-selector'; 31 | langSelector.textContent = selectLanguage; 32 | var langIcon = document.createElement('img'); 33 | langIcon.setAttribute('src', '/src/language.svg'); 34 | langSelector.prepend(langIcon); 35 | var langDropdown = document.createElement('div'); 36 | langDropdown.id = 'lang-dropdown'; 37 | langDropdown.setAttribute('style', `max-height: ${window.innerHeight - 80}px;`); 38 | var langOptions = Object.keys(allLangs).map( function(lang) { 39 | var langOption = document.createElement('div'); 40 | langOption.textContent = allLangs[lang]; 41 | if ( document.documentElement.lang === lang ) langOption.className = 'current'; 42 | langOption.onclick = function() { 43 | document.cookie = `language="${lang}"; Path=/; Max-Age=31536000`; 44 | location.reload(); 45 | }; 46 | return langOption; 47 | } ); 48 | langDropdown.append(...langOptions); 49 | langSelector.append(langDropdown); 50 | channellist.after(langSelector); 51 | channellist.setAttribute('style', 'bottom: 32px;'); 52 | var selectedChannel = channellist.querySelector('.channel.selected'); 53 | if ( selectedChannel ) { 54 | var selectedChannelOffset = channellist.offsetHeight - selectedChannel.offsetTop; 55 | if ( selectedChannelOffset < 64 ) channellist.scrollBy(0, 64 - selectedChannelOffset); 56 | } -------------------------------------------------------------------------------- /dashboard/src/language.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dashboard/src/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /functions/global_block.js: -------------------------------------------------------------------------------- 1 | import { load as cheerioLoad } from 'cheerio'; 2 | import { got, isMessage, canShowEmbed, escapeFormatting } from '../util/functions.js'; 3 | 4 | /** 5 | * Add global blocks to user messages. 6 | * @param {import('../util/i18n.js').default} lang - The user language. 7 | * @param {import('discord.js').Message|import('discord.js').ChatInputCommandInteraction} msg - The Discord message. 8 | * @param {String} username - The name of the user. 9 | * @param {String} text - The text of the response. 10 | * @param {import('discord.js').EmbedBuilder} embed - The embed for the page. 11 | * @param {import('../util/wiki.js').default} wiki - The wiki for the page. 12 | * @param {String} spoiler - If the response is in a spoiler. 13 | * @param {String} [gender] - The gender of the user. 14 | * @param {String} [isGlobalBlocked] - If the user is globally blocked. 15 | * @returns {Promise} The edited message. 16 | */ 17 | export default function global_block(lang, msg, username, text, embed, wiki, spoiler, gender, isGlobalBlocked) { 18 | if ( !msg || !msg.inGuild() || !patreonGuildsPrefix.has(msg.guildId) || wiki.wikifarm !== 'fandom' ) return; 19 | if ( embed && !canShowEmbed(msg) ) embed = null; 20 | 21 | var isUser = true; 22 | if ( !gender ) { 23 | isUser = false; 24 | gender = 'unknown'; 25 | } 26 | 27 | if ( isMessage(msg) ) { 28 | if ( embed ) { 29 | embed.spliceFields( -1, 1 ); 30 | if ( isGlobalBlocked ) embed.spliceFields( -1, 1 ); 31 | } 32 | else { 33 | let splittext = text.split('\n\n'); 34 | splittext.pop(); 35 | if ( isGlobalBlocked ) splittext.pop(); 36 | text = splittext.join('\n\n'); 37 | } 38 | } 39 | 40 | return Promise.all([ 41 | got.get( 'https://community.fandom.com/wiki/Special:Contributions/' + encodeURIComponent( username ) + '?limit=1', { 42 | responseType: 'text', 43 | context: { 44 | guildId: msg.guildId 45 | } 46 | } ).then( response => { 47 | var body = response.body; 48 | if ( response.statusCode !== 200 || !body ) { 49 | console.log( '- ' + response.statusCode + ': Error while getting the global block.' ); 50 | } 51 | else { 52 | let $ = cheerioLoad(body, {baseURI: response.url}); 53 | if ( $('#mw-content-text .userprofile.mw-warning-with-logexcerpt').length ) { 54 | if ( embed ) embed.addFields( {name: '\u200b', value: '**' + lang.get('user.gblock.header', FIRST_STRONG_ISOLATE + escapeFormatting(username) + POP_DIRECTIONAL_ISOLATE, gender) + '**'} ); 55 | else text += '\n\n**' + lang.get('user.gblock.header', FIRST_STRONG_ISOLATE + escapeFormatting(username) + POP_DIRECTIONAL_ISOLATE, gender) + '**'; 56 | } 57 | if ( $('#mw-content-text .errorbox').length ) { 58 | if ( embed ) embed.addFields( {name: '\u200b', value: '**' + lang.get('user.gblock.disabled') + '**'} ); 59 | else text += '\n\n**' + lang.get('user.gblock.disabled') + '**'; 60 | } 61 | } 62 | }, error => { 63 | console.log( '- Error while getting the global block: ' + error ); 64 | } ), 65 | ( isUser && wiki.isGamepedia() ? got.get( 'https://help.fandom.com/wiki/UserProfile:' + encodeURIComponent( username ) + '?cache=' + Date.now(), { 66 | responseType: 'text', 67 | context: { 68 | guildId: msg.guildId 69 | } 70 | } ).then( gresponse => { 71 | var gbody = gresponse.body; 72 | if ( gresponse.statusCode !== 200 || !gbody ) { 73 | console.log( '- ' + gresponse.statusCode + ': Error while getting the global edit count.' ); 74 | } 75 | else { 76 | let $ = cheerioLoad(gbody, {baseURI: gresponse.url}); 77 | var wikisedited = $('.curseprofile .rightcolumn .section.stats dd').eq(0).prop('innerText').replace( /[,\.]/g, '' ); 78 | if ( wikisedited ) { 79 | wikisedited = parseInt(wikisedited, 10).toLocaleString(lang.get('dateformat')); 80 | if ( embed ) embed.spliceFields(1, 0, { 81 | name: lang.get('user.info.wikisedited'), 82 | value: wikisedited, 83 | inline: true 84 | }); 85 | else { 86 | let splittext = text.split('\n'); 87 | splittext.splice(5, 0, lang.get('user.info.wikisedited') + ' ' + wikisedited); 88 | text = splittext.join('\n'); 89 | } 90 | } 91 | var globaledits = $('.curseprofile .rightcolumn .section.stats dd').eq(2).prop('innerText').replace( /[,\.]/g, '' ); 92 | if ( globaledits ) { 93 | globaledits = parseInt(globaledits, 10).toLocaleString(lang.get('dateformat')); 94 | if ( embed ) embed.spliceFields(1, 0, { 95 | name: lang.get('user.info.globaleditcount'), 96 | value: globaledits, 97 | inline: true 98 | }); 99 | else { 100 | let splittext = text.split('\n'); 101 | splittext.splice(5, 0, lang.get('user.info.globaleditcount') + ' ' + globaledits); 102 | text = splittext.join('\n'); 103 | } 104 | } 105 | if ( embed ) { 106 | let avatar = $('.curseprofile .mainavatar img').prop('src'); 107 | if ( avatar ) { 108 | embed.setThumbnail( avatar.replace( /^(?:https?:)?\/\//, 'https://' ).replace( '?d=mm&s=96', '?d=' + encodeURIComponent( embed.data.thumbnail?.url || '404' ) ) ); 109 | } 110 | } 111 | } 112 | }, error => { 113 | console.log( '- Error while getting the global edit count: ' + error ); 114 | } ) : undefined ) 115 | ]).then( () => { 116 | var content = spoiler + text + spoiler; 117 | var embeds = []; 118 | if ( embed ) embeds.push(embed); 119 | if ( isMessage(msg) ) return msg.edit( {content, embeds} ).catch(log_error); 120 | else return {message: {content, embeds}}; 121 | } ); 122 | } -------------------------------------------------------------------------------- /functions/helpserver.js: -------------------------------------------------------------------------------- 1 | import help_setup from './helpsetup.js'; 2 | 3 | /** 4 | * Post a message about the help server. 5 | * @param {import('../util/i18n.js').default} lang - The user language. 6 | * @param {import('discord.js').Message} msg - The Discord message. 7 | */ 8 | export default function help_server(lang, msg) { 9 | if ( msg.isAdmin() && msg.defaultSettings ) help_setup(lang, msg); 10 | msg.sendChannel( lang.get('general.helpserver') + '\n' + process.env.invite ); 11 | } -------------------------------------------------------------------------------- /functions/helpsetup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Send send message to setup the bot. 3 | * @param {import('../util/i18n.js').default} lang - The user language. 4 | * @param {import('discord.js').Message} msg - The Discord message. 5 | */ 6 | export default function help_setup(lang, msg) { 7 | msg.defaultSettings = false; 8 | msg.replyMsg( lang.get('general.default', '`' + process.env.prefix + 'settings`') + ( process.env.dashboard ? '\n' + new URL(`/guild/${msg.guildId}/settings`, process.env.dashboard).href : '' ) ); 9 | } -------------------------------------------------------------------------------- /functions/rcscript_buttons.js: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'node:fs'; 2 | import gotDefault from 'got'; 3 | const got = gotDefault.extend( { 4 | throwHttpErrors: true, 5 | timeout: { 6 | request: 3_000 7 | }, 8 | headers: { 9 | 'user-agent': 'Wiki-Bot/internal' 10 | }, 11 | responseType: 'json' 12 | } ); 13 | 14 | const buttonsExists = ( process.env.buttons_token && process.env.buttons_url && existsSync('./RcGcDw_buttons/main.js') ); 15 | 16 | /** 17 | * @param {import('discord.js').ButtonInteraction<'cached'|'raw'>|import('discord.js').ModalSubmitInteraction<'cached'|'raw'>} interaction 18 | */ 19 | function rcscript_buttons(interaction) { 20 | got.post( process.env.buttons_url, { 21 | json: { 22 | version: interaction.version, 23 | type: interaction.type, 24 | id: interaction.id, 25 | token: interaction.token, 26 | application_id: interaction.applicationId, 27 | guild_id: interaction.guildId, 28 | channel_id: interaction.channelId, 29 | app_permissions: interaction.appPermissions, 30 | locale: interaction.locale, 31 | guild_locale: interaction.guildLocale, 32 | data: { 33 | custom_id: interaction.customId.replace( 'rc_', '' ), 34 | component_type: interaction.componentType, 35 | values: interaction.values, 36 | components: interaction.components?.map( row => { 37 | return { 38 | type: row.type, 39 | component: row.component && { 40 | type: row.component.type, 41 | value: row.component.value, 42 | values: row.component.values, 43 | custom_id: row.component.customId 44 | }, 45 | components: row.components?.map( component => { 46 | return { 47 | type: component.type, 48 | value: component.value, 49 | custom_id: component.customId 50 | }; 51 | } ) 52 | }; 53 | } ) 54 | }, 55 | message: ( interaction.message ? { 56 | id: interaction.message.id, 57 | content: interaction.message.content, 58 | embeds: interaction.message.embeds, 59 | components: interaction.message.components 60 | } : null ), 61 | member: { 62 | user: { 63 | id: interaction.user.id, 64 | }, 65 | } 66 | }, 67 | headers: { 68 | authorization: process.env.buttons_token 69 | } 70 | } ).then( response => { 71 | if ( response.body.result !== 'Success' ) console.log( '- RcGcDw buttons: ' + response.statusCode + ': Error while sending the interaction.' ); 72 | }, error => { 73 | console.log( '- RcGcDw buttons: Error while sending the interaction: ' + error ); 74 | } ); 75 | } 76 | 77 | export default ( buttonsExists ? rcscript_buttons : () => {} ); -------------------------------------------------------------------------------- /i18n/_new.json: -------------------------------------------------------------------------------- 1 | { 2 | "__translator": [ 3 | "", 4 | " ", 5 | " ", 6 | " ", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " " 13 | ], 14 | "aliases": { 15 | "bug": [ 16 | "", 17 | " ", 18 | " ", 19 | " ", 20 | " " 21 | ], 22 | "command": [ 23 | "", 24 | " ", 25 | " ", 26 | " ", 27 | " " 28 | ], 29 | "diff": [ 30 | "", 31 | " ", 32 | " ", 33 | " ", 34 | " " 35 | ], 36 | "discussion": [ 37 | "", 38 | " ", 39 | " ", 40 | " ", 41 | " " 42 | ], 43 | "help": [ 44 | "", 45 | " ", 46 | " ", 47 | " ", 48 | " " 49 | ], 50 | "info": [ 51 | "", 52 | " ", 53 | " ", 54 | " ", 55 | " " 56 | ], 57 | "invite": [ 58 | "", 59 | " ", 60 | " ", 61 | " ", 62 | " " 63 | ], 64 | "overview": [ 65 | "", 66 | " ", 67 | " ", 68 | " ", 69 | " " 70 | ], 71 | "page": [ 72 | "", 73 | " ", 74 | " ", 75 | " ", 76 | " " 77 | ], 78 | "random": [ 79 | "", 80 | " ", 81 | " ", 82 | " ", 83 | " " 84 | ], 85 | "search": [ 86 | "", 87 | " ", 88 | " ", 89 | " ", 90 | " " 91 | ], 92 | "test": [ 93 | "", 94 | " ", 95 | " ", 96 | " ", 97 | " " 98 | ], 99 | "user": [ 100 | "", 101 | " ", 102 | " ", 103 | " ", 104 | " " 105 | ], 106 | "verify": [ 107 | "", 108 | " ", 109 | " ", 110 | " ", 111 | " " 112 | ] 113 | }, 114 | "fallback": [ 115 | "en", 116 | " ", 117 | " ", 118 | " ", 119 | " " 120 | ], 121 | "test": { 122 | "text": [ 123 | "", 124 | " ", 125 | " ", 126 | " ", 127 | " ", 128 | " ", 129 | " ", 130 | " ", 131 | " ", 132 | " ", 133 | " ", 134 | " ", 135 | " ", 136 | " ", 137 | " ", 138 | " ", 139 | " ", 140 | " ", 141 | " ", 142 | " ", 143 | " ", 144 | " ", 145 | " ", 146 | " ", 147 | " ", 148 | " ", 149 | " ", 150 | " ", 151 | " ", 152 | " " 153 | ] 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /i18n/allLangs.json: -------------------------------------------------------------------------------- 1 | { 2 | "allLangs": { 3 | "names": { 4 | "en": "English (en)", 5 | "bn": "বাংলা (bn)", 6 | "de": "Deutsch (de)", 7 | "el": "Ελληνικά (el)", 8 | "es": "Español (es)", 9 | "fr": "Français (fr)", 10 | "hi": "हिन्दी (hi)", 11 | "id": "Bahasa Indonesia (id)", 12 | "it": "Italiano (it)", 13 | "ja": "日本語 (ja)", 14 | "ko": "한국어 (ko)", 15 | "pl": "Polski (pl)", 16 | "pt-br": "Português do Brasil (pt-br)", 17 | "ru": "Русский (ru)", 18 | "sv": "Svenska (sv)", 19 | "tr": "Türkçe (tr)", 20 | "uk": "Українська (uk)", 21 | "vi": "Tiếng Việt (vi)", 22 | "zh-hans": "简体中文 (zh-hans)", 23 | "zh-hant": "繁體中文 (zh-hant)" 24 | }, 25 | "map": { 26 | "en": "en", 27 | "en-gb": "en", 28 | "en-us": "en", 29 | "eng": "en", 30 | "english": "en", 31 | "english (en)": "en", 32 | "bn": "bn", 33 | "bengali": "bn", 34 | "বাংলা": "bn", 35 | "বাংলা (bn)": "bn", 36 | "de": "de", 37 | "german": "de", 38 | "deutsch": "de", 39 | "deutsch (de)": "de", 40 | "el": "el", 41 | "greek": "el", 42 | "ελληνικά": "el", 43 | "ελληνικά (el)": "el", 44 | "es": "es", 45 | "es-es": "es", 46 | "spanish": "es", 47 | "español": "es", 48 | "español (es)": "es", 49 | "fr": "fr", 50 | "french": "fr", 51 | "français": "fr", 52 | "français (fr)": "fr", 53 | "hi": "hi", 54 | "hindi": "hi", 55 | "हिन्दी": "hi", 56 | "हिन्दी (hi)": "hi", 57 | "id": "id", 58 | "indonesian":"id", 59 | "bahasa indonesia": "id", 60 | "bahasa indonesia (id)": "id", 61 | "it": "it", 62 | "italian":"it", 63 | "italiano": "it", 64 | "italiano (it)": "it", 65 | "ja": "ja", 66 | "japanese": "ja", 67 | "日本語": "ja", 68 | "日本語 (ja)": "ja", 69 | "ko": "ko", 70 | "korean": "ko", 71 | "한국어": "ko", 72 | "한국어 (ko)": "ko", 73 | "pl": "pl", 74 | "polish": "pl", 75 | "polski": "pl", 76 | "polski (pl)": "pl", 77 | "pt": "pt-br", 78 | "portuguese": "pt-br", 79 | "português": "pt-br", 80 | "português (pt)": "pt-br", 81 | "pt-br": "pt-br", 82 | "brazilian portuguese": "pt-br", 83 | "português do brasil": "pt-br", 84 | "português do brasil (pt-br)": "pt-br", 85 | "ru": "ru", 86 | "russian": "ru", 87 | "русский": "ru", 88 | "русский (ru)": "ru", 89 | "sv": "sv", 90 | "swedish": "sv", 91 | "svenska": "sv", 92 | "svenska (sv)": "sv", 93 | "tr": "tr", 94 | "turkish": "tr", 95 | "turkçe": "tr", 96 | "turkçe (tr)": "tr", 97 | "uk": "uk", 98 | "ukrainian": "uk", 99 | "українська": "uk", 100 | "українська (uk)": "uk", 101 | "vi": "vi", 102 | "vietnamese": "vi", 103 | "tiếng việt": "vi", 104 | "tiếng việt (vi)": "vi", 105 | "zh": "zh-hans", 106 | "chinese": "zh-hans", 107 | "中文": "zh-hans", 108 | "汉语": "zh-hans", 109 | "漢語": "zh-hant", 110 | "zh-hans": "zh-hans", 111 | "zh-cn": "zh-hans", 112 | "chinese (simplified)": "zh-hans", 113 | "simplified chinese": "zh-hans", 114 | "简体中文": "zh-hans", 115 | "简体中文 (zh-hans)": "zh-hans", 116 | "zh-hant": "zh-hant", 117 | "zh-tw": "zh-hant", 118 | "chinese (traditional)": "zh-hant", 119 | "traditional chinese": "zh-hant", 120 | "chinese (taiwan)": "zh-hant", 121 | "繁體中文": "zh-hant", 122 | "繁體中文 (zh-hant)": "zh-hant" 123 | } 124 | }, 125 | "RcGcDw": { 126 | "names": { 127 | "en": "English (en)", 128 | "de": "Deutsch (de)", 129 | "es": "Español (es)", 130 | "hi": "हिन्दी (hi)", 131 | "it": "Italiano (it)", 132 | "pl": "Polski (pl)", 133 | "pt-br": "Português do Brasil (pt-br)", 134 | "ru": "Русский (ru)", 135 | "zh-hans": "简体中文 (zh-hans)", 136 | "zh-hant": "繁體中文 (zh-hant)" 137 | }, 138 | "map": { 139 | "en": "en", 140 | "eng": "en", 141 | "english": "en", 142 | "english (en)": "en", 143 | "de": "de", 144 | "german": "de", 145 | "deutsch": "de", 146 | "deutsch (de)": "de", 147 | "es": "es", 148 | "es-es": "es", 149 | "spanish": "es", 150 | "español": "es", 151 | "español (es)": "es", 152 | "hi": "hi", 153 | "hindi": "hi", 154 | "हिन्दी": "hi", 155 | "हिन्दी (hi)": "hi", 156 | "it": "it", 157 | "italian":"it", 158 | "italiano": "it", 159 | "italiano (it)": "it", 160 | "pl": "pl", 161 | "polish": "pl", 162 | "polski": "pl", 163 | "polski (pl)": "pl", 164 | "pt": "pt-br", 165 | "portuguese": "pt-br", 166 | "português": "pt-br", 167 | "português (pt)": "pt-br", 168 | "pt-br": "pt-br", 169 | "brazilian portuguese": "pt-br", 170 | "português do brasil": "pt-br", 171 | "português do brasil (pt-br)": "pt-br", 172 | "ru": "ru", 173 | "russian": "ru", 174 | "русский": "ru", 175 | "русский (ru)": "ru", 176 | "zh": "zh-hans", 177 | "chinese": "zh-hans", 178 | "中文": "zh-hans", 179 | "汉语": "zh-hans", 180 | "zh-hans": "zh-hans", 181 | "zh-cn": "zh-hans", 182 | "chinese (simplified)": "zh-hans", 183 | "simplified chinese": "zh-hans", 184 | "简体中文": "zh-hans", 185 | "简体中文 (zh-hans)": "zh-hans", 186 | "zh-hant": "zh-hant", 187 | "zh-tw": "zh-hant", 188 | "chinese (traditional)": "zh-hant", 189 | "traditional chinese": "zh-hant", 190 | "chinese (taiwan)": "zh-hant", 191 | "繁體中文": "zh-hant", 192 | "繁體中文 (zh-hant)": "zh-hant" 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /i18n/et.json: -------------------------------------------------------------------------------- 1 | { 2 | "__translator": [ 3 | "Raudrobot", 4 | " ", 5 | " ", 6 | " ", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " " 13 | ], 14 | "aliases": { 15 | "bug": [ 16 | "", 17 | " ", 18 | " ", 19 | " ", 20 | " " 21 | ], 22 | "command": [ 23 | "", 24 | " ", 25 | " ", 26 | " ", 27 | " " 28 | ], 29 | "diff": [ 30 | "", 31 | " ", 32 | " ", 33 | " ", 34 | " " 35 | ], 36 | "discussion": [ 37 | "", 38 | " ", 39 | " ", 40 | " ", 41 | " " 42 | ], 43 | "help": [ 44 | "", 45 | " ", 46 | " ", 47 | " ", 48 | " " 49 | ], 50 | "info": [ 51 | "", 52 | " ", 53 | " ", 54 | " ", 55 | " " 56 | ], 57 | "invite": [ 58 | "", 59 | " ", 60 | " ", 61 | " ", 62 | " " 63 | ], 64 | "overview": [ 65 | "", 66 | " ", 67 | " ", 68 | " ", 69 | " " 70 | ], 71 | "page": [ 72 | "", 73 | " ", 74 | " ", 75 | " ", 76 | " " 77 | ], 78 | "random": [ 79 | "", 80 | " ", 81 | " ", 82 | " ", 83 | " " 84 | ], 85 | "search": [ 86 | "", 87 | " ", 88 | " ", 89 | " ", 90 | " " 91 | ], 92 | "test": [ 93 | "", 94 | " ", 95 | " ", 96 | " ", 97 | " " 98 | ], 99 | "user": [ 100 | "", 101 | " ", 102 | " ", 103 | " ", 104 | " " 105 | ], 106 | "verify": [ 107 | "", 108 | " ", 109 | " ", 110 | " ", 111 | " " 112 | ] 113 | }, 114 | "dateformat": "et-EE", 115 | "diff": { 116 | "badrev": "Vähemalt ühte revisjoni pole olemas!", 117 | "hidden": "*peidetud*", 118 | "info": { 119 | "more": "Ja veel", 120 | "removed": "Eemaldatud:", 121 | "size": "Erinevus:" 122 | } 123 | }, 124 | "fallback": [ 125 | "en", 126 | " ", 127 | " ", 128 | " ", 129 | " " 130 | ], 131 | "test": { 132 | "text": [ 133 | "", 134 | " ", 135 | " ", 136 | " ", 137 | " ", 138 | " ", 139 | " ", 140 | " ", 141 | " ", 142 | " ", 143 | " ", 144 | " ", 145 | " ", 146 | " ", 147 | " ", 148 | " ", 149 | " ", 150 | " ", 151 | " ", 152 | " ", 153 | " ", 154 | " ", 155 | " ", 156 | " ", 157 | " ", 158 | " ", 159 | " ", 160 | " ", 161 | " ", 162 | " " 163 | ] 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /i18n/lzh.json: -------------------------------------------------------------------------------- 1 | { 2 | "__translator": [ 3 | "", 4 | " ", 5 | " ", 6 | " ", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " " 13 | ], 14 | "aliases": { 15 | "bug": [ 16 | "", 17 | " ", 18 | " ", 19 | " ", 20 | " " 21 | ], 22 | "command": [ 23 | "令", 24 | " ", 25 | " ", 26 | " ", 27 | " " 28 | ], 29 | "diff": [ 30 | "", 31 | " ", 32 | " ", 33 | " ", 34 | " " 35 | ], 36 | "discussion": [ 37 | "", 38 | " ", 39 | " ", 40 | " ", 41 | " " 42 | ], 43 | "help": [ 44 | "", 45 | " ", 46 | " ", 47 | " ", 48 | " " 49 | ], 50 | "info": [ 51 | "", 52 | " ", 53 | " ", 54 | " ", 55 | " " 56 | ], 57 | "invite": [ 58 | "邀", 59 | " ", 60 | " ", 61 | " ", 62 | " " 63 | ], 64 | "overview": [ 65 | "", 66 | " ", 67 | " ", 68 | " ", 69 | " " 70 | ], 71 | "page": [ 72 | "頁", 73 | " ", 74 | " ", 75 | " ", 76 | " " 77 | ], 78 | "random": [ 79 | "", 80 | " ", 81 | " ", 82 | " ", 83 | " " 84 | ], 85 | "search": [ 86 | "", 87 | " ", 88 | " ", 89 | " ", 90 | " " 91 | ], 92 | "test": [ 93 | "", 94 | " ", 95 | " ", 96 | " ", 97 | " " 98 | ], 99 | "user": [ 100 | "", 101 | " ", 102 | " ", 103 | " ", 104 | " " 105 | ], 106 | "verify": [ 107 | "", 108 | " ", 109 | " ", 110 | " ", 111 | " " 112 | ] 113 | }, 114 | "diff": { 115 | "info": { 116 | "added": "增:", 117 | "removed": "刪:" 118 | } 119 | }, 120 | "fallback": [ 121 | "zh-hant", 122 | "zh-hans", 123 | "en", 124 | " ", 125 | " " 126 | ], 127 | "help": { 128 | "list": { 129 | "help": { 130 | "admin": { 131 | "cmd": "help admin" 132 | } 133 | }, 134 | "inline": { 135 | "link": { 136 | "cmd": "[[<頁名>]]" 137 | }, 138 | "template": { 139 | "cmd": "{{<頁名>}}" 140 | } 141 | } 142 | } 143 | }, 144 | "test": { 145 | "text": [ 146 | "", 147 | " ", 148 | " ", 149 | " ", 150 | " ", 151 | " ", 152 | " ", 153 | " ", 154 | " ", 155 | " ", 156 | " ", 157 | " ", 158 | " ", 159 | " ", 160 | " ", 161 | " ", 162 | " ", 163 | " ", 164 | " ", 165 | " ", 166 | " ", 167 | " ", 168 | " ", 169 | " ", 170 | " ", 171 | " ", 172 | " ", 173 | " ", 174 | " ", 175 | " " 176 | ] 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /i18n/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "__translator": [ 3 | "Fensokratica", 4 | " ", 5 | " ", 6 | " ", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " " 13 | ], 14 | "aliases": { 15 | "bug": [ 16 | "", 17 | " ", 18 | " ", 19 | " ", 20 | " " 21 | ], 22 | "command": [ 23 | "", 24 | " ", 25 | " ", 26 | " ", 27 | " " 28 | ], 29 | "diff": [ 30 | "", 31 | " ", 32 | " ", 33 | " ", 34 | " " 35 | ], 36 | "discussion": [ 37 | "", 38 | " ", 39 | " ", 40 | " ", 41 | " " 42 | ], 43 | "help": [ 44 | "", 45 | " ", 46 | " ", 47 | " ", 48 | " " 49 | ], 50 | "info": [ 51 | "", 52 | " ", 53 | " ", 54 | " ", 55 | " " 56 | ], 57 | "invite": [ 58 | "", 59 | " ", 60 | " ", 61 | " ", 62 | " " 63 | ], 64 | "overview": [ 65 | "", 66 | " ", 67 | " ", 68 | " ", 69 | " " 70 | ], 71 | "page": [ 72 | "", 73 | " ", 74 | " ", 75 | " ", 76 | " " 77 | ], 78 | "random": [ 79 | "", 80 | " ", 81 | " ", 82 | " ", 83 | " " 84 | ], 85 | "search": [ 86 | "", 87 | " ", 88 | " ", 89 | " ", 90 | " " 91 | ], 92 | "test": [ 93 | "", 94 | " ", 95 | " ", 96 | " ", 97 | " " 98 | ], 99 | "user": [ 100 | "", 101 | " ", 102 | " ", 103 | " ", 104 | " " 105 | ], 106 | "verify": [ 107 | "", 108 | " ", 109 | " ", 110 | " ", 111 | " " 112 | ] 113 | }, 114 | "diff": { 115 | "info": { 116 | "added": "Dodato:", 117 | "comment": "Komentar:", 118 | "editor": "Urednik:", 119 | "more": "I više", 120 | "removed": "Uklonjeno:", 121 | "size": "Razlika:", 122 | "tags": "Oznake (Tagovi):", 123 | "timestamp": "Uredi datum:", 124 | "whitespace": "Samo razmak" 125 | }, 126 | "nocomment": "*Nema opisa*" 127 | }, 128 | "discussion": { 129 | "image": "Prikaži Sliku", 130 | "main": "Diskusije", 131 | "post": "objava", 132 | "tags": "Oznake (Tagovi):" 133 | }, 134 | "fallback": [ 135 | "en", 136 | " ", 137 | " ", 138 | " ", 139 | " " 140 | ], 141 | "general": { 142 | "database": "⚠️ **Ograničena Funkcionalnost** ⚠️\nNisu pronađena podešavanja, kontaktirajte vlasnika bota!", 143 | "default": "Ovaj server još nije podešen. Koristite $1 ili kontrolnu tablu da promenite podešavanja." 144 | }, 145 | "test": { 146 | "text": [ 147 | "", 148 | " ", 149 | " ", 150 | " ", 151 | " ", 152 | " ", 153 | " ", 154 | " ", 155 | " ", 156 | " ", 157 | " ", 158 | " ", 159 | " ", 160 | " ", 161 | " ", 162 | " ", 163 | " ", 164 | " ", 165 | " ", 166 | " ", 167 | " ", 168 | " ", 169 | " ", 170 | " ", 171 | " ", 172 | " ", 173 | " ", 174 | " ", 175 | " ", 176 | " " 177 | ] 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /i18n/th.json: -------------------------------------------------------------------------------- 1 | { 2 | "__translator": [ 3 | "Patsagorn Y.", 4 | " ", 5 | " ", 6 | " ", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " " 13 | ], 14 | "aliases": { 15 | "bug": [ 16 | "บัก", 17 | "บัค", 18 | " ", 19 | " ", 20 | " " 21 | ], 22 | "command": [ 23 | "คอมมานด์", 24 | "คำสั่ง", 25 | "คอมมาน", 26 | " ", 27 | " " 28 | ], 29 | "diff": [ 30 | "ต่าง", 31 | "ความต่าง", 32 | "ความแตกต่าง", 33 | "แตกต่าง", 34 | " " 35 | ], 36 | "discussion": [ 37 | "อภิปราย", 38 | "พูดคุย", 39 | "คุย", 40 | "ถกเถียง", 41 | " " 42 | ], 43 | "help": [ 44 | "วิธีใช้", 45 | "ช่วยเหลือ", 46 | "ความช่วยเหลือ", 47 | "การช่วยเหลือ", 48 | " " 49 | ], 50 | "info": [ 51 | "ข้อมูล", 52 | "สารนิเทศ", 53 | " ", 54 | " ", 55 | " " 56 | ], 57 | "invite": [ 58 | "เชิญ", 59 | " ", 60 | " ", 61 | " ", 62 | " " 63 | ], 64 | "overview": [ 65 | "ภาพรวม", 66 | " ", 67 | " ", 68 | " ", 69 | " " 70 | ], 71 | "page": [ 72 | "หน้า", 73 | " ", 74 | " ", 75 | " ", 76 | " " 77 | ], 78 | "random": [ 79 | "สุ่ม", 80 | " ", 81 | " ", 82 | " ", 83 | " " 84 | ], 85 | "search": [ 86 | "ค้นหา", 87 | " ", 88 | " ", 89 | " ", 90 | " " 91 | ], 92 | "test": [ 93 | "ทดสอบ", 94 | " ", 95 | " ", 96 | " ", 97 | " " 98 | ], 99 | "user": [ 100 | "ผู้ใช้", 101 | " ", 102 | " ", 103 | " ", 104 | " " 105 | ], 106 | "verify": [ 107 | "ยืนยัน", 108 | " ", 109 | " ", 110 | " ", 111 | " " 112 | ] 113 | }, 114 | "dateformat": "th", 115 | "diff": { 116 | "badrev": "รุ่นการแก้ไขอย่างน้อยหนึ่งรุ่นไม่มีอยู่จริง!", 117 | "hidden": "*ซ่อนอยู่*", 118 | "info": { 119 | "added": "เพิ่ม:", 120 | "bytes": "$1 ไบต์", 121 | "comment": "คำอธิบายอย่างย่อ:", 122 | "editor": "ผู้แก้ไข:", 123 | "minor": "_(ล)", 124 | "more": "และอื่น ๆ", 125 | "removed": "ลบ:", 126 | "size": "แตกต่าง:", 127 | "tags": "ป้ายระบุ:", 128 | "timestamp": "วันที่แก้ไข:", 129 | "whitespace": "เฉพาะ whitespace" 130 | }, 131 | "nocomment": "*ไม่มีคำอธิบายอย่างย่อ*" 132 | }, 133 | "discussion": { 134 | "image": "ดูภาพ", 135 | "main": "อภิปราย", 136 | "post": "โพสต์", 137 | "tags": "ป้ายระบุ:", 138 | "votes": "$1 โหวต ($3%)" 139 | }, 140 | "fallback": [ 141 | "en", 142 | " ", 143 | " ", 144 | " ", 145 | " " 146 | ], 147 | "general": { 148 | "database": "⚠️ **การทำงานถูกจำกัด** ⚠️\nไม่พบการตั้งค่าใด ๆ กรุณาติดต่อเจ้าของบอต!", 149 | "default": "เซิร์ฟเวอร์นี้ยังไม่ถูกปรัยตั้งค่าใด ๆ เลย ใช้ $1 หรือที่แดชบอร์ดเพื่อเปลี่ยนแปลงการตั้งค่า", 150 | "disclaimer": "ผมคือบอตเล็ก ๆ ที่ช่วยให้ค้นหาและลิงก์ไปเว็บที่ใช้มีเดียวิกิได้ง่ายชึ้น เช่นบนวิกิพีเดียหรือ Fandom เมื่อเรียก ผมจะดึงข้อมูลส่อ ๆ ของบทความและข้อมูลอื่น ๆ และผมทำงานได้กับการเปลี่ยนทางหรือลิงก์ข้ามโครงการ $1 เขียนผมขึ้นมาด้วยภาษาจาวาสคริปต์\n\nสนับสนุนผมต่อไปได้ที่ Patreon:", 151 | "experimental": "**ฟีเจอร์นี้อยู่ระหว่างทดลอง! ไม่สามารถยืนยันได้ว่าจะทำงานอย่างถูกต้องและอาจถูกนำออกในอนาคต**", 152 | "helpserver": "มีข้อสงสัยหรือปัญหา ลองมาที่เซิร์ฟเวอร์สำหรับความช่วยเหลือของผม:", 153 | "limit": "🚨 **หยุดเดี๋ยวนี้ คุณแตะขีดจำกัด!** 🚨\n\n$1 ข้อความเมื่อสักครู่มีคำสั่งมากเกินไปนะ!", 154 | "missingperm": "ผมขาดสิทธิ์บางอย่างเพื่อทำคำสั่งนี้:", 155 | "patreon": "นี่คือสิทธิพิเศษสำหรับชาว Patreon ของเรา สนับสนุนเราบน Patreon เพื่อเข้าถึงฟีเจอร์นี้:", 156 | "prefix": "Prefix ของเซิร์ฟเวอร์นี้คือ `$1` คุณสามารถเปลี่ยนแปลงได้โดยใช้ `$1settings prefix` ถ้าจะดูรายการทั้งหมด ใช้ `$1help` นะครับ", 157 | "readonly": "**ขณะนี้ฐานข้อมูลกำลังอยู่ในโหมดอ่านอย่างเดียว คุณจะยังไม่สามารถแก้ไขการตั้งค่าได้ในขณะนี้**" 158 | }, 159 | "help": { 160 | "admin": "คำสั่งเหล่านี้จะสามารถใช้ได้โดยผู้ดูแลเท่านั้น:", 161 | "adminfooter": "ผู้ดูแลยังสามารถใช้แดชบอร์ดเพื่อเปลี่ยนแปลงการตั้งค่าได้:", 162 | "all": "คุณอยากรู้ใช่ไหมว่าผมทำอะไรได้บ้าง? นี่คือคำสั่งที่ผมเข้าใจ:", 163 | "footer": "หากคุณได้รับคำตอบกลับที่ไม่น่าพอใจ กด react 🗑️ (`:wastebasket:`) ที่ข้อความแล้วผมจะลบออกให้", 164 | "list": { 165 | "default": { 166 | "cmd": "<ข้อความค้นหา>", 167 | "desc": "ตอบกลับด้วยลิงก์ไปยังบทความที่ตรงในวิกิ" 168 | }, 169 | "diff": { 170 | "id": { 171 | "cmd": "diff []", 172 | "desc": "ผมจะตอบกลับด้วยลิงก์ความแตกต่างในวิกิ" 173 | }, 174 | "name": { 175 | "cmd": "diff ", 176 | "desc": "ตอบกลับด้วยลิงก์ความแตกต่างล่าสุดของหน้าในวิกิ" 177 | } 178 | }, 179 | "discussion": { 180 | "post": { 181 | "cmd": "discussion post " 182 | } 183 | } 184 | } 185 | }, 186 | "test": { 187 | "text": [ 188 | "", 189 | " ", 190 | " ", 191 | " ", 192 | " ", 193 | " ", 194 | " ", 195 | " ", 196 | " ", 197 | " ", 198 | " ", 199 | " ", 200 | " ", 201 | " ", 202 | " ", 203 | " ", 204 | " ", 205 | " ", 206 | " ", 207 | " ", 208 | " ", 209 | " ", 210 | " ", 211 | " ", 212 | " ", 213 | " ", 214 | " ", 215 | " ", 216 | " ", 217 | " " 218 | ] 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /i18n/widgets/bn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/bn.png -------------------------------------------------------------------------------- /i18n/widgets/de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/de.png -------------------------------------------------------------------------------- /i18n/widgets/el.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/el.png -------------------------------------------------------------------------------- /i18n/widgets/en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/en.png -------------------------------------------------------------------------------- /i18n/widgets/es.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/es.png -------------------------------------------------------------------------------- /i18n/widgets/fr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/fr.png -------------------------------------------------------------------------------- /i18n/widgets/hi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/hi.png -------------------------------------------------------------------------------- /i18n/widgets/id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/id.png -------------------------------------------------------------------------------- /i18n/widgets/it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/it.png -------------------------------------------------------------------------------- /i18n/widgets/ja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/ja.png -------------------------------------------------------------------------------- /i18n/widgets/ko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/ko.png -------------------------------------------------------------------------------- /i18n/widgets/lzh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/lzh.png -------------------------------------------------------------------------------- /i18n/widgets/nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/nl.png -------------------------------------------------------------------------------- /i18n/widgets/pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/pl.png -------------------------------------------------------------------------------- /i18n/widgets/pt-br.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/pt-br.png -------------------------------------------------------------------------------- /i18n/widgets/ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/ru.png -------------------------------------------------------------------------------- /i18n/widgets/sr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/sr.png -------------------------------------------------------------------------------- /i18n/widgets/sv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/sv.png -------------------------------------------------------------------------------- /i18n/widgets/th.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/th.png -------------------------------------------------------------------------------- /i18n/widgets/tr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/tr.png -------------------------------------------------------------------------------- /i18n/widgets/uk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/uk.png -------------------------------------------------------------------------------- /i18n/widgets/vi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/vi.png -------------------------------------------------------------------------------- /i18n/widgets/zh-hans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/zh-hans.png -------------------------------------------------------------------------------- /i18n/widgets/zh-hant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Markus-Rost/discord-wiki-bot/4265990e06c8bc4cb92b519c19f19bfadd75e06b/i18n/widgets/zh-hant.png -------------------------------------------------------------------------------- /interactions/admin.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default { 4 | name: 'admin', 5 | allowDelete: false 6 | }; -------------------------------------------------------------------------------- /interactions/commands/admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "application_id": "", 4 | "type": 1, 5 | "integration_types": [0], 6 | "contexts": [0, 1], 7 | "default_member_permissions": "0", 8 | "name": "admin", 9 | "name_localizations": {}, 10 | "description": "Post a message with inline wiki links.", 11 | "description_localizations": {}, 12 | "options": [ 13 | { 14 | "type": 2, 15 | "name": "patreon", 16 | "name_localizations": {}, 17 | "description": "Text including wikitext links.", 18 | "description_localizations": {}, 19 | "options": [ 20 | { 21 | "type": 1, 22 | "name": "check", 23 | "name_localizations": {}, 24 | "description": "Check the current list of servers with patreon features enabled by a user.", 25 | "description_localizations": {}, 26 | "options": [ 27 | { 28 | "type": 6, 29 | "name": "user", 30 | "name_localizations": {}, 31 | "description": "The user to check the current list of servers with patreon features enabled for.", 32 | "description_localizations": {}, 33 | "required": true, 34 | "autocomplete": true 35 | } 36 | ] 37 | }, 38 | { 39 | "type": 1, 40 | "name": "disable", 41 | "name_localizations": {}, 42 | "description": "Disable the patreon features for a server.", 43 | "description_localizations": {}, 44 | "options": [ 45 | { 46 | "type": 3, 47 | "name": "guild", 48 | "name_localizations": {}, 49 | "description": "The server to disable patreon features for.", 50 | "description_localizations": {}, 51 | "min_length": 17, 52 | "max_length": 20, 53 | "required": true, 54 | "autocomplete": true 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /interactions/commands/inline.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "application_id": "", 4 | "type": 1, 5 | "integration_types": [ 6 | 0, 7 | 1 8 | ], 9 | "contexts": [ 10 | 0, 11 | 1, 12 | 2 13 | ], 14 | "default_member_permissions": "1024", 15 | "name": "inline", 16 | "name_localizations": { 17 | "en-GB": "inline", 18 | "en-US": "inline", 19 | "de": "inline", 20 | "it": "inline", 21 | "sv-SE": "inline", 22 | "tr": "inline", 23 | "hi": "inline", 24 | "pl": "inline", 25 | "ru": "inline", 26 | "zh-CN": "inline", 27 | "zh-TW": "inline", 28 | "es-ES": "inline", 29 | "pt-BR": "inline", 30 | "ja": "inline", 31 | "el": "inline", 32 | "fr": "inline", 33 | "vi": "noituyen" 34 | }, 35 | "description": "Post a message with inline wiki links.", 36 | "description_localizations": { 37 | "en-GB": "Post a message with inline wiki links.", 38 | "en-US": "Post a message with inline wiki links.", 39 | "de": "Sende eine Nachricht mit Inline-Links zum Wiki.", 40 | "es-ES": "Publicar un mensaje con enlaces wiki inline.", 41 | "it": "Posta un messaggio con link wiki inline.", 42 | "sv-SE": "Skicka ett meddelande med wikilänkar på samma rad.", 43 | "tr": "Satır içi viki bağlantıları ile mesaj paylaş.", 44 | "hi": "इनलाइन विकि कड़ियों के साथ एक संदेश पोस्ट करें।", 45 | "pl": "Utwórz wiadomość z linkami pisanymi składnią wiki.", 46 | "ru": "Разместите сообщение со встроенными вики-ссылками.", 47 | "zh-CN": "发送一条带有行内wiki链接的消息。", 48 | "zh-TW": "傳送含有行內wiki連結的訊息。", 49 | "pt-BR": "Poste uma mensagem com links da wiki embutidos na linha.", 50 | "ja": "埋め込み形式のWikiリンクを含むメッセージを投稿する。", 51 | "el": "Δημοσίευση ενός μηνύματος με συνδέσμους wiki εντός κειμένου.", 52 | "fr": "Publiez un message avec des liens wiki intégrés.", 53 | "vi": "Gửi tin nhắn có liên kết wiki nội tuyến." 54 | }, 55 | "options": [ 56 | { 57 | "type": 3, 58 | "name": "text", 59 | "name_localizations": { 60 | "en-GB": "text", 61 | "en-US": "text", 62 | "de": "text", 63 | "it": "testo", 64 | "sv-SE": "text", 65 | "tr": "text", 66 | "hi": "text", 67 | "pl": "tekst", 68 | "ru": "text", 69 | "zh-CN": "文本", 70 | "zh-TW": "文字", 71 | "es-ES": "texto", 72 | "pt-BR": "texto", 73 | "ja": "text", 74 | "el": "κείμενο", 75 | "fr": "text", 76 | "vi": "noidung" 77 | }, 78 | "description": "Text including wikitext links.", 79 | "description_localizations": { 80 | "en-GB": "Text including wikitext links.", 81 | "en-US": "Text including wikitext links.", 82 | "de": "Text mit Wikitext-Links.", 83 | "it": "Testo che include link in wikitext.", 84 | "sv-SE": "Text med länkar till wikitext.", 85 | "tr": "Wikitext bağlantılarını içeren metin.", 86 | "hi": "विकिटेक्स्ट कड़ियों वाला टेक्स्ट।", 87 | "pl": "Tekst zawierający linki pisane składnią wiki.", 88 | "ru": "Текст, включающий ссылки на вики-текст.", 89 | "zh-CN": "包含wikitext链接的文本。", 90 | "zh-TW": "含有wikitext連結的文字。", 91 | "es-ES": "Texto que incluye enlaces de wikitexto.", 92 | "pt-BR": "Texto incluindo links no estilo wikitexto.", 93 | "ja": "wikitextのリンクを含むテキスト。", 94 | "el": "Κείμενο που περιέχει συνδέσμους wikitext.", 95 | "fr": "Texte incluant des liens wikitexte.", 96 | "vi": "Nội dung tin nhắn bao gồm liên kết wikitext." 97 | }, 98 | "required": true 99 | }, 100 | { 101 | "type": 3, 102 | "name": "wiki", 103 | "name_localizations": { 104 | "de": "wiki", 105 | "es-ES": "wiki", 106 | "zh-CN": "wiki", 107 | "zh-TW": "wiki", 108 | "hi": "wiki", 109 | "it": "wiki", 110 | "tr": "wiki", 111 | "el": "wiki", 112 | "pt-BR": "wiki", 113 | "en-GB": "wiki", 114 | "en-US": "wiki", 115 | "ja": "wiki", 116 | "fr": "wiki", 117 | "sv-SE": "wiki", 118 | "vi": "wiki" 119 | }, 120 | "description": "The wiki to search in.", 121 | "description_localizations": { 122 | "de": "Das Wiki zum Durchsuchen.", 123 | "es-ES": "El wiki en el que buscar.", 124 | "zh-CN": "要搜索的wiki。", 125 | "zh-TW": "要搜尋的wiki。", 126 | "hi": "विकि जिसमें खोजना है।", 127 | "it": "La wiki in cui cercare.", 128 | "tr": "Aratma yapılacak viki.", 129 | "el": "To wiki προς αναζήτηση.", 130 | "pt-BR": "A wiki para pesquisar.", 131 | "en-GB": "The wiki to search in.", 132 | "en-US": "The wiki to search in.", 133 | "ja": "検索対象となるWiki。", 134 | "fr": "Le wiki dans lequel effectuer la recherche.", 135 | "sv-SE": "Wikin att söka i.", 136 | "vi": "Wiki bạn cần tìm kiếm." 137 | }, 138 | "required": false, 139 | "autocomplete": true 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /interactions/commands/overview.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "application_id": "", 4 | "type": 1, 5 | "integration_types": [ 6 | 0, 7 | 1 8 | ], 9 | "contexts": [ 10 | 0, 11 | 1, 12 | 2 13 | ], 14 | "default_member_permissions": "1024", 15 | "name": "overview", 16 | "name_localizations": { 17 | "de": "übersicht", 18 | "es-ES": "resumen", 19 | "hi": "overview", 20 | "zh-CN": "overview", 21 | "zh-TW": "overview", 22 | "it": "panoramica", 23 | "tr": "overview", 24 | "ja": "overview", 25 | "el": "ανασκόπηση", 26 | "pt-BR": "visão_geral", 27 | "en-GB": "overview", 28 | "en-US": "overview", 29 | "fr": "overview", 30 | "uk": "огляд", 31 | "vi": "thongtinwiki" 32 | }, 33 | "description": "Post a wiki overview.", 34 | "description_localizations": { 35 | "de": "Sende eine Wiki-Übersicht.", 36 | "es-ES": "Publica un resumen del wiki.", 37 | "hi": "विकि का अवलोकन पोस्ट करें।", 38 | "zh-CN": "发送wiki概览。", 39 | "zh-TW": "傳送wiki概覽。", 40 | "it": "Posta la panoramica di una wiki.", 41 | "tr": "Viki ön izlemesi paylaş.", 42 | "ja": "Post a wiki overview.", 43 | "el": "Δημοσίευση ανασκόπησης wiki.", 44 | "pt-BR": "Postar uma visualização geral da wiki.", 45 | "en-GB": "Post a wiki overview.", 46 | "en-US": "Post a wiki overview.", 47 | "fr": "Afficher un aperçu du wiki.", 48 | "vi": "Hiển thị thông tin cơ bản của wiki." 49 | }, 50 | "options": [ 51 | { 52 | "type": 3, 53 | "name": "wiki", 54 | "name_localizations": { 55 | "de": "wiki", 56 | "es-ES": "wiki", 57 | "zh-CN": "wiki", 58 | "zh-TW": "wiki", 59 | "hi": "wiki", 60 | "it": "wiki", 61 | "tr": "wiki", 62 | "ja": "wiki", 63 | "el": "wiki", 64 | "pt-BR": "wiki", 65 | "en-GB": "wiki", 66 | "en-US": "wiki", 67 | "fr": "wiki", 68 | "uk": "вікі", 69 | "vi": "wiki" 70 | }, 71 | "description": "The wiki to search in.", 72 | "description_localizations": { 73 | "de": "Das Wiki zum Durchsuchen.", 74 | "es-ES": "El wiki en el que buscar.", 75 | "zh-CN": "要搜索的wiki。", 76 | "zh-TW": "要搜尋的wiki。", 77 | "hi": "विकि जिसमें खोजना है।", 78 | "it": "La wiki in cui cercare.", 79 | "tr": "Aratma yapılacak viki.", 80 | "ja": "検索対象となるWiki。", 81 | "el": "Το wiki προς αναζήτηση.", 82 | "pt-BR": "A wiki a ser pesquisada.", 83 | "en-GB": "The wiki to search in.", 84 | "en-US": "The wiki to search in.", 85 | "fr": "Le wiki dans lequel effectuer la recherche.", 86 | "vi": "Wiki bạn cần tìm kiếm." 87 | }, 88 | "required": false, 89 | "autocomplete": true 90 | }, 91 | { 92 | "type": 5, 93 | "name": "private", 94 | "name_localizations": { 95 | "de": "privat", 96 | "es-ES": "privado", 97 | "hi": "private", 98 | "ja": "private", 99 | "zh-CN": "仅自己可见", 100 | "zh-TW": "僅自己可見", 101 | "it": "privato", 102 | "tr": "private", 103 | "el": "ιδιωτικό", 104 | "pt-BR": "privado", 105 | "en-GB": "private", 106 | "en-US": "private", 107 | "fr": "private", 108 | "uk": "приватне", 109 | "vi": "riengtu" 110 | }, 111 | "description": "Send a response only visible to yourself?", 112 | "description_localizations": { 113 | "de": "Sende eine Antwort nur sichtbar für dich?", 114 | "es-ES": "¿Enviar una respuesta que solo sea visible para ti?", 115 | "hi": "सिर्फ आपको दिखने वाला एक जवाब भेजना है?", 116 | "ja": "自分だけに見える応答を送信しますか?", 117 | "zh-CN": "发送的消息是否仅对自己可见?", 118 | "zh-TW": "傳送的訊息是否僅對自己可見?", 119 | "it": "Invia una risposta visibile solo per te?", 120 | "tr": "Yalnızca sizin görebileceğiniz bir yanıt gönderilsin mi?", 121 | "el": "Αποστολή απάντησης ορατής μόνο σε εσάς;", 122 | "pt-BR": "Deseja que a resposta seja privada?", 123 | "en-GB": "Send a response only visible to yourself?", 124 | "en-US": "Send a response only visible to yourself?", 125 | "fr": "Envoyer une réponse uniquement visible par vous-même ?", 126 | "vi": "Gửi phản hồi chỉ xem được bởi bạn?" 127 | }, 128 | "required": false 129 | }, 130 | { 131 | "type": 5, 132 | "name": "noembed", 133 | "name_localizations": { 134 | "de": "noembed", 135 | "es-ES": "noembed", 136 | "hi": "noembed", 137 | "ja": "noembed", 138 | "zh-CN": "不包含嵌入内容", 139 | "zh-TW": "不包含嵌入內容", 140 | "it": "noembed", 141 | "tr": "noembed", 142 | "el": "noembed", 143 | "pt-BR": "sem_embed", 144 | "en-GB": "noembed", 145 | "en-US": "noembed", 146 | "fr": "noembed", 147 | "vi": "khongnhung" 148 | }, 149 | "description": "Send only the link without embed?", 150 | "description_localizations": { 151 | "de": "Sende nur den Link ohne Einbettung?", 152 | "es-ES": "¿Enviar solo el enlace sin embed?", 153 | "hi": "बिना एम्बेड के सिर्फ कड़ी को भेजना है?", 154 | "ja": "埋め込みなしでリンクだけを送る?", 155 | "zh-CN": "发送的链接是否不包含嵌入内容?", 156 | "zh-TW": "傳送的連結是否不包含嵌入內容?", 157 | "it": "Invia solo il link senza embed?", 158 | "tr": "Gömülü bir mesaj olmadan sadece bağlantı mı paylaşılmalı?", 159 | "el": "Αποστολή μόνο του συνδέσμου χωρίς ενσωμάτωση;", 160 | "pt-BR": "Enviar o link sem a pré-visualização?", 161 | "en-GB": "Send only the link without embed?", 162 | "en-US": "Send only the link without embed?", 163 | "fr": "Envoyer uniquement le lien sans inclusion ?", 164 | "vi": "Chỉ gửi liên kết mà không nhúng?" 165 | }, 166 | "required": false 167 | }, 168 | { 169 | "type": 5, 170 | "name": "spoiler", 171 | "name_localizations": { 172 | "de": "spoiler", 173 | "es-ES": "spoiler", 174 | "hi": "spoiler", 175 | "ja": "spoiler", 176 | "zh-CN": "标记为剧透", 177 | "zh-TW": "標記為劇透", 178 | "it": "spoiler", 179 | "tr": "spoiler", 180 | "el": "σπόιλερ", 181 | "pt-BR": "spoiler", 182 | "en-GB": "spoiler", 183 | "en-US": "spoiler", 184 | "fr": "spoiler", 185 | "uk": "спойлер", 186 | "vi": "spoiler" 187 | }, 188 | "description": "Hide the link and embed in a spoiler?", 189 | "description_localizations": { 190 | "de": "Verstecke Link und Einbettung in einem Spoiler?", 191 | "es-ES": "¿Ocultar el enlace y el embed en un spoiler?", 192 | "hi": "कड़ी और एम्बेड को एक स्पॉइलर में छिपाना है?", 193 | "ja": "リンクを隠してスポイラーで埋め込む?", 194 | "zh-CN": "是否将链接和嵌入内容标记为剧透?", 195 | "zh-TW": "是否將連結和嵌入內容標記為劇透?", 196 | "it": "Nascondi il link e l'embed in uno spoiler?", 197 | "tr": "Bağlantı ve gömülü mesaj bir spoiler'da gizlenmeli mi?", 198 | "el": "Απόκρυψη συνδέσμου και ενσωμάτωση στο σπόιλερ;", 199 | "pt-BR": "Ocultar o link como spoiler?", 200 | "en-GB": "Hide the link and embed in a spoiler?", 201 | "en-US": "Hide the link and embed in a spoiler?", 202 | "fr": "Cacher le lien et l'inclure dans un spoiler ?", 203 | "vi": "Ẩn liên kết bằng cách dùng mã ẩn nội dung?" 204 | }, 205 | "required": false 206 | } 207 | ] 208 | } 209 | -------------------------------------------------------------------------------- /interactions/commands/random.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "application_id": "", 4 | "type": 1, 5 | "integration_types": [ 6 | 0, 7 | 1 8 | ], 9 | "contexts": [ 10 | 0, 11 | 1, 12 | 2 13 | ], 14 | "default_member_permissions": "1024", 15 | "name": "random", 16 | "name_localizations": { 17 | "de": "zufall", 18 | "zh-CN": "random", 19 | "zh-TW": "random", 20 | "hi": "random", 21 | "it": "casuale", 22 | "el": "τυχαίος", 23 | "es-ES": "aleatorio", 24 | "pt-BR": "aleatório", 25 | "en-GB": "random", 26 | "en-US": "random", 27 | "ja": "random", 28 | "fr": "random", 29 | "uk": "випадково", 30 | "vi": "ngaunhien" 31 | }, 32 | "description": "Post a random wiki link.", 33 | "description_localizations": { 34 | "de": "Sende einen zufälligen Link zum Wiki.", 35 | "zh-CN": "发送一条指向随机页面的wiki链接。", 36 | "zh-TW": "傳送一條指向隨機頁面的 wiki 連結。", 37 | "hi": "एक बेतरतीब विकि कड़ी पोस्ट करें।", 38 | "it": "Posta un link casuale alla wiki.", 39 | "el": "Δημοσίευση ενός τυχαίου συνδέσμου wiki.", 40 | "es-ES": "Publica un enlace wiki aleatorio.", 41 | "pt-BR": "Poste um link aleatório da wiki.", 42 | "en-GB": "Post a random wiki link.", 43 | "en-US": "Post a random wiki link.", 44 | "ja": "ランダムなWikiのリンクを貼ります。", 45 | "fr": "Postez un lien de wiki au hasard.", 46 | "vi": "Gửi liên kết tới trang ngẫu nhiên trong wiki." 47 | }, 48 | "options": [ 49 | { 50 | "type": 3, 51 | "name": "wiki", 52 | "name_localizations": { 53 | "de": "wiki", 54 | "es-ES": "wiki", 55 | "zh-CN": "wiki", 56 | "zh-TW": "wiki", 57 | "hi": "wiki", 58 | "it": "wiki", 59 | "tr": "wiki", 60 | "el": "wiki", 61 | "pt-BR": "wiki", 62 | "en-GB": "wiki", 63 | "en-US": "wiki", 64 | "ja": "wiki", 65 | "fr": "wiki", 66 | "uk": "вікі", 67 | "vi": "wiki" 68 | }, 69 | "description": "The wiki to search in.", 70 | "description_localizations": { 71 | "de": "Das Wiki zum Durchsuchen.", 72 | "es-ES": "El wiki en el que buscar.", 73 | "zh-CN": "要搜索的wiki。", 74 | "zh-TW": "要搜尋的wiki。", 75 | "hi": "विकि जिसमें खोजना है।", 76 | "it": "La wiki in cui cercare.", 77 | "tr": "Aratma yapılacak viki.", 78 | "el": "Το wiki προς αναζήτηση.", 79 | "pt-BR": "A wiki a ser pesquisada.", 80 | "en-GB": "The wiki to search in.", 81 | "en-US": "The wiki to search in.", 82 | "ja": "検索対象となるWiki。", 83 | "fr": "Le wiki dans lequel effectuer la recherche.", 84 | "vi": "Wiki bạn cần tìm kiếm." 85 | }, 86 | "required": false, 87 | "autocomplete": true 88 | }, 89 | { 90 | "type": 3, 91 | "name": "namespace", 92 | "name_localizations": { 93 | "de": "namensraum", 94 | "zh-CN": "命名空间", 95 | "zh-TW": "命名空間", 96 | "hi": "namespace", 97 | "it": "namespace", 98 | "el": "ονοματοχώρος", 99 | "es-ES": "namespace", 100 | "pt-BR": "espaço_nominal", 101 | "en-GB": "namespace", 102 | "en-US": "namespace", 103 | "ja": "namespace", 104 | "fr": "namespace", 105 | "vi": "kogianten" 106 | }, 107 | "description": "The namespaces to search in.", 108 | "description_localizations": { 109 | "de": "Die Namesräume zum Durchsuchen.", 110 | "zh-CN": "要搜索的命名空间。", 111 | "zh-TW": "要搜尋的命名空間。", 112 | "hi": "नामस्थान जिनमें खोजना है।", 113 | "it": "I namespace in cui cercare.", 114 | "el": "Οι ονοματοχώροι προς αναζήτηση.", 115 | "es-ES": "Los espacios de nombres en los que buscar.", 116 | "pt-BR": "Os espaços nominais a serem pesquisados.", 117 | "en-GB": "The namespaces to search in.", 118 | "en-US": "The namespaces to search in.", 119 | "ja": "検索対象となる名前空間。", 120 | "fr": "L'espace de noms dans lequel la recherche doit être effectuée.", 121 | "vi": "Không gian tên để tìm kiếm." 122 | }, 123 | "required": false, 124 | "autocomplete": true 125 | }, 126 | { 127 | "type": 5, 128 | "name": "private", 129 | "name_localizations": { 130 | "de": "privat", 131 | "es-ES": "privado", 132 | "hi": "private", 133 | "ja": "private", 134 | "zh-CN": "仅自己可见", 135 | "zh-TW": "僅自己可見", 136 | "it": "privato", 137 | "tr": "private", 138 | "el": "ιδιωτικό", 139 | "pt-BR": "privado", 140 | "en-GB": "private", 141 | "en-US": "private", 142 | "fr": "private", 143 | "uk": "приватне", 144 | "vi": "riengtu" 145 | }, 146 | "description": "Send a response only visible to yourself?", 147 | "description_localizations": { 148 | "de": "Sende eine Antwort nur sichtbar für dich?", 149 | "es-ES": "¿Enviar una respuesta que solo sea visible para ti?", 150 | "hi": "सिर्फ आपको दिखने वाला एक जवाब भेजना है?", 151 | "ja": "自分だけに見える応答で送信しますか?", 152 | "zh-CN": "发送的消息是否仅对自己可见?", 153 | "zh-TW": "傳送的訊息是否僅對自己可見?", 154 | "it": "Invia una risposta visibile solo per te?", 155 | "tr": "Yalnızca sizin görebileceğiniz bir yanıt gönderilsin mi?", 156 | "el": "Αποστολή απάντησης ορατής μόνο σε εσάς;", 157 | "pt-BR": "Deseja que a resposta seja privada?", 158 | "en-GB": "Send a response only visible to yourself?", 159 | "en-US": "Send a response only visible to yourself?", 160 | "fr": "Envoyer une réponse uniquement visible par vous-même ?", 161 | "vi": "Gửi phản hồi chỉ xem được bởi bạn?" 162 | }, 163 | "required": false 164 | }, 165 | { 166 | "type": 5, 167 | "name": "noembed", 168 | "name_localizations": { 169 | "de": "noembed", 170 | "es-ES": "noembed", 171 | "hi": "noembed", 172 | "ja": "noembed", 173 | "zh-CN": "不包含嵌入内容", 174 | "zh-TW": "不包含嵌入內容", 175 | "it": "noembed", 176 | "tr": "noembed", 177 | "el": "noembed", 178 | "pt-BR": "sem_embed", 179 | "en-GB": "noembed", 180 | "en-US": "noembed", 181 | "fr": "noembed", 182 | "vi": "khongnhung" 183 | }, 184 | "description": "Send only the link without embed?", 185 | "description_localizations": { 186 | "de": "Sende nur den Link ohne Einbettung?", 187 | "es-ES": "¿Enviar solo el enlace sin embed?", 188 | "hi": "बिना एम्बेड के सिर्फ कड़ी को भेजना है?", 189 | "ja": "埋め込みなしでリンクだけを送信しますか?", 190 | "zh-CN": "发送的链接是否不包含嵌入内容?", 191 | "zh-TW": "傳送的連結是否不包含嵌入內容?", 192 | "it": "Invia solo il link senza embed?", 193 | "tr": "Gömülü bir mesaj olmadan sadece bağlantı mı paylaşılmalı?", 194 | "el": "Αποστολή μόνο του συνδέσμου χωρίς ενσωμάτωση;", 195 | "pt-BR": "Enviar o link sem a pré-visualização?", 196 | "en-GB": "Send only the link without embed?", 197 | "en-US": "Send only the link without embed?", 198 | "fr": "Envoyer uniquement le lien sans inclusion ?", 199 | "vi": "Chỉ gửi liên kết mà không nhúng?" 200 | }, 201 | "required": false 202 | }, 203 | { 204 | "type": 5, 205 | "name": "spoiler", 206 | "name_localizations": { 207 | "de": "spoiler", 208 | "es-ES": "spoiler", 209 | "hi": "spoiler", 210 | "ja": "spoiler", 211 | "zh-CN": "标记为剧透", 212 | "zh-TW": "標記為劇透", 213 | "it": "spoiler", 214 | "tr": "spoiler", 215 | "el": "σπόιλερ", 216 | "pt-BR": "spoiler", 217 | "en-GB": "spoiler", 218 | "en-US": "spoiler", 219 | "fr": "spoiler", 220 | "vi": "spoiler" 221 | }, 222 | "description": "Hide the link and embed in a spoiler?", 223 | "description_localizations": { 224 | "de": "Verstecke Link und Einbettung in einem Spoiler?", 225 | "es-ES": "¿Ocultar el enlace y el embed en un spoiler?", 226 | "hi": "कड़ी और एम्बेड को एक स्पॉइलर में छिपाना है?", 227 | "ja": "リンクを隠して、スポイラーに埋め込む?", 228 | "zh-CN": "是否将链接和嵌入内容标记为剧透?", 229 | "zh-TW": "是否將連結和嵌入內容標記為劇透?", 230 | "it": "Nascondi il link e l'embed in uno spoiler?", 231 | "tr": "Bağlantı ve gömülü mesaj bir spoiler'da gizlenmeli mi?", 232 | "el": "Απόκρυψη συνδέσμου και ενσωμάτωση στο σπόιλερ;", 233 | "pt-BR": "Ocultar o link como spoiler?", 234 | "en-GB": "Hide the link and embed in a spoiler?", 235 | "en-US": "Hide the link and embed in a spoiler?", 236 | "fr": "Cacher le lien et l'inclure dans un spoiler ?", 237 | "vi": "Ẩn liên kết bằng cách dùng mã ẩn nội dung?" 238 | }, 239 | "required": false 240 | } 241 | ] 242 | } 243 | -------------------------------------------------------------------------------- /interactions/commands/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "application_id": "", 4 | "type": 1, 5 | "integration_types": [ 6 | 0, 7 | 1 8 | ], 9 | "contexts": [ 10 | 0, 11 | 1, 12 | 2 13 | ], 14 | "default_member_permissions": "1024", 15 | "name": "user", 16 | "name_localizations": { 17 | "en-GB": "user", 18 | "en-US": "user", 19 | "de": "benutzer", 20 | "zh-CN": "user", 21 | "zh-TW": "user", 22 | "it": "utente", 23 | "es-ES": "usuario", 24 | "hi": "user", 25 | "ja": "user", 26 | "el": "χρήστης", 27 | "fr": "user", 28 | "pt-BR": "usuario", 29 | "vi": "nguoidung" 30 | }, 31 | "description": "Post information about a wiki user.", 32 | "description_localizations": { 33 | "en-GB": "Post information about a wiki user.", 34 | "en-US": "Post information about a wiki user.", 35 | "de": "Sende Informationen über einen Wiki-Benutzer.", 36 | "zh-CN": "发送关于wiki用户的信息。", 37 | "zh-TW": "傳送關於wiki使用者的資訊。", 38 | "it": "Posta informazioni su un utente della wiki.", 39 | "es-ES": "Publicar información sobre un usuario wiki.", 40 | "hi": "एक विकि सदस्य के बारे में जानकारी पोस्ट करें।", 41 | "ja": "Wikiユーザーに関する情報を投稿する。", 42 | "el": "Πληροφορίες δημοσίευσης για έναν χρήστη του wiki.", 43 | "fr": "Publier des informations sur un utilisateur de wiki.", 44 | "pt-BR": "Postar informações sobre um usuário da wiki.", 45 | "vi": "Gửi thông tin về một người dùng wiki." 46 | }, 47 | "options": [ 48 | { 49 | "type": 3, 50 | "name": "username", 51 | "name_localizations": { 52 | "en-GB": "username", 53 | "en-US": "username", 54 | "de": "benutzername", 55 | "zh-CN": "用户名", 56 | "zh-TW": "使用者名稱", 57 | "it": "username", 58 | "es-ES": "nombredeusuario", 59 | "hi": "username", 60 | "ja": "username", 61 | "el": "όνομα_χρήστη", 62 | "fr": "username", 63 | "pt-BR": "nomedousuario", 64 | "vi": "tennguoidung" 65 | }, 66 | "description": "The user to show information of.", 67 | "description_localizations": { 68 | "en-GB": "The user to show information of.", 69 | "en-US": "The user to show information of.", 70 | "de": "Der Benutzer über den Informationen gezeigt werden sollen.", 71 | "zh-CN": "要展示信息的用户。", 72 | "zh-TW": "要展示其資訊的使用者。", 73 | "it": "L'utente di cui mostrare informazioni.", 74 | "es-ES": "El usuario a mostrar información de.", 75 | "hi": "सदस्य जिसके बारे में जानकारी पोस्ट करनी है।", 76 | "ja": "この情報を表示するユーザー。", 77 | "el": "Ο χρήστης του οποίου οι πληροφορίες θα εμφανιστούν.", 78 | "fr": "L'utilisateur pour lequel des informations doivent être affichées.", 79 | "pt-BR": "O usuário do qual mostrar informações.", 80 | "vi": "Người dùng mà bạn cần xem thông tin." 81 | }, 82 | "required": true, 83 | "autocomplete": true 84 | }, 85 | { 86 | "type": 3, 87 | "name": "wiki", 88 | "name_localizations": { 89 | "de": "wiki", 90 | "es-ES": "wiki", 91 | "zh-CN": "wiki", 92 | "zh-TW": "wiki", 93 | "hi": "wiki", 94 | "it": "wiki", 95 | "tr": "wiki", 96 | "el": "wiki", 97 | "pt-BR": "wiki", 98 | "en-GB": "wiki", 99 | "en-US": "wiki", 100 | "ja": "wiki", 101 | "fr": "wiki", 102 | "vi": "wiki" 103 | }, 104 | "description": "The wiki to search in.", 105 | "description_localizations": { 106 | "de": "Das Wiki zum Durchsuchen.", 107 | "es-ES": "El wiki en el que buscar.", 108 | "zh-CN": "要搜索的wiki。", 109 | "zh-TW": "要搜尋的wiki。", 110 | "hi": "विकि जिसमें खोजना है।", 111 | "it": "La wiki in cui cercare.", 112 | "tr": "Aratma yapılacak viki.", 113 | "el": "To wiki προς αναζήτηση.", 114 | "pt-BR": "A wiki para pesquisar.", 115 | "en-GB": "The wiki to search in.", 116 | "en-US": "The wiki to search in.", 117 | "ja": "検索対象となるWiki。", 118 | "fr": "Le wiki dans lequel effectuer la recherche.", 119 | "vi": "Wiki bạn cần tìm kiếm." 120 | }, 121 | "required": false, 122 | "autocomplete": true 123 | }, 124 | { 125 | "type": 5, 126 | "name": "private", 127 | "name_localizations": { 128 | "de": "privat", 129 | "es-ES": "privado", 130 | "hi": "private", 131 | "ja": "private", 132 | "zh-CN": "仅自己可见", 133 | "zh-TW": "僅自己可見", 134 | "it": "privato", 135 | "tr": "private", 136 | "el": "ιδιωτικό", 137 | "pt-BR": "privado", 138 | "en-GB": "private", 139 | "en-US": "private", 140 | "fr": "private", 141 | "vi": "riengtu" 142 | }, 143 | "description": "Send a response only visible to yourself?", 144 | "description_localizations": { 145 | "de": "Sende eine Antwort nur sichtbar für dich?", 146 | "es-ES": "¿Enviar una respuesta que solo sea visible para ti?", 147 | "hi": "सिर्फ आपको दिखने वाला एक जवाब भेजना है?", 148 | "ja": "自分だけに見える応答で送信しますか?", 149 | "zh-CN": "发送的消息是否仅对自己可见?", 150 | "zh-TW": "傳送的訊息是否僅對自己可見?", 151 | "it": "Invia una risposta visibile solo per te?", 152 | "tr": "Yalnızca sizin görebileceğiniz bir yanıt gönderilsin mi?", 153 | "el": "Αποστολή απόκρισης ορατή μόνο σε εσάς;", 154 | "pt-BR": "Enviar uma resposta visível apenas para você?", 155 | "en-GB": "Send a response only visible to yourself?", 156 | "en-US": "Send a response only visible to yourself?", 157 | "fr": "Envoyer une réponse uniquement visible par vous-même ?", 158 | "vi": "Gửi phản hồi chỉ xem được bởi bạn?" 159 | }, 160 | "required": false 161 | }, 162 | { 163 | "type": 5, 164 | "name": "noembed", 165 | "name_localizations": { 166 | "de": "noembed", 167 | "es-ES": "noembed", 168 | "hi": "noembed", 169 | "ja": "noembed", 170 | "zh-CN": "不包含嵌入内容", 171 | "zh-TW": "不包含嵌入內容", 172 | "it": "noembed", 173 | "tr": "noembed", 174 | "el": "αποενσωμάτωση", 175 | "pt-BR": "noembed", 176 | "en-GB": "noembed", 177 | "en-US": "noembed", 178 | "fr": "noembed", 179 | "vi": "khongnhung" 180 | }, 181 | "description": "Send only the link without embed?", 182 | "description_localizations": { 183 | "de": "Sende nur den Link ohne Einbettung?", 184 | "es-ES": "¿Enviar solo el enlace sin embed?", 185 | "hi": "बिना एम्बेड के सिर्फ कड़ी को भेजना है?", 186 | "ja": "埋め込みなしでリンクだけを送信しますか?", 187 | "zh-CN": "发送的链接是否不包含嵌入内容?", 188 | "zh-TW": "傳送的連結是否不包含嵌入內容?", 189 | "it": "Invia solo il link senza embed?", 190 | "tr": "Gömülü bir mesaj olmadan sadece bağlantı mı paylaşılmalı?", 191 | "el": "Αποστολή μόνο συνδέσμου χωρίς ενσωμάτωση;", 192 | "pt-BR": "Enviar apenas o link sem incorporar?", 193 | "en-GB": "Send only the link without embed?", 194 | "en-US": "Send only the link without embed?", 195 | "fr": "Envoyer uniquement le lien sans inclusion ?", 196 | "vi": "Chỉ gửi liên kết mà không nhúng?" 197 | }, 198 | "required": false 199 | }, 200 | { 201 | "type": 5, 202 | "name": "spoiler", 203 | "name_localizations": { 204 | "de": "spoiler", 205 | "es-ES": "spoiler", 206 | "hi": "spoiler", 207 | "ja": "spoiler", 208 | "zh-CN": "标记为剧透", 209 | "zh-TW": "標記為劇透", 210 | "it": "spoiler", 211 | "tr": "spoiler", 212 | "el": "spoiler", 213 | "pt-BR": "spoiler", 214 | "en-GB": "spoiler", 215 | "en-US": "spoiler", 216 | "fr": "spoiler", 217 | "vi": "spoiler" 218 | }, 219 | "description": "Hide the link and embed in a spoiler?", 220 | "description_localizations": { 221 | "de": "Verstecke Link und Einbettung in einem Spoiler?", 222 | "es-ES": "¿Ocultar el enlace y el embed en un spoiler?", 223 | "hi": "कड़ी और एम्बेड को एक स्पॉइलर में छिपाना है?", 224 | "ja": "リンクを隠して、スポイラーに埋め込む?", 225 | "zh-CN": "是否将链接和嵌入内容标记为剧透?", 226 | "zh-TW": "是否將連結和嵌入內容標記為劇透?", 227 | "it": "Nascondi il link e l'embed in uno spoiler?", 228 | "tr": "Bağlantı ve gömülü mesaj bir spoiler'da gizlenmeli mi?", 229 | "el": "Απόκρυψη του συνδέσμου και ενσωμάτωση σε spoiler;", 230 | "pt-BR": "Ocultar o link e incorporar em um spoiler?", 231 | "en-GB": "Hide the link and embed in a spoiler?", 232 | "en-US": "Hide the link and embed in a spoiler?", 233 | "fr": "Cacher le lien et l'inclure dans un spoiler ?", 234 | "vi": "Ẩn liên kết bằng cách dùng mã ẩn nội dung?" 235 | }, 236 | "required": false 237 | } 238 | ] 239 | } 240 | -------------------------------------------------------------------------------- /interactions/commands/verify.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "application_id": "", 4 | "type": 1, 5 | "integration_types": [ 6 | 0 7 | ], 8 | "contexts": [ 9 | 0 10 | ], 11 | "default_member_permissions": "3072", 12 | "name": "verify", 13 | "name_localizations": { 14 | "en-GB": "verify", 15 | "en-US": "verify", 16 | "de": "verifizieren", 17 | "it": "verifica", 18 | "sv-SE": "verifiera", 19 | "tr": "verify", 20 | "hi": "verify", 21 | "pl": "weryfikuj", 22 | "ru": "verify", 23 | "zh-CN": "verify", 24 | "zh-TW": "verify", 25 | "es-ES": "verificar", 26 | "pt-BR": "verificar", 27 | "ja": "verify", 28 | "el": "επιβεβαίωση", 29 | "fr": "verify", 30 | "vi": "xacnhan" 31 | }, 32 | "description": "Verify your Discord account with your wiki account.", 33 | "description_localizations": { 34 | "en-GB": "Verify your Discord account with your wiki account.", 35 | "en-US": "Verify your Discord account with your wiki account.", 36 | "de": "Verifiziere deinen Discord-Account mit deinem Wiki-Account.", 37 | "it": "Verifica il tuo account Discord tramite il tuo account sulla wiki.", 38 | "sv-SE": "Verifiera ditt Discord-konto med ditt wiki-konto.", 39 | "tr": "Viki hesabın ile Discord hesabını doğrula.", 40 | "hi": "अपने विकि खाते के साथ अपने डिस्कॉर्ड खाते को प्रमाणित करें।", 41 | "pl": "Zweryfikuj swoje konto Discord używając konta na wiki.", 42 | "ru": "Подтвердите свою учетную запись Discord с помощью вашей учетной записи wiki.", 43 | "zh-CN": "使用你的wiki账号来验证你的Discord账号。", 44 | "zh-TW": "使用您的wiki帳號驗證您的Discord帳號。", 45 | "es-ES": "Verifica tu cuenta de Discord con tu cuenta wiki.", 46 | "pt-BR": "Verifique a sua conta do Discord na sua conta da wiki.", 47 | "ja": "DiscordのアカウントとWikiのアカウントを連携してください。", 48 | "el": "Επιβεβαίωση του λογαριασμού Discord με τον λογαριασμό wiki.", 49 | "fr": "Vérifiez votre compte Discord avec votre compte wiki.", 50 | "vi": "Liên kết tài khoản Discord của bạn với tài khoản wiki của bạn." 51 | }, 52 | "options": [ 53 | { 54 | "type": 3, 55 | "name": "username", 56 | "name_localizations": { 57 | "en-GB": "username", 58 | "en-US": "username", 59 | "de": "benutzername", 60 | "it": "username", 61 | "sv-SE": "username", 62 | "tr": "username", 63 | "hi": "username", 64 | "pl": "nick", 65 | "ru": "username", 66 | "zh-CN": "用户名", 67 | "zh-TW": "使用者名稱", 68 | "es-ES": "nombre_de_usuario", 69 | "pt-BR": "nomedeusuario", 70 | "ja": "username", 71 | "el": "username", 72 | "fr": "username", 73 | "vi": "tennguoidung" 74 | }, 75 | "description": "Your username on the wiki.", 76 | "description_localizations": { 77 | "en-GB": "Your username on the wiki.", 78 | "en-US": "Your username on the wiki.", 79 | "de": "Dein Benutzername im Wiki.", 80 | "it": "Il tuo nome utente sulla wiki.", 81 | "sv-SE": "Ditt användarnamn på wikin.", 82 | "tr": "Vikideki kullanıcı adın.", 83 | "hi": "विकि पर आपका सदस्यनाम।", 84 | "pl": "Twoja nazwa użytkownika na wiki.", 85 | "ru": "Ваше имя пользователя на вики.", 86 | "zh-CN": "你在此wiki上的用户名。", 87 | "zh-TW": "您在此wiki上的使用者名稱。", 88 | "es-ES": "Tu nombre de usuario en el wiki.", 89 | "pt-BR": "Seu nome de usuário na wiki.", 90 | "ja": "Wiki上でのユーザー名。", 91 | "el": "Το όνομα χρήστη σας στο wiki.", 92 | "fr": "Votre nom d'utilisateur sur le wiki.", 93 | "vi": "Tên người dùng wiki của bạn." 94 | }, 95 | "required": false, 96 | "autocomplete": true 97 | } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /interactions/diff.js: -------------------------------------------------------------------------------- 1 | import { MessageFlags, PermissionFlagsBits } from 'discord.js'; 2 | import { isMessage, canShowEmbed, allowDelete, sendMessage } from '../util/functions.js'; 3 | import interwiki_interaction from './interwiki.js'; 4 | import wiki_diff from '../cmds/wiki/diff.js'; 5 | 6 | /** 7 | * Post a message with a wiki edit diff. 8 | * @param {import('discord.js').ChatInputCommandInteraction} interaction - The interaction. 9 | * @param {import('../util/i18n.js').default} lang - The user language. 10 | * @param {import('../util/wiki.js').default} wiki - The wiki for the interaction. 11 | */ 12 | function slash_diff(interaction, lang, wiki) { 13 | var args = []; 14 | let subcommand = interaction.options.getSubcommand(); 15 | if ( subcommand === 'page' ) { 16 | let title = interaction.options.getString('title')?.trim(); 17 | if ( !title ) { 18 | return interaction.reply( { 19 | content: lang.uselang(interaction.locale).get('interaction.notitle'), 20 | flags: MessageFlags.Ephemeral 21 | } ).catch(log_error); 22 | } 23 | args.push(title); 24 | } 25 | else { 26 | args.push(interaction.options.getInteger('diffid').toString()); 27 | if ( subcommand === 'relative' ) args.push(interaction.options.getString('compare') ?? 'prev'); 28 | else if ( subcommand === 'multiple' ) args.push(interaction.options.getInteger('oldid').toString()); 29 | } 30 | return interwiki_interaction.FUNCTIONS.getWiki(interaction.options.getString('wiki'), wiki).then( newWiki => { 31 | var flags = ( interaction.options.getBoolean('private') ?? false ) || pausedGuilds.has(interaction.guildId) ? MessageFlags.Ephemeral : undefined; 32 | if ( interaction.wikiWhitelist.length && !interaction.wikiWhitelist.includes( newWiki.href ) ) flags = MessageFlags.Ephemeral; 33 | var noEmbed = interaction.options.getBoolean('noembed') || !canShowEmbed(interaction); 34 | var spoiler = interaction.options.getBoolean('spoiler') ? '||' : ''; 35 | if ( flags ) lang = lang.uselang(interaction.locale); 36 | return interaction.deferReply( {flags} ).then( () => { 37 | return wiki_diff(lang, interaction, args, newWiki, spoiler, noEmbed).then( result => { 38 | if ( !result || isMessage(result) ) return result; 39 | let noEmoji = !interaction.appPermissions?.has(PermissionFlagsBits.UseExternalEmojis); 40 | if ( result.message ) { 41 | if ( Array.isArray(result.message) ) { 42 | let list = []; 43 | return result.message.slice(1).reduce( (prev, content) => { 44 | return prev.then( message => { 45 | list.push(message); 46 | return interaction.followUp( {content, flags} ).then( msg => { 47 | if ( !msg.flags.has(MessageFlags.Ephemeral) ) allowDelete(msg, interaction.user.id); 48 | return msg; 49 | }, log_error ); 50 | } ); 51 | }, sendMessage(interaction, { 52 | content: result.message[0], 53 | flags 54 | }) ).then( message => { 55 | list.push(message); 56 | return list; 57 | } ); 58 | } 59 | if ( result.reaction === WB_EMOJI.error ) { 60 | if ( typeof result.message === 'string' ) result.message = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.error ) + ' ' + result.message; 61 | else result.message.content = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.error ) + ' ' + ( result.message.content ?? '' ); 62 | } 63 | else if ( result.reaction === WB_EMOJI.warning ) { 64 | if ( typeof result.message === 'string' ) result.message = WB_EMOJI.warning + ' ' + result.message; 65 | else result.message.content = WB_EMOJI.warning + ' ' + ( result.message.content ?? '' ); 66 | } 67 | return sendMessage(interaction, result.message); 68 | } 69 | else if ( result.reaction ) { 70 | let message = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.error ) + ' ' + lang.get('interaction.error') + '\n' + process.env.invite; 71 | if ( result.reaction === WB_EMOJI.nowiki ) message = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.nowiki ) + ' ' + lang.get('interaction.nowiki'); 72 | if ( result.reaction === WB_EMOJI.shrug ) message = WB_EMOJI.shrug + ' ' + lang.get('search.noresult'); 73 | return sendMessage(interaction, {content: message}); 74 | } 75 | } ); 76 | }, log_error ); 77 | }, () => { 78 | return interaction.reply( { 79 | content: lang.uselang(interaction.locale).get('interaction.interwiki'), 80 | flags: MessageFlags.Ephemeral 81 | } ).catch(log_error); 82 | } ); 83 | } 84 | 85 | export default { 86 | name: 'diff', 87 | slash: slash_diff, 88 | autocomplete: interwiki_interaction.autocomplete, 89 | allowDelete: true 90 | }; -------------------------------------------------------------------------------- /interactions/i18n/bn.json: -------------------------------------------------------------------------------- 1 | { 2 | "inline": { 3 | "description": "ইনলাইন উইকি লিঙ্কসহ একটি বার্তা পোস্ট করুন।", 4 | "name": "inline", 5 | "options[0].description": "উইকিটেক্সট লিঙ্ক সহ পাঠ্য।", 6 | "options[0].name": "text" 7 | }, 8 | "verify": { 9 | "description": "আপনার উইকি অ্যাকাউন্ট দিয়ে আপনার ডিসকর্ড অ্যাকাউন্ট যাচাই করুন।", 10 | "name": "verify", 11 | "options[0].description": "উইকিতে আপনার ব্যবহারকারী নাম।", 12 | "options[0].name": "username" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /interactions/i18n/et.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /interactions/i18n/ko.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /interactions/i18n/lzh.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /interactions/i18n/nl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /interactions/i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "_app-directory": { 3 | "detailed_description": "**Wiki-Bot** to bot, który ma na celu ułatwienie **linkowania oraz przeszukiwania stron opartych o oprogramowanie MediaWiki** takich jak Wikipedia czy Fandom. Wyświetla on **krótki opis oraz dodatkowe informacje** na temat stron, oraz jest w stanie podążać za przekierowaniami czy linkami interwiki. Wiki-Bot dodatkowo daje możliwość **weryfikacji kont Discord z użyciem kont wiki** oraz **integrację ostatnich zmian wiki na Discordzie**.\n\nZarządzający serwerem mogą zmieniać ustawienia bota na serwerze z użyciem panelu sterowania: https://settings.wikibot.de/\n\n# Weryfikacja Użytkownika\n\nUżywając komendy `!wiki weryfikuj `, użytkownicy mogą zweryfikować się za pomocą konta na wiki używając do tego określonego miejsca (pola Discord lub strony użytkownika). Jeżeli nazwa użytkownika się zgadza oraz weryfikacje są włączone na danym kanale, Wiki-Bot nada im odpowiednie role dla wszystkich wpisów w ustawieniach weryfikacji, dla których użytkownik spełnia wymagania.\n\nUżywając komendy `!wiki verification`, administratorzy mogą dodać do 10 różnych rekordów weryfikacji. Każdy rekord weryfikacji pozwala na ustawienie wielu restrykcji, jakie użytkownik musi spełnić aby zostać zweryfikowany:\n\n* Kanał, w którym komenda `!wiki weryfikuj` może zostać użyta.\n* Rola do otrzymania gdy użytkownik spełnia wymagania dla rekordów.\n* Wymagana ilość edycji na wiki.\n* Wymagana przynależność do grupy użytkowników na wiki.\n* Wymagany wiek konta wiki.\n* Czy nazwa użytkownika na Discordzie powinna zostać zmieniona na nazwę użytkownika z wiki gdy nastąpi poprawna weryfikacja.\n\n# Webhook Ostatnich zmian\n\nWiki-Bot jest w stanie wysyłać ostatnie zmiany z wiki na Discorda z pomocą webhooka. Aby to zrobić, należy użyć komendy `!wiki rcscript`. Ostatnie zmiany mogą być wysłane w dwóch formach: skróconej (kompaktowej) oraz szczegółowej z większą ilością informacji ale także zajmującą więcej miejsca.\n\nWymagania do dodania webhooka ostatnich zmian:\n\n* Wiki musi działać na wersji MediaWiki 1.30 lub wyższej.\n* Wiadomość systemowa `MediaWiki:Custom-RcGcDw` musi zawierać jedynie numer id serwera Discord, na którym tworzony jest webhook.\n\n# Konfiguracja\n\nPo zaproszeniu Wiki-Bota na serwer, pierwszą rzeczą, którą powinno się wykonać jest ustawienie domyślnej wiki, którą chcesz aby Wiki-Bot przeszukiwał domyślnie. Możesz to zrobić za pomocą komendy `!wiki settings` lub używając panelu sterowania.\n\n* Zmień domyślną wiki za pomocą `!wiki settings wiki `\n * Na przykład: `!wiki settings wiki https://minecraft.wiki/`\n* Zmień język używając `!wiki settings lang `\n * Na przykład: `!wiki settings lang Polish`\n\n# Komendy\n\nPełna lista komend jest dostępna po wykonaniu `!wiki pomoc`\n\n| Komenda | Opis |\n| ------- | ----------- |\n| `!wiki ` | Wiki-Bot odpowie linkiem do pasującego artykułu na wiki. |\n| `!wiki ! ` | Wiki-Bot odpowie linkiem do pasującego artykułu na Wikipedii w podanym języku: `https://.wikipedia.org/` |\n| `!wiki ? ` | Wiki-Bot odpowie linkiem do pasującego artykułu na podanej wiki Fandomu: `https://.fandom.com/` |\n| `!wiki !! ` | Wiki-Bot odpowie linkiem do pasującego artykułu na podanym projekcie MediaWiki. Na przykład: `!wiki !!pl.wikipedia.org Ciastko` |\n| `!wiki User:` | Wiki-Bot pokaże informacje o koncie na wiki. |\n| `!wiki losuj` | Wiki-Bot odpowie linkiem do losowej strony na wiki. |\n| `!wiki przegląd` | Wiki-Bot pokaże dodatkowe informacje i statystyki na temat wiki. |\n| `!wiki diff []` | Wiki-Bot odpowie linkiem do określonych zmian na wiki. |\n| `!wiki diff ` | Wiki-Bot odpowie linkiem do ostatniej edycji wprowadzonej w podanym artykule. |\n| `!wiki informacje` | Wiki-Bot się przywita. |\n| `!wiki pomoc` | Wiki-Bot wylistuje wszystkie komendy, które rozumie. |\n| `!wiki pomoc ` | Wiki-Bot wyjaśni komendę. |\n| `!wiki test` | Wiki-Bot odpowie informacjami o stanie połączenia z Discordem oraz wiki. |\n\nJeżeli odpowiedź nie była tą, którą oczekiwano, możesz zareagować na wiadomość bota używając emotki 🗑️ (`:wastebasket:`) pod wiadomością bota, a Wiki-Bot ją usunie.\n\n## Administracja\n\nPełna lista komend administracyjnych jest dostępna po wykonaniu `!wiki pomoc admin`\n\n| Komenda | Opis |\n| ------- | ----------- |\n| `!wiki help admin` | Wiki-Bot wylistuje wszystkie komendy administracyjne. |\n| `!wiki settings` | Wiki-Bot zmieni ustawienia serwera. |\n| `!wiki verification` | Wiki-Bot zmieni ustawienia weryfikacji, które pozwalają na użycie komendy `!wiki weryfikuj`. |\n| `!wiki rcscript` | Wiki-Bot zmieni webhook ostatnich zmian. |\n| `!wiki pause @Wiki-Bot` | Wiki-Bot zacznie ignorować wszystkie komendy na serwerze z wyjątkiem paru administracyjnych. |\n\nAdministratorzy serwera mogą również użyć panelu sterowania dostępnego pod adresem: https://settings.wikibot.de/", 4 | "short_description": "Wiki-Bot ułatwia szukanie oraz linkowanie do stron wiki, pokazując opisy oraz dodatkowe informacje. Wiki-Bot również oferuje funkcję weryfikacji kont Discord oraz przesyłanie ostatnich zmian z wiki." 5 | }, 6 | "inline": { 7 | "description": "Utwórz wiadomość z linkami pisanymi składnią wiki.", 8 | "name": "inline", 9 | "options[0].description": "Tekst zawierający linki pisane składnią wiki.", 10 | "options[0].name": "tekst" 11 | }, 12 | "verify": { 13 | "description": "Zweryfikuj swoje konto Discord używając konta na wiki.", 14 | "name": "weryfikuj", 15 | "options[0].description": "Twoja nazwa użytkownika na wiki.", 16 | "options[0].name": "nick" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /interactions/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "inline": { 3 | "description": "Разместите сообщение со встроенными вики-ссылками.", 4 | "name": "inline", 5 | "options[0].description": "Текст, включающий ссылки на вики-текст.", 6 | "options[0].name": "text" 7 | }, 8 | "verify": { 9 | "description": "Подтвердите свою учетную запись Discord с помощью вашей учетной записи wiki.", 10 | "name": "verify", 11 | "options[0].description": "Ваше имя пользователя на вики.", 12 | "options[0].name": "username" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /interactions/i18n/sr.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /interactions/i18n/sv-SE.json: -------------------------------------------------------------------------------- 1 | { 2 | "_app-directory": { 3 | "detailed_description": "**Wiki-Bot** är en bot med syftet att enkelt **länka och söka MediaWiki-sajter** som Wikipedia och Fandom-wikis. Den visar **korta beskrivningar och ytterligare information** om sidor och kan lösa omdirigeringar och följa interwikilänkar. Wiki-Bot tillhandahåller också **användarverifiering av wiki-konton** och **flöden för senaste ändringar**.\n\nÄndra serverinställningarna för Wiki-Bot med hjälp av kontrollpanelen: https://settings.wikibot.de/\n\n# Användarverifiering\n\nGenom att använda kommandot `!wiki verify ` kan användare verifiera sig själva som en specifik wiki-användare genom att använda Discord-fältet på sin wikiprofil. Om användarmatchningar och användarverifieringar är inställda på servern kommer Wiki-Bot att ge dem rollerna för alla verifieringsposter de matchade.\n\nGenom att använda kommandot `!wiki verification` kan administratörer lägga till upp till 10 verifieringsposter på en server. Varje verifieringspost tillåter flera begränsningar för när en användare ska matcha verifieringen.\n\n* Kanal för att använda kommandot `!wiki verify` i.\n* Få en roll när du matchar verifieringsposten.\n* Kräv ett visst antal redigeringar på wikin för att matcha verifieringsposten.\n* Tillhör en viss användargrupp på wikin för att matcha verifieringsposten.\n* Obligatorisk kontoålder i antal dagar för att matcha verifieringsposten.\n* Om Discord-användarnas smeknamn ska ställas in på deras wiki-användarnamn när de matchar verifieringsinlägget.\n\n# Senaste ändringar-Webhook\n\nWiki-Bot kan köra en webbhook för senaste ändringar genom att använda kommandot `!wiki rcscript`. De senaste ändringarna kan visas i kompakta textmeddelanden med \"inline\"-länkar eller bädda in meddelanden med redigeringstaggar och kategoriändringar.\n\nRequirements to add a recent changes webhook:\n\n* Wiki måste köras på MediaWiki 1.30 eller senare.\n* Systemmeddelandet `MediaWiki:Custom-RcGcDw` måste ställas in på Discord-serverns id.\n\n# Setup\n\nEfter att ha bjudit in Wiki-Bot till din server måste du ställa in den wiki du vill söka efter som standard. Du gör detta med kommandot `!wiki settings` eller genom att använda kontrollpanelen.\n\n* Ändra wiki med `!wiki settings wiki `\n * Exempel: `!wiki settings wiki https://minecraft.wiki/`\n* Ändra språk med `!wiki settings lang `\n * Exempel: `!wiki settings lang svenska`\n\n# Kommandon\n\nFör en fullständig lista med alla kommandon, använd `!wiki help`\n\n| Kommando | Beskrivning |\n\n| `!wiki ` | Wiki-Bot kommer att svara med en länk till en matchande artikel i wikin. |\n| `!wiki ! ` | Wiki-Bot kommer att svara med en länk till en matchande artikel på det namngivna Wikipedia-språket: `https://.wikipedia.org/` |\n| `!wiki ? ` | Wiki-Bot kommer att svara med en länk till en matchande artikel i den namngivna Fandom-wikin: `https://.fandom.com/` |\n| `!wiki !! ` | Wiki-Bot kommer att svara med en länk till en matchande artikel i det namngivna MediaWiki-projektet. Exempel: `!wiki !!en.wikipedia.org Cookie` |\n| `!wiki User:` | Wiki-Bot kommer att visa lite information om användaren. |\n| `!wiki random` | Wiki-Bot kommer att svara med en länk till en slumpmässig sida på wikin. |\n| `!wiki overview` | Wiki-Bot kommer att visa lite information och statistik om wikin. |\n| `!wiki diff []` | Wiki-Bot kommer att svara med en länk till skillnaden på wikin. |\n| `!wiki diff ` | Wiki-Bot kommer att svara med en länk till den sista ändringen i artikeln på wikin. |\n| `!wiki info` | Wiki-Bot kommer att presentera sig själv. |\n| `!wiki help` | Wiki-Bot kommer att lista alla kommandon som den förstår. |\n| `!wiki help ` | Wiki-Bot kommer att förklara kommandot. |\n| `!wiki test` | Wiki-Bot kommer att svara med sin egen och wikins svarstid. |\n\nOm du fick ett oönskat svar kan du reagera med 🗑️ (`:wastebasket:`) på bottens meddelande och Wiki-Bot raderar det.\n\n## Admin\n\nFör en fullständig lista med alla administratörskommandon använd `!wiki help admin`\n\n| Kommando | Beskrivning |\n| ------- | ----------- |\n| `!wiki help admin` | Wiki-Bot kommer att lista alla administratörskommandon. |\n| `!wiki settings` | Wiki-Bot kommer att ändra inställningarna för servern. |\n| `!wiki verification` | Wiki-Bot kommer ändra wiki-verifieringar använda av`!wiki verify`-kommandot. |\n| `!wiki rcscript` | Wiki-Bot kommer ändra \"senaste ändringarna\"-webhooken. |\n| `!wiki pause @Wiki-Bot` | Wiki-Bot kommer att ignorera alla kommandon på den här servern, förutom några administratörskommandon. |\n\nAdministratörer kan också använda instrumentpanelen för att ändra botinställningarna: https://settings.wikibot.de/", 4 | "short_description": "Wiki-Bot har som syfte att enkelt kunna söka efter och länka till wiki-sida, samtidigt som den visar beskrivningar och ytterligare information. Wiki-Bot tillhandahåller också användarverifiering av wiki-konton och flöden för senaste ändringar." 5 | }, 6 | "diff": { 7 | "description": "Visa skillnad av en wiki redigeringar.", 8 | "name": "diff", 9 | "options[0].description": "Visa skillnaden för den senaste redigeringen för en wiki sida.", 10 | "options[0].name": "sida", 11 | "options[0].options[0].description": "Titeln på sidan du vill se den senaste skillnaden för.", 12 | "options[0].options[0].name": "titel", 13 | "options[0].options[1].description": "Wikin att söka i.", 14 | "options[0].options[1].name": "wiki", 15 | "options[0].options[2].description": "Vill du endast visa svaret för dig själv?", 16 | "options[0].options[2].name": "privat", 17 | "options[0].options[3].description": "Skicka endast länken, utan inbäddning?", 18 | "options[0].options[4].description": "Dölj länken och inbäddningen som en \"spoiler\"?", 19 | "options[0].options[4].name": "spoiler", 20 | "options[1].description": "Visa en wiki-redigeringsdiff baserat på ID:et.", 21 | "options[1].name": "ids", 22 | "options[1].options[0].description": "Visa en relativ wiki-redigeringsdiff baserat på ID:et.", 23 | "options[1].options[0].name": "relativ" 24 | }, 25 | "inline": { 26 | "description": "Skicka ett meddelande med wikilänkar på samma rad.", 27 | "name": "inline", 28 | "options[0].description": "Text med länkar till wikitext.", 29 | "options[0].name": "text", 30 | "options[1].description": "Wikin att söka i.", 31 | "options[1].name": "wiki" 32 | }, 33 | "verify": { 34 | "description": "Verifiera ditt Discord-konto med ditt wiki-konto.", 35 | "name": "verifiera", 36 | "options[0].description": "Ditt användarnamn på wikin.", 37 | "options[0].name": "username" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /interactions/i18n/th.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /interactions/i18n/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "_app-directory": { 3 | "short_description": "Wiki-Bot'un amacı viki sayfalarını kolayca aratıp bağlantı vermek ve bunu yaparken kısa açıklamalar ile ek bilgi sağlamaktır. Ayrıca viki hesabı doğrulama ve son değişiklikler akışı da sağlamaktadır." 4 | }, 5 | "diff": { 6 | "description": "Bir viki düzenlemesi değişikliklerini göster.", 7 | "name": "diff", 8 | "options[0].description": "Bir viki sayfasının son düzenleme farkını göster.", 9 | "options[0].name": "sayfa", 10 | "options[0].options[0].description": "Son düzenleme farkını gösteren sayfa başlığı.", 11 | "options[0].options[0].name": "başlık", 12 | "options[0].options[1].description": "Aranacak viki.", 13 | "options[0].options[1].name": "viki", 14 | "options[0].options[2].description": "Sadece sizin görebileceği bir yanıt gönderilsin mi?", 15 | "options[0].options[2].name": "gizli", 16 | "options[0].options[3].description": "Gömülü mesaj olmadan bağlantı mı gönderilsin?", 17 | "options[0].options[3].name": "gömülüsüz", 18 | "options[0].options[4].description": "Bağlantı ve gömülü mesaj spoiler ile gizlensin mi?", 19 | "options[0].options[4].name": "spoiler", 20 | "options[1].description": "ID'ye göre bir viki düzenleme farkını göster.", 21 | "options[1].name": "idler", 22 | "options[1].options[0].description": "ID'ye göre göreli bir viki düzenleme farkı göster.", 23 | "options[1].options[0].name": "ilgili", 24 | "options[1].options[0].options[0].description": "Gösterilecek düzenleme farkının fark ID'si.", 25 | "options[1].options[0].options[0].name": "farkid", 26 | "options[1].options[0].options[1].choices[0].name": "Önceki revizyonla karşılaştır. (varsayılan)", 27 | "options[1].options[0].options[1].choices[1].name": "Bir sonraki revizyonla karşılaştır.", 28 | "options[1].options[0].options[1].choices[2].name": "Mevcut revizyonla karşılaştır.", 29 | "options[1].options[0].options[1].description": "Gösterilecek düzenleme farkının ilgili revizyonu.", 30 | "options[1].options[0].options[1].name": "karşılaştır", 31 | "options[1].options[0].options[2].description": "Aranacak viki.", 32 | "options[1].options[0].options[2].name": "viki", 33 | "options[1].options[0].options[3].description": "Sadece kendinizin görebileceği bir yanıt mı gönderilsin?", 34 | "options[1].options[0].options[3].name": "gizli", 35 | "options[1].options[0].options[4].description": "Gömülü mesaj olmadan sadece bağlantı mı gönderilmeli?", 36 | "options[1].options[0].options[4].name": "gömülümesajsız", 37 | "options[1].options[0].options[5].description": "Bağlantıyı gizle ve spoiler içine göm?", 38 | "options[1].options[0].options[5].name": "spoiler", 39 | "options[1].options[1].description": "ID'lere dayalı olarak birden fazla düzeltme üzerinde bir viki düzenleme farkını göster.", 40 | "options[1].options[1].name": "birdenfazla" 41 | }, 42 | "inline": { 43 | "description": "Satır içi viki bağlantıları ile mesaj paylaş.", 44 | "name": "inline", 45 | "options[0].description": "Wikitext bağlantılarını içeren metin.", 46 | "options[0].name": "text", 47 | "options[1].description": "Aratma yapılacak viki.", 48 | "options[1].name": "wiki" 49 | }, 50 | "interwiki": { 51 | "description": "Farklı bir vikiye bağlantı ver.", 52 | "name": "interwiki", 53 | "options[0].description": "Aratma yapılacak viki.", 54 | "options[0].name": "wiki", 55 | "options[1].description": "Aranılacak sayfa başlığı.", 56 | "options[1].name": "title", 57 | "options[2].description": "Bağlantı verilecek sayfa bölümü.", 58 | "options[2].name": "section", 59 | "options[3].description": "Bağlantıya eklenecek arama argümanları.", 60 | "options[3].name": "query", 61 | "options[4].description": "Yalnızca sizin görebileceğiniz bir yanıt gönderilsin mi?", 62 | "options[4].name": "private", 63 | "options[5].description": "Gömülü bir mesaj olmadan sadece bağlantı mı paylaşılmalı?", 64 | "options[5].name": "noembed", 65 | "options[6].description": "Bağlantı ve gömülü mesaj bir spoiler'da gizlenmeli mi?", 66 | "options[6].name": "spoiler" 67 | }, 68 | "overview": { 69 | "description": "Viki ön izlemesi paylaş.", 70 | "name": "overview", 71 | "options[0].description": "Aratma yapılacak viki.", 72 | "options[0].name": "wiki", 73 | "options[1].description": "Yalnızca sizin görebileceğiniz bir yanıt gönderilsin mi?", 74 | "options[1].name": "private", 75 | "options[2].description": "Gömülü bir mesaj olmadan sadece bağlantı mı paylaşılmalı?", 76 | "options[2].name": "noembed", 77 | "options[3].description": "Bağlantı ve gömülü mesaj bir spoiler'da gizlenmeli mi?", 78 | "options[3].name": "spoiler" 79 | }, 80 | "verify": { 81 | "description": "Viki hesabın ile Discord hesabını doğrula.", 82 | "name": "verify", 83 | "options[0].description": "Vikideki kullanıcı adın.", 84 | "options[0].name": "username" 85 | }, 86 | "wiki": { 87 | "description": "Bir wiki bağlantısı paylaş.", 88 | "name": "wiki", 89 | "options[0].description": "Aratılacak sayfa başlığı.", 90 | "options[0].name": "title", 91 | "options[1].description": "Bağlantı verilecek sayfa bölümü.", 92 | "options[1].name": "section", 93 | "options[2].description": "Bağlantıya eklenecek arama argümanları.", 94 | "options[2].name": "query", 95 | "options[3].description": "Yalnızca sizin görebileceğiniz bir yanıt gönderilsin mi?", 96 | "options[3].name": "private", 97 | "options[4].description": "Gömülü bir mesaj olmadan sadece bağlantı mı paylaşılmalı?", 98 | "options[4].name": "noembed", 99 | "options[5].description": "Bağlantı ve gömülü mesaj bir spoiler'da gizlenmeli mi?" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /interactions/i18n/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "diff": { 3 | "description": "Показати відмінності в редагуванні вікі.", 4 | "name": "різниця", 5 | "options[0].description": "Показати відмінності останнього редагування вікі-сторінки.", 6 | "options[0].name": "сторінка", 7 | "options[0].options[0].description": "Назва сторінки, щоб показати різницю останнього редагування.", 8 | "options[0].options[0].name": "назва", 9 | "options[0].options[1].description": "Вікі для пошуку.", 10 | "options[0].options[1].name": "вікі", 11 | "options[0].options[2].description": "Надіслати відповідь, видиму лише вам?", 12 | "options[0].options[2].name": "приватне", 13 | "options[0].options[3].description": "Надсилати лише посилання без вбудовування?", 14 | "options[0].options[3].name": "без вбудовування", 15 | "options[0].options[4].description": "Приховати посилання й вставити в спойлер?", 16 | "options[0].options[4].name": "спойлер", 17 | "options[1].description": "Показати відмінності в редагуванні вікі на основі ідентифікатора.", 18 | "options[1].name": "ids", 19 | "options[1].options[0].description": "Показувати відносну різницю редагувань вікі на основі ідентифікатора.", 20 | "options[1].options[0].name": "відносне" 21 | }, 22 | "overview": { 23 | "name": "огляд", 24 | "options[0].name": "вікі", 25 | "options[1].name": "приватне", 26 | "options[3].name": "спойлер" 27 | }, 28 | "patreon": { 29 | "name": "патрон", 30 | "options[0].name": "перевірка", 31 | "options[1].name": "увімкнути", 32 | "options[1].options[0].name": "гільдія", 33 | "options[2].name": "вимкнути", 34 | "options[2].options[0].name": "гільдія" 35 | }, 36 | "random": { 37 | "name": "випадково", 38 | "options[0].name": "вікі", 39 | "options[2].name": "приватне" 40 | }, 41 | "wiki": { 42 | "options[5].name": "спойлер" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /interactions/overview.js: -------------------------------------------------------------------------------- 1 | import { MessageFlags, PermissionFlagsBits } from 'discord.js'; 2 | import { isMessage, canShowEmbed, allowDelete, sendMessage } from '../util/functions.js'; 3 | import interwiki_interaction from './interwiki.js'; 4 | import wiki_overview from '../cmds/wiki/overview.js'; 5 | 6 | /** 7 | * Post a message with wiki overview. 8 | * @param {import('discord.js').ChatInputCommandInteraction} interaction - The interaction. 9 | * @param {import('../util/i18n.js').default} lang - The user language. 10 | * @param {import('../util/wiki.js').default} wiki - The wiki for the interaction. 11 | */ 12 | function slash_overview(interaction, lang, wiki) { 13 | return interwiki_interaction.FUNCTIONS.getWiki(interaction.options.getString('wiki'), wiki).then( newWiki => { 14 | var flags = ( interaction.options.getBoolean('private') ?? false ) || pausedGuilds.has(interaction.guildId) ? MessageFlags.Ephemeral : undefined; 15 | if ( interaction.wikiWhitelist.length && !interaction.wikiWhitelist.includes( newWiki.href ) ) flags = MessageFlags.Ephemeral; 16 | var noEmbed = interaction.options.getBoolean('noembed') || !canShowEmbed(interaction); 17 | var spoiler = interaction.options.getBoolean('spoiler') ? '||' : ''; 18 | if ( flags ) lang = lang.uselang(interaction.locale); 19 | return interaction.deferReply( {flags} ).then( () => { 20 | return wiki_overview(lang, interaction, newWiki, spoiler, noEmbed).then( result => { 21 | if ( !result || isMessage(result) ) return result; 22 | let noEmoji = !interaction.appPermissions?.has(PermissionFlagsBits.UseExternalEmojis); 23 | if ( result.message ) { 24 | if ( Array.isArray(result.message) ) { 25 | let list = []; 26 | return result.message.slice(1).reduce( (prev, content) => { 27 | return prev.then( message => { 28 | list.push(message); 29 | return interaction.followUp( {content, flags} ).then( msg => { 30 | if ( !msg.flags.has(MessageFlags.Ephemeral) ) allowDelete(msg, interaction.user.id); 31 | return msg; 32 | }, log_error ); 33 | } ); 34 | }, sendMessage(interaction, { 35 | content: result.message[0], 36 | flags 37 | }) ).then( message => { 38 | list.push(message); 39 | return list; 40 | } ); 41 | } 42 | if ( result.reaction === WB_EMOJI.error ) { 43 | if ( typeof result.message === 'string' ) result.message = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.error ) + ' ' + result.message; 44 | else result.message.content = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.error ) + ' ' + ( result.message.content ?? '' ); 45 | } 46 | else if ( result.reaction === WB_EMOJI.warning ) { 47 | if ( typeof result.message === 'string' ) result.message = WB_EMOJI.warning + ' ' + result.message; 48 | else result.message.content = WB_EMOJI.warning + ' ' + ( result.message.content ?? '' ); 49 | } 50 | return sendMessage(interaction, result.message); 51 | } 52 | else if ( result.reaction ) { 53 | let message = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.error ) + ' ' + lang.get('interaction.error') + '\n' + process.env.invite; 54 | if ( result.reaction === WB_EMOJI.nowiki ) message = ( noEmoji ? WB_EMOJI.warning : WB_EMOJI.nowiki ) + ' ' + lang.get('interaction.nowiki'); 55 | if ( result.reaction === WB_EMOJI.shrug ) message = WB_EMOJI.shrug + ' ' + lang.get('search.noresult'); 56 | return sendMessage(interaction, {content: message}); 57 | } 58 | } ); 59 | }, log_error ); 60 | }, () => { 61 | return interaction.reply( { 62 | content: lang.uselang(interaction.locale).get('interaction.interwiki'), 63 | flags: MessageFlags.Ephemeral 64 | } ).catch(log_error); 65 | } ); 66 | } 67 | 68 | export default { 69 | name: 'overview', 70 | slash: slash_overview, 71 | autocomplete: interwiki_interaction.autocomplete, 72 | allowDelete: true 73 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-wiki-bot", 3 | "version": "4.5.0", 4 | "type": "module", 5 | "description": "Wiki-Bot is a bot with the purpose to easily search for and link to wiki pages. Wiki-Bot shows short descriptions and additional info about pages and is able to resolve redirects and follow interwiki links.", 6 | "main": "main.js", 7 | "scripts": { 8 | "build": "cp -n .env.example .env", 9 | "readonly": "node --trace-warnings main.js readonly", 10 | "test": "node --trace-warnings main.js debug", 11 | "start": "node --trace-warnings main.js" 12 | }, 13 | "author": "MarkusRost", 14 | "license": "ISC", 15 | "engines": { 16 | "node": ">=23.1.0" 17 | }, 18 | "dependencies": { 19 | "cheerio": "^1.1.2", 20 | "datetime-difference": "^1.0.2", 21 | "discord-oauth2": "^2.12.1", 22 | "discord.js": "^14.23.0", 23 | "dotenv": "^17.2.3", 24 | "full-icu": "^1.5.0", 25 | "got": "^14.4.9", 26 | "got-ssrf": "^3.0.0", 27 | "htmlparser2": "^10.0.0", 28 | "mediawiki-projects-list": "^4.9.5", 29 | "npm": "^11.4.2", 30 | "pg": "^8.16.3" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/Markus-Rost/discord-wiki-bot.git" 35 | }, 36 | "keywords": [ 37 | "mediawiki", 38 | "fandom", 39 | "gamepedia", 40 | "wikia", 41 | "discord", 42 | "wikibot", 43 | "wiki", 44 | "discord-bot", 45 | "wikipedia" 46 | ], 47 | "bugs": { 48 | "url": "https://github.com/Markus-Rost/discord-wiki-bot/issues" 49 | }, 50 | "homepage": "https://github.com/Markus-Rost/discord-wiki-bot#readme" 51 | } 52 | -------------------------------------------------------------------------------- /util/database.js: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | const db = new pg.Pool(process.env.PGSSL === 'true' ? {ssl: true} : {}); 3 | export default db.on( 'error', dberror => { 4 | console.log( '- ' + process.env.SHARDS + ': Error while connecting to the database: ' + dberror ); 5 | } ); 6 | 7 | db.query( 'SELECT guild, prefix FROM discord WHERE patreon IS NOT NULL' ).then( ({rows}) => { 8 | console.log( '- ' + process.env.SHARDS + ': Patreons successfully loaded.' ); 9 | rows.forEach( row => { 10 | patreonGuildsPrefix.set(row.guild, row.prefix); 11 | } ); 12 | }, dberror => { 13 | console.log( '- ' + process.env.SHARDS + ': Error while getting the patreons: ' + dberror ); 14 | } ); -------------------------------------------------------------------------------- /util/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "limit": { 3 | "command": { 4 | "default": 10, 5 | "patreon": 15 6 | }, 7 | "interwiki": { 8 | "default": 5, 9 | "patreon": 10 10 | }, 11 | "search": { 12 | "default": 10, 13 | "patreon": 25 14 | }, 15 | "discussion": { 16 | "default": 50, 17 | "patreon": 100 18 | }, 19 | "verification": { 20 | "default": 10, 21 | "patreon": 25 22 | }, 23 | "rcgcdw": { 24 | "default": 2, 25 | "patreon": 50, 26 | "display": 2 27 | } 28 | }, 29 | "defaultPermissions": "275817811008", 30 | "defaultSettings": { 31 | "lang": "en", 32 | "wiki": "https://en.wikipedia.org/w/", 33 | "subprefixes": [ 34 | ["!", "wikipedia.org"], 35 | ["?", "fandom.com"] 36 | ], 37 | "embedLimits": { 38 | "descLength": 1000, 39 | "fieldCount": 25, 40 | "fieldLength": 500, 41 | "sectionLength": 2000, 42 | "sectionDescLength": 500 43 | } 44 | }, 45 | "timeoptions": { 46 | "year": "numeric", 47 | "month": "short", 48 | "day": "numeric", 49 | "hour": "2-digit", 50 | "minute": "2-digit", 51 | "timeZoneName": "short" 52 | }, 53 | "usergroups": { 54 | "ignored": [ 55 | "fandom-editor", 56 | "request-to-be-forgotten-admin", 57 | "util", 58 | "content-reviewer", 59 | "imagereviewer", 60 | "notifications-cms-user", 61 | "restricted-login", 62 | "restricted-login-auto", 63 | "restricted-login-exempt", 64 | "council", 65 | "*" 66 | ], 67 | "sorted": [ 68 | "bot", 69 | "suppress", 70 | "checkuser", 71 | "bureaucrat", 72 | "advocate", 73 | "interface-admin", 74 | "sysop", 75 | "wiki_guardian", 76 | "content-moderator", 77 | "widgeteditor", 78 | "threadmoderator", 79 | "reviewer", 80 | "editor", 81 | "__CUSTOM__", 82 | "autoreview", 83 | "autopatrol", 84 | "rollback", 85 | "autoconfirmed", 86 | "emailconfirmed", 87 | "user" 88 | ], 89 | "global": [ 90 | "founder", 91 | "global-bot", 92 | "bot-global", 93 | "flood", 94 | "staff", 95 | "sysadmin", 96 | "steward", 97 | "wiki-representative", 98 | "global-interface-editor", 99 | "helper", 100 | "global-sysop", 101 | "globalsysop", 102 | "wiki-specialist", 103 | "ombuds", 104 | "soap", 105 | "global-discussions-moderator", 106 | "vanguard", 107 | "__CUSTOM__", 108 | "interwiki-admin", 109 | "global-deleter", 110 | "abusefilter-maintainer", 111 | "abusefilter-helper", 112 | "global-rollbacker", 113 | "global-ipblock-exempt", 114 | "fandom-star", 115 | "voldev" 116 | ] 117 | }, 118 | "defaultNamespaces": [ 119 | { 120 | "id": 0, 121 | "name": "", 122 | "aliases": [], 123 | "content": true 124 | }, 125 | { 126 | "id": 1, 127 | "name": "Talk", 128 | "aliases": [], 129 | "content": false 130 | }, 131 | { 132 | "id": 2, 133 | "name": "User", 134 | "aliases": [], 135 | "content": false 136 | }, 137 | { 138 | "id": 3, 139 | "name": "User talk", 140 | "aliases": [], 141 | "content": false 142 | }, 143 | { 144 | "id": 4, 145 | "name": "Project", 146 | "aliases": [], 147 | "content": false 148 | }, 149 | { 150 | "id": 5, 151 | "name": "Project talk", 152 | "aliases": [], 153 | "content": false 154 | }, 155 | { 156 | "id": 6, 157 | "name": "File", 158 | "aliases": [ 159 | "Image" 160 | ], 161 | "content": false 162 | }, 163 | { 164 | "id": 7, 165 | "name": "File talk", 166 | "aliases": [ 167 | "Image talk" 168 | ], 169 | "content": false 170 | }, 171 | { 172 | "id": 8, 173 | "name": "MediaWiki", 174 | "aliases": [], 175 | "content": false 176 | }, 177 | { 178 | "id": 9, 179 | "name": "MediaWiki talk", 180 | "aliases": [], 181 | "content": false 182 | }, 183 | { 184 | "id": 10, 185 | "name": "Template", 186 | "aliases": [], 187 | "content": false 188 | }, 189 | { 190 | "id": 11, 191 | "name": "Template talk", 192 | "aliases": [], 193 | "content": false 194 | }, 195 | { 196 | "id": 12, 197 | "name": "Help", 198 | "aliases": [], 199 | "content": false 200 | }, 201 | { 202 | "id": 13, 203 | "name": "Help talk", 204 | "aliases": [], 205 | "content": false 206 | }, 207 | { 208 | "id": 14, 209 | "name": "Category", 210 | "aliases": [], 211 | "content": false 212 | }, 213 | { 214 | "id": 15, 215 | "name": "Category talk", 216 | "aliases": [], 217 | "content": false 218 | }, 219 | { 220 | "id": -2, 221 | "name": "Media", 222 | "aliases": [], 223 | "content": false 224 | }, 225 | { 226 | "id": -1, 227 | "name": "Special", 228 | "aliases": [], 229 | "content": false 230 | } 231 | ] 232 | } 233 | -------------------------------------------------------------------------------- /util/defaults.js: -------------------------------------------------------------------------------- 1 | import defaultData from './default.json' with { type: 'json' }; 2 | 3 | export const { 4 | limit: botLimits, 5 | defaultPermissions, 6 | defaultSettings, 7 | timeoptions, 8 | usergroups, 9 | defaultNamespaces 10 | } = defaultData; -------------------------------------------------------------------------------- /util/edit_diff.js: -------------------------------------------------------------------------------- 1 | import { Parser as HTMLParser } from 'htmlparser2'; 2 | import { escapeFormatting } from './functions.js'; 3 | 4 | /** 5 | * Change edit diffs to markdown text. 6 | * @param {String} html - The edit diff in HTML. 7 | * @param {Number} sectionLength - The maximal length of the edit diff. 8 | * @param {String} more - The localized string for more content. 9 | * @param {String} whitespace - The localized string for only whitespace. 10 | * @returns {[String, String]} 11 | */ 12 | export default function diffParser(html, sectionLength, more, whitespace) { 13 | if ( !sectionLength ) return ['', '']; 14 | var current_tag = ''; 15 | var last_ins = null; 16 | var last_del = null; 17 | var empty = false; 18 | var small_prev_ins = ''; 19 | var small_prev_del = ''; 20 | var ins_length = more.length; 21 | var del_length = more.length; 22 | var parser = new HTMLParser( { 23 | onopentag: (tagname, attribs) => { 24 | if ( ins_length > sectionLength && del_length > sectionLength ) parser.pause(); // Prevent the parser from running too long 25 | if ( tagname === 'ins' || tagname == 'del' ) current_tag = tagname; 26 | if ( tagname === 'td' ) { 27 | let classes = ( attribs.class?.split(' ') || [] ); 28 | if ( classes.includes( 'diff-addedline' ) && ins_length <= sectionLength ) { 29 | current_tag = 'tda'; 30 | last_ins = ''; 31 | } 32 | if ( classes.includes( 'diff-deletedline' ) && del_length <= sectionLength ) { 33 | current_tag = 'tdd'; 34 | last_del = ''; 35 | } 36 | if ( classes.includes( 'diff-empty' ) ) empty = true; 37 | } 38 | }, 39 | ontext: (htmltext) => { 40 | if ( current_tag === 'ins' && ins_length <= sectionLength ) { 41 | ins_length += ( '**' + escapeFormatting(htmltext) + '**' ).length; 42 | if ( ins_length <= sectionLength ) last_ins += '**' + escapeFormatting(htmltext) + '**'; 43 | } 44 | if ( current_tag === 'del' && del_length <= sectionLength ) { 45 | del_length += ( '~~' + escapeFormatting(htmltext) + '~~' ).length; 46 | if ( del_length <= sectionLength ) last_del += '~~' + escapeFormatting(htmltext) + '~~'; 47 | } 48 | if ( current_tag === 'tda' && ins_length <= sectionLength ) { 49 | ins_length += escapeFormatting(htmltext).length; 50 | if ( ins_length <= sectionLength ) last_ins += escapeFormatting(htmltext); 51 | } 52 | if ( current_tag === 'tdd' && del_length <= sectionLength ) { 53 | del_length += escapeFormatting(htmltext).length; 54 | if ( del_length <= sectionLength ) last_del += escapeFormatting(htmltext); 55 | } 56 | }, 57 | onclosetag: (tagname) => { 58 | if ( tagname === 'ins' ) current_tag = 'tda'; 59 | if ( tagname === 'del' ) current_tag = 'tdd'; 60 | if ( tagname === 'td' ) current_tag = ''; 61 | if ( tagname === 'tr' ) { 62 | if ( last_ins !== null ) { 63 | ins_length++; 64 | if ( empty && last_ins.trim().length ) { 65 | if ( last_ins.includes( '**' ) ) last_ins = last_ins.replaceAll( '**', '__' ); 66 | ins_length += 4; 67 | last_ins = '**' + last_ins + '**'; 68 | } 69 | small_prev_ins += '\n' + last_ins; 70 | if ( ins_length > sectionLength ) small_prev_ins += more; 71 | last_ins = null; 72 | } 73 | if ( last_del !== null ) { 74 | del_length++; 75 | if ( empty && last_del.trim().length ) { 76 | if ( last_del.includes( '~~' ) ) last_del = last_del.replaceAll( '~~', '__' ); 77 | del_length += 4; 78 | last_del = '~~' + last_del + '~~'; 79 | } 80 | small_prev_del += '\n' + last_del; 81 | if ( del_length > sectionLength ) small_prev_del += more; 82 | last_del = null; 83 | } 84 | empty = false; 85 | } 86 | } 87 | } ); 88 | parser.write( html ); 89 | parser.end(); 90 | var compare = ['', '']; 91 | if ( small_prev_del.length ) { 92 | if ( small_prev_del.replace( /\~\~|__/g, '' ).trim().length ) { 93 | compare[0] = small_prev_del.replace( /\~\~\~\~|____/g, '' ); 94 | } else compare[0] = whitespace; 95 | } 96 | if ( small_prev_ins.length ) { 97 | if ( small_prev_ins.replace( /\*\*|__/g, '' ).trim().length ) { 98 | compare[1] = small_prev_ins.replace( /\*\*\*\*|____/g, '' ); 99 | } else compare[1] = whitespace; 100 | } 101 | return compare; 102 | } -------------------------------------------------------------------------------- /util/extract_desc.js: -------------------------------------------------------------------------------- 1 | import { escapeFormatting } from './functions.js'; 2 | 3 | /** 4 | * Get the description for a page. 5 | * @param {String} [text] - The full page extract. 6 | * @param {Object} [embedLimits] - The embed limits. 7 | * @param {Number} [embedLimits.descLength] - The description length. 8 | * @param {Number} [embedLimits.sectionLength] - The section length. 9 | * @param {Number} [embedLimits.sectionDescLength] - The description length with section. 10 | * @param {String} [fragment] - The section title. 11 | * @returns {String} 12 | */ 13 | export default function extract_desc(text = '', {descLength = 1_000, sectionLength = 2_000, sectionDescLength = 500} = {}, fragment = '') { 14 | var sectionIndex = text.indexOf('\ufffd\ufffd'); 15 | var extract = ( descLength ? escapeFormatting(( sectionIndex !== -1 ? text.substring(0, sectionIndex) : text ).trim()) : '' ); 16 | if ( extract.length > descLength ) extract = extract.substring(0, descLength) + '\u2026'; 17 | var section = null; 18 | var regex = /\ufffd{2}(\d)\ufffd{2}([^\n]+)/g; 19 | while ( fragment && sectionLength && ( section = regex.exec(text) ) !== null ) { 20 | if ( section[2].replaceAll( ' ', '_' ) !== fragment.replaceAll( ' ', '_' ) ) continue; 21 | let sectionHeader = section_formatting(escapeFormatting(section[2]), section[1]); 22 | let sectionText = text.substring(regex.lastIndex); 23 | switch ( section[1] ) { 24 | case '6': 25 | sectionIndex = sectionText.indexOf('\ufffd\ufffd6\ufffd\ufffd'); 26 | if ( sectionIndex !== -1 ) sectionText = sectionText.substring(0, sectionIndex); 27 | case '5': 28 | sectionIndex = sectionText.indexOf('\ufffd\ufffd5\ufffd\ufffd'); 29 | if ( sectionIndex !== -1 ) sectionText = sectionText.substring(0, sectionIndex); 30 | case '4': 31 | sectionIndex = sectionText.indexOf('\ufffd\ufffd4\ufffd\ufffd'); 32 | if ( sectionIndex !== -1 ) sectionText = sectionText.substring(0, sectionIndex); 33 | case '3': 34 | sectionIndex = sectionText.indexOf('\ufffd\ufffd3\ufffd\ufffd'); 35 | if ( sectionIndex !== -1 ) sectionText = sectionText.substring(0, sectionIndex); 36 | case '2': 37 | sectionIndex = sectionText.indexOf('\ufffd\ufffd2\ufffd\ufffd'); 38 | if ( sectionIndex !== -1 ) sectionText = sectionText.substring(0, sectionIndex); 39 | case '1': 40 | sectionIndex = sectionText.indexOf('\ufffd\ufffd1\ufffd\ufffd'); 41 | if ( sectionIndex !== -1 ) sectionText = sectionText.substring(0, sectionIndex); 42 | } 43 | sectionText = escapeFormatting(sectionText.trim()).replace( /\ufffd{2}(\d)\ufffd{2}([^\n]+)/g, (match, n, sectionTitle) => { 44 | return section_formatting(sectionTitle, n); 45 | } ); 46 | if ( !sectionDescLength ) extract = ''; 47 | else if ( extract.length > sectionDescLength ) extract = extract.substring(0, sectionDescLength) + '\u2026'; 48 | if ( sectionText.length > sectionLength ) sectionText = sectionText.substring(0, sectionLength) + '\u2026'; 49 | extract += '\n' + sectionHeader + '\n' + sectionText; 50 | if ( extract.length > 4_000 ) extract = extract.substring(0, 4_000) + '\u2026'; 51 | break; 52 | } 53 | return extract; 54 | } 55 | 56 | /** 57 | * Format section title. 58 | * @param {String} title - The section title. 59 | * @param {String} n - The header level. 60 | * @returns {String} 61 | */ 62 | function section_formatting(title, n) { 63 | switch ( n ) { 64 | case '1': 65 | title = '# ' + title; 66 | break; 67 | case '2': 68 | title = '## ' + title; 69 | break; 70 | case '3': 71 | title = '### ' + title; 72 | break; 73 | case '4': 74 | title = '***__' + title + '__***'; 75 | break; 76 | case '5': 77 | title = '**__' + title + '__**'; 78 | break; 79 | case '6': 80 | title = '**' + title + '**'; 81 | break; 82 | } 83 | return title; 84 | } -------------------------------------------------------------------------------- /util/globals.js: -------------------------------------------------------------------------------- 1 | import { inspect } from 'node:util'; 2 | inspect.defaultOptions = {compact: false, breakLength: Infinity, depth: 3}; 3 | 4 | /** 5 | * If debug logging is enabled. 6 | * @type {Boolean} 7 | * @global 8 | */ 9 | globalThis.isDebug = ( process.argv[2] === 'debug' ); 10 | 11 | /** 12 | * Custom emojis. 13 | * @enum {String} 14 | * @global 15 | */ 16 | globalThis.WB_EMOJI = { 17 | /** @type {'<:error:440871715938238494>'} */ 18 | error: '<:error:440871715938238494>', 19 | /** @type {''} */ 20 | loading: '', 21 | /** @type {'<:unknown_wiki:505884572001763348>'} */ 22 | nowiki: '<:unknown_wiki:505884572001763348>', 23 | /** @type {'<:wikibot:1042228093940682842>'} */ 24 | wikibot: '<:wikibot:1042228093940682842>', 25 | /** @type {'🔂'} */ again: '🔂', 26 | /** @type {'🗑️'} */ delete: '🗑️', 27 | /** @type {'✅'} */ done: '✅', 28 | /** @type {'🔗'} */ link: '🔗', 29 | /** @type {'📩'} */ message: '📩', 30 | /** @type {'❌'} */ no: '❌', 31 | /** @type {'❓'} */ question: '❓', 32 | /** @type {'🤷'} */ shrug: '🤷', 33 | /** @type {'⏳'} */ waiting: '⏳', 34 | /** @type {'⚠️'} */ warning: '⚠️' 35 | }; 36 | 37 | /** 38 | * First Strong Isolate (FSI) 39 | * @type {'\u2068'} 40 | * @global 41 | */ 42 | globalThis.FIRST_STRONG_ISOLATE = '\u2068'; 43 | 44 | /** 45 | * Pop Directional Isolate (PDI) 46 | * @type {'\u2069'} 47 | * @global 48 | */ 49 | globalThis.POP_DIRECTIONAL_ISOLATE = '\u2069'; 50 | 51 | /** 52 | * Prefix of guilds with patreon features enabled. 53 | * @type {Map} 54 | * @global 55 | */ 56 | globalThis.patreonGuildsPrefix = new Map(); 57 | 58 | /** 59 | * Guilds with pause activated. 60 | * @type {Set} 61 | * @global 62 | */ 63 | globalThis.pausedGuilds = new Set(); 64 | 65 | /** 66 | * Map of database event listeners 67 | * @type {Map} 68 | */ 69 | globalThis.dbListenerMap = new Map(); 70 | 71 | /** 72 | * Logs an error. 73 | * @param {Error} error - The error. 74 | * @param {Boolean} isBig - If the error should get a big log. 75 | * @param {String} type - Type of the error. 76 | * @global 77 | */ 78 | globalThis.log_error = function(error, isBig = false, type = '') { 79 | var time = new Date(Date.now()).toLocaleTimeString('de-DE', { timeZone: 'Europe/Berlin' }); 80 | if ( isDebug ) { 81 | console.error( '--- ' + type + 'ERROR START ' + time + ' ---\n', error, '\n--- ' + type + 'ERROR END ' + time + ' ---' ); 82 | } else { 83 | if ( isBig ) console.log( '--- ' + type + 'ERROR: ' + time + ' ---\n-', error ); 84 | else console.log( '- ' + error.name + ': ' + error.message ); 85 | } 86 | } 87 | 88 | const common_warnings = { 89 | main: [ 90 | 'Unrecognized parameters: exlimit, explaintext, exsectionformat, piprop.', 91 | 'Unrecognized parameters: piprop, explaintext, exsectionformat, exlimit.', 92 | 'Unrecognized parameters: explaintext, exsectionformat, exlimit.', 93 | 'Unrecognized parameters: exlimit, explaintext, exsectionformat.', 94 | 'Unrecognized parameter: piprop.', 95 | 'Unrecognized parameter: rvslots.' 96 | ], 97 | query: [ 98 | 'Unrecognized values for parameter "prop": pageimages, extracts.', 99 | 'Unrecognized values for parameter "prop": pageimages, extracts', 100 | 'Unrecognized value for parameter "prop": extracts.', 101 | 'Unrecognized value for parameter "prop": extracts', 102 | 'Unrecognized value for parameter "prop": pageimages.', 103 | 'Unrecognized value for parameter "prop": pageimages' 104 | ], 105 | extracts: [ 106 | 'Extract for a title in File namespace was requested, none returned.' 107 | ] 108 | } 109 | 110 | /** 111 | * Logs a warning. 112 | * @param {Object} warning - The warning. 113 | * @param {Boolean} api - If the warning is from the MediaWiki API. 114 | * @global 115 | */ 116 | globalThis.log_warning = function(warning, api = true) { 117 | if ( isDebug ) { 118 | console.warn( '--- Warning Start ---\n' + inspect( warning ) + '\n--- Warning End ---' ); 119 | } 120 | else if ( api ) { 121 | if ( common_warnings.main.includes( warning?.main?.['*'] ) ) delete warning.main; 122 | if ( common_warnings.query.includes( warning?.query?.['*'] ) ) delete warning.query; 123 | if ( common_warnings.extracts.includes( warning?.extracts?.['*'] ) ) delete warning.extracts; 124 | var warningKeys = Object.keys(warning); 125 | if ( warningKeys.length ) console.warn( '- Warning: ' + warningKeys.join(', ') ); 126 | } 127 | else console.warn( '--- Warning ---\n' + inspect( warning ) ); 128 | } 129 | 130 | if ( !globalThis.verifyOauthUser ) { 131 | /** 132 | * Oauth wiki user verification. 133 | * @param {String} state - Unique state for the authorization. 134 | * @param {String} access_token - Access token. 135 | * @param {Object} [settings] - Settings to skip oauth. 136 | * @param {import('discord.js').TextChannel} settings.channel - The channel. 137 | * @param {String} settings.user - The user id. 138 | * @param {String} settings.wiki - The OAuth2 wiki. 139 | * @param {import('discord.js').ChatInputCommandInteraction|import('discord.js').ButtonInteraction} [settings.interaction] - The interaction. 140 | * @param {Function} [settings.fail] - The function to call when the verifiction errors. 141 | * @param {import('discord.js').Message} [settings.sourceMessage] - The source message with the command. 142 | * @global 143 | */ 144 | globalThis.verifyOauthUser = function(state, access_token, settings) { 145 | return settings?.fail?.(); 146 | }; 147 | } -------------------------------------------------------------------------------- /util/logging.js: -------------------------------------------------------------------------------- 1 | import { createWriteStream } from 'node:fs'; 2 | 3 | /** 4 | * Log wikis by usage. 5 | * @param {import('./wiki.js').default} wiki - The wiki. 6 | * @param {String} [guild] - The guild. 7 | * @param {String[]} [notes] - The notes about the usage. 8 | */ 9 | var logging = function(wiki, guild, ...notes) {}; 10 | 11 | if ( process.env.usagelog ) { 12 | const usageLog = createWriteStream(process.env.usagelog, {flags:'a'}); 13 | 14 | usageLog.on( 'error', (error) => { 15 | console.log( '- ' + process.env.SHARDS + ': Error while logging the usage: ' + error ); 16 | } ); 17 | 18 | /** 19 | * Log wikis by usage. 20 | * @param {import('./wiki.js').default} wiki - The wiki. 21 | * @param {String} [guild] - The guild. 22 | * @param {String[]} [notes] - The notes about the usage. 23 | */ 24 | logging = function(wiki, guild, ...notes) { 25 | usageLog.write( [new Date().toISOString(), wiki, ( guild || 'DM' ), ...notes].join('\t') + '\n', 'utf8' ); 26 | }; 27 | } 28 | 29 | export default logging; --------------------------------------------------------------------------------