Add Cogs
This commit is contained in:
parent
787a367d13
commit
676bcc8b10
170 changed files with 141166 additions and 0 deletions
64
dictionary/README.rst
Normal file
64
dictionary/README.rst
Normal file
|
@ -0,0 +1,64 @@
|
|||
.. _dictionary:
|
||||
==========
|
||||
Dictionary
|
||||
==========
|
||||
|
||||
This is the cog guide for the ``Dictionary`` cog. This guide contains the collection of commands which you can use in the cog.
|
||||
Through this guide, ``[p]`` will always represent your prefix. Replace ``[p]`` with your own prefix when you use these commands in Discord.
|
||||
|
||||
.. note::
|
||||
|
||||
Ensure that you are up to date by running ``[p]cog update dictionary``.
|
||||
If there is something missing, or something that needs improving in this documentation, feel free to create an issue `here <https://github.com/AAA3A-AAA3A/AAA3A-cogs/issues>`_.
|
||||
This documentation is generated everytime this cog receives an update.
|
||||
|
||||
---------------
|
||||
About this cog:
|
||||
---------------
|
||||
|
||||
A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)...
|
||||
|
||||
---------
|
||||
Commands:
|
||||
---------
|
||||
|
||||
Here are all the commands included in this cog (1):
|
||||
|
||||
* ``[p]dictionary <query>``
|
||||
Search a word in the english dictionnary.
|
||||
|
||||
------------
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you haven't added my repo before, lets add it first. We'll call it "AAA3A-cogs" here.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]repo add AAA3A-cogs https://github.com/AAA3A-AAA3A/AAA3A-cogs
|
||||
|
||||
Now, we can install Dictionary.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]cog install AAA3A-cogs dictionary
|
||||
|
||||
Once it's installed, it is not loaded by default. Load it by running the following command:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]load dictionary
|
||||
|
||||
----------------
|
||||
Further Support:
|
||||
----------------
|
||||
|
||||
Check out my docs `here <https://aaa3a-cogs.readthedocs.io/en/latest/>`_.
|
||||
Mention me in the #support_other-cogs in the `cog support server <https://discord.gg/GET4DVk>`_ if you need any help.
|
||||
Additionally, feel free to open an issue or pull request to this repo.
|
||||
|
||||
--------
|
||||
Credits:
|
||||
--------
|
||||
|
||||
Thanks to Kreusada for the Python code to automatically generate this documentation!
|
32
dictionary/locales/de-DE.po
Normal file
32
dictionary/locales/de-DE.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: de_DE\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Ein Zahnrad, um einen englischen Begriff/Wort im Wörterbuch zu suchen! Synonyme, Antonyme, Phonetik (mit Audio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Wort nicht im englischen Wörterbuch gefunden."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Quelle anzeigen"
|
||||
|
32
dictionary/locales/el-GR.po
Normal file
32
dictionary/locales/el-GR.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Greek\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: el\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: el_GR\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Ένα γρανάζι για την αναζήτηση ενός αγγλικού όρου/λέξης στο λεξικό! Συνώνυμα, αντώνυμα, φωνητική (με ήχο)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Λέξη που δεν υπάρχει στο αγγλικό λεξικό."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Δείτε την πηγή"
|
||||
|
32
dictionary/locales/es-ES.po
Normal file
32
dictionary/locales/es-ES.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: es-ES\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: es_ES\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Un engranaje para buscar un término/palabra en inglés en el diccionario. Sinónimos, antónimos, fonética (con audio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Palabra no encontrada en el diccionario inglés."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Ver la fuente"
|
||||
|
32
dictionary/locales/fi-FI.po
Normal file
32
dictionary/locales/fi-FI.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Finnish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: fi\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: fi_FI\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Hammasratas, jolla voi etsiä englanninkielistä termiä/sanaa sanakirjasta! Synonyymit, antonyymit, fonetiikka (äänen kanssa)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Sanaa ei löydy englannin sanakirjasta."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Katso lähde"
|
||||
|
32
dictionary/locales/fr-FR.po
Normal file
32
dictionary/locales/fr-FR.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: fr\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: fr_FR\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Un rouage pour rechercher un terme/mot anglais dans le dictionnaire ! Synonymes, antonymes, phonétique (avec audio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Mot non trouvé dans le dictionnaire anglais."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Voir la source"
|
||||
|
32
dictionary/locales/it-IT.po
Normal file
32
dictionary/locales/it-IT.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: it\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: it_IT\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Un ingranaggio per cercare un termine/parola inglese nel dizionario! Sinonimi, contrari, fonetica (con audio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Parola non trovata nel dizionario inglese."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Visualizza la fonte"
|
||||
|
32
dictionary/locales/ja-JP.po
Normal file
32
dictionary/locales/ja-JP.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ja\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: ja_JP\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "辞書で英単語を検索するための歯車です!類義語、反意語、音声(音声付き)...。"
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "英語の辞書に載っていない単語。"
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "ソースを見る"
|
||||
|
31
dictionary/locales/messages.pot
Normal file
31
dictionary/locales/messages.pot
Normal file
|
@ -0,0 +1,31 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2025-03-15 23:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid ""
|
||||
"A cog to search an english term/word in the dictionary! Synonyms, antonyms, "
|
||||
"phonetics (with audio)..."
|
||||
msgstr ""
|
||||
|
||||
#: dictionary\dictionary.py:87
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr ""
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr ""
|
||||
|
||||
#: dictionary\view.py:46
|
||||
msgid "You are not allowed to use this interaction."
|
||||
msgstr ""
|
32
dictionary/locales/nl-NL.po
Normal file
32
dictionary/locales/nl-NL.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: nl\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: nl_NL\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Een radertje om een Engelse term/woord te zoeken in het woordenboek! Synoniemen, antoniemen, fonetiek (met audio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Woord niet gevonden in Engels woordenboek."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Bekijk de bron"
|
||||
|
32
dictionary/locales/pl-PL.po
Normal file
32
dictionary/locales/pl-PL.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pl\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: pl_PL\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Trybik do wyszukiwania angielskiego terminu/słowa w słowniku! Synonimy, antonimy, fonetyka (z dźwiękiem)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Słowo nie występujące w słowniku języka angielskiego."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Zobacz źródło"
|
||||
|
32
dictionary/locales/pt-BR.po
Normal file
32
dictionary/locales/pt-BR.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese, Brazilian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pt-BR\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: pt_BR\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Uma engrenagem para procurar um termo/palavra inglesa no dicionário! Sinónimos, antónimos, fonética (com áudio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Palavra não encontrada no dicionário de inglês."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Ver a fonte"
|
||||
|
32
dictionary/locales/pt-PT.po
Normal file
32
dictionary/locales/pt-PT.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pt-PT\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: pt_PT\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Uma engrenagem para procurar um termo/palavra inglesa no dicionário! Sinónimos, antónimos, fonética (com áudio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Palavra não encontrada no dicionário de inglês."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Ver a fonte"
|
||||
|
32
dictionary/locales/ro-RO.po
Normal file
32
dictionary/locales/ro-RO.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Romanian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ro\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: ro_RO\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "O rotiță pentru a căuta un termen/cuvânt englezesc în dicționar! Sinonime, antonime, fonetică (cu audio)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Cuvânt care nu se găsește în dicționarul englez."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Vezi sursa"
|
||||
|
32
dictionary/locales/ru-RU.po
Normal file
32
dictionary/locales/ru-RU.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ru\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: ru_RU\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Коготь для поиска английского термина/слова в словаре! Синонимы, антонимы, фонетика (с аудио)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Слово не найдено в английском словаре."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Просмотреть источник"
|
||||
|
32
dictionary/locales/tr-TR.po
Normal file
32
dictionary/locales/tr-TR.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-21 13:27\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: tr\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: tr_TR\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "İngilizce bir terimi/kelimeyi sözlükte aramak için bir cog! Eşanlamlılar, zıt anlamlılar, fonetik (sesli)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Kelime İngilizce sözlükte bulunamadı."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Kaynağı görüntüle"
|
||||
|
32
dictionary/locales/uk-UA.po
Normal file
32
dictionary/locales/uk-UA.po
Normal file
|
@ -0,0 +1,32 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Ukrainian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: uk\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dictionary/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 229\n"
|
||||
"Language: uk_UA\n"
|
||||
|
||||
#: dictionary\dictionary.py:23
|
||||
#, docstring
|
||||
msgid "A cog to search an english term/word in the dictionary! Synonyms, antonyms, phonetics (with audio)..."
|
||||
msgstr "Гвинтик для пошуку англійського терміну/слова у словнику! Синоніми, антоніми, фонетика (з аудіо)..."
|
||||
|
||||
#: dictionary\dictionary.py:86
|
||||
msgid "Word not found in English dictionary."
|
||||
msgstr "Слово не знайдено в англійському словнику."
|
||||
|
||||
#: dictionary\view.py:33
|
||||
msgid "View the source"
|
||||
msgstr "Переглянути джерело"
|
||||
|
60
dictionary/types.py
Normal file
60
dictionary/types.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
import discord # isort:skip
|
||||
import typing # isort:skip
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from redbot.core.utils.chat_formatting import humanize_list
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Word:
|
||||
url: str
|
||||
source_url: str
|
||||
word: str
|
||||
phonetics: typing.List[typing.Dict[str, typing.Optional[str]]]
|
||||
meanings: typing.Dict[
|
||||
str,
|
||||
typing.Dict[
|
||||
str,
|
||||
typing.List[typing.Dict[str, typing.Optional[typing.Union[str, typing.List[str]]]]],
|
||||
],
|
||||
]
|
||||
|
||||
def to_json(self) -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
v: getattr(self, v)
|
||||
for v in dir(self)
|
||||
if not v.startswith("_") and v != "to_json" and v != "to_embed"
|
||||
}
|
||||
|
||||
def to_embed(self, embed_color: discord.Color = discord.Color.green()) -> discord.Embed:
|
||||
embed: discord.Embed = discord.Embed(
|
||||
title=f'Dictionary - "{self.word}"', url=self.source_url, color=embed_color
|
||||
)
|
||||
for meaning in self.meanings:
|
||||
if len(embed.fields) >= 10:
|
||||
break
|
||||
value = "\n\n".join(
|
||||
[
|
||||
(f"**{n}.** " if len(self.meanings[meaning]) > 1 else "")
|
||||
+ f"{definition['definition']}"
|
||||
+ (
|
||||
f"\n- Synonyms: {humanize_list(definition['synonyms'])}"
|
||||
if definition["synonyms"]
|
||||
else ""
|
||||
)
|
||||
+ (
|
||||
f"\n- Antonyms: {humanize_list(definition['antonyms'])}"
|
||||
if definition["antonyms"]
|
||||
else ""
|
||||
)
|
||||
+ (f"\n> Example: {definition['example']}" if definition["example"] else "")
|
||||
for n, definition in enumerate(self.meanings[meaning], start=1)
|
||||
]
|
||||
)
|
||||
embed.add_field(
|
||||
name=meaning.capitalize(),
|
||||
value=value if len(value) <= 1024 else f"{value[:1020]}\n...",
|
||||
inline=False,
|
||||
)
|
||||
return embed
|
1
dictionary/utils_version.json
Normal file
1
dictionary/utils_version.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"needed_utils_version": 7.0}
|
120
dictionary/view.py
Normal file
120
dictionary/view.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
from AAA3A_utils import Menu, CogsUtils # isort:skip
|
||||
from redbot.core import commands # isort:skip
|
||||
from redbot.core.i18n import Translator # isort:skip
|
||||
import discord # isort:skip
|
||||
|
||||
import asyncio
|
||||
from io import BytesIO
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .types import Word
|
||||
|
||||
_: Translator = Translator("Dictionary", __file__)
|
||||
|
||||
|
||||
class DictionaryView(discord.ui.View):
|
||||
def __init__(self, cog: commands.Cog, word: Word) -> None:
|
||||
super().__init__(timeout=180)
|
||||
self.cog: commands.Cog = cog
|
||||
self.ctx: commands.Context = None
|
||||
|
||||
self.word: Word = word
|
||||
|
||||
self._message: discord.Message = None
|
||||
self._ready: asyncio.Event = asyncio.Event()
|
||||
|
||||
async def start(self, ctx: commands.Context) -> discord.Message:
|
||||
self.ctx: commands.Context = ctx
|
||||
embed = self.word.to_embed(embed_color=await ctx.embed_color())
|
||||
if self.word.source_url:
|
||||
self.add_item(
|
||||
discord.ui.Button(
|
||||
label=_("View the source"),
|
||||
url=self.word.source_url,
|
||||
style=discord.ButtonStyle.url,
|
||||
)
|
||||
)
|
||||
self._message: discord.Message = await self.ctx.send(embed=embed, view=self)
|
||||
self.cog.views[self._message] = self
|
||||
await self._ready.wait()
|
||||
return self._message
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
if interaction.user.id not in [self.ctx.author.id] + list(self.ctx.bot.owner_ids):
|
||||
await interaction.response.send_message(
|
||||
_("You are not allowed to use this interaction."), ephemeral=True
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def on_timeout(self) -> None:
|
||||
for child in self.children:
|
||||
child: discord.ui.Item
|
||||
if hasattr(child, "disabled") and not (
|
||||
isinstance(child, discord.ui.Button) and child.style == discord.ButtonStyle.url
|
||||
):
|
||||
child.disabled = True
|
||||
try:
|
||||
await self._message.edit(view=self)
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
self._ready.set()
|
||||
|
||||
@discord.ui.button(style=discord.ButtonStyle.danger, emoji="✖️", custom_id="close_page")
|
||||
async def close_page(
|
||||
self, interaction: discord.Interaction, button: discord.ui.Button
|
||||
) -> None:
|
||||
try:
|
||||
await interaction.response.defer()
|
||||
except discord.errors.NotFound:
|
||||
pass
|
||||
self.stop()
|
||||
await CogsUtils.delete_message(self._message)
|
||||
self._ready.set()
|
||||
|
||||
@discord.ui.button(
|
||||
label="Phonetics", custom_id="show_phonetics", style=discord.ButtonStyle.secondary
|
||||
)
|
||||
async def show_phonetics(
|
||||
self, interaction: discord.Interaction, button: discord.ui.Button
|
||||
) -> None:
|
||||
await interaction.response.defer()
|
||||
embed: discord.Embed = discord.Embed(title="Phonetics", color=await self.ctx.embed_color())
|
||||
embed.description = "\n".join(
|
||||
[
|
||||
(
|
||||
(
|
||||
f"**•** [**`{phonetic['text'] or 'Name not provided'}`**]({phonetic['audio_url']})"
|
||||
+ (
|
||||
f" ({phonetic['audio_url'].split('/')[-1]})"
|
||||
if phonetic["audio_url"]
|
||||
else ""
|
||||
)
|
||||
)
|
||||
if phonetic["audio_url"]
|
||||
else f"**•** **`{phonetic['text']}`**"
|
||||
)
|
||||
for phonetic in self.word.phonetics
|
||||
]
|
||||
)
|
||||
files = []
|
||||
if self.ctx.bot_permissions.attach_files:
|
||||
for phonetic in self.word.phonetics:
|
||||
if phonetic["audio_url"] is None:
|
||||
continue
|
||||
if phonetic["audio_file"] is not None:
|
||||
files.append(phonetic["audio_file"])
|
||||
continue
|
||||
try:
|
||||
async with self.cog._session.get(
|
||||
phonetic["audio_url"], raise_for_status=True
|
||||
) as r:
|
||||
file = discord.File(
|
||||
BytesIO(await r.read()), filename=phonetic["audio_url"].split("/")[-1]
|
||||
)
|
||||
except (aiohttp.InvalidURL, aiohttp.ClientResponseError):
|
||||
continue
|
||||
phonetic["audio_file"] = file
|
||||
files.append(file)
|
||||
await Menu(pages=[{"embed": embed, "files": files}]).start(self.ctx)
|
37
fakemod/__init__.py
Normal file
37
fakemod/__init__.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-present Kuro-Rui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from redbot.core.bot import Red
|
||||
|
||||
from .fakemod import FakeMod
|
||||
|
||||
with open(Path(__file__).parent / "info.json") as fp:
|
||||
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
|
||||
|
||||
|
||||
async def setup(bot: Red):
|
||||
await bot.add_cog(FakeMod(bot))
|
34
fakemod/converters.py
Normal file
34
fakemod/converters.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-present Kuro-Rui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from redbot.core.commands import BadArgument, Context, Converter
|
||||
|
||||
|
||||
class Action(Converter):
|
||||
async def convert(self, ctx: Context, argument):
|
||||
if argument.lower() not in ["warn", "mute", "kick", "ban"]:
|
||||
raise BadArgument(
|
||||
"I can't find that action. You can choose either `warn`, `mute`, `kick`, and `ban`."
|
||||
)
|
||||
return argument.lower()
|
239
fakemod/fakemod.py
Normal file
239
fakemod/fakemod.py
Normal file
|
@ -0,0 +1,239 @@
|
|||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-present Kuro-Rui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Literal, Optional
|
||||
|
||||
import discord
|
||||
import kuroutils
|
||||
from kuroutils.converters import Emoji
|
||||
from redbot.core import Config, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.utils.predicates import MessagePredicate
|
||||
|
||||
from .converters import Action
|
||||
|
||||
|
||||
# Inspired by Jeff (https://github.com/Noa-DiscordBot/Noa-Cogs/blob/main/fakemod/fakemod.py)
|
||||
class FakeMod(kuroutils.Cog):
|
||||
"""Fake moderation tools to prank your friends!"""
|
||||
|
||||
__author__ = ["Kuro"]
|
||||
__version__ = "0.1.1"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
super().__init__(bot)
|
||||
self._config = Config.get_conf(self, 9863948134, True)
|
||||
self._config.register_guild(
|
||||
channel=None,
|
||||
case_id=1,
|
||||
warn_emoji="\N{HEAVY HEART EXCLAMATION MARK ORNAMENT}\N{VARIATION SELECTOR-16}",
|
||||
mute_emoji="\N{FACE WITH FINGER COVERING CLOSED LIPS}",
|
||||
kick_emoji="\N{HIGH-HEELED SHOE}",
|
||||
ban_emoji="\N{COLLISION SYMBOL}",
|
||||
)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
@commands.group()
|
||||
async def fakemodlogset(self, ctx: commands.Context):
|
||||
"""Manage fake modlog settings."""
|
||||
pass
|
||||
|
||||
@fakemodlogset.command(aliases=["channel"])
|
||||
async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
||||
"""
|
||||
Set a channel as the fake modlog.
|
||||
|
||||
Omit [channel] to disable the fake modlog.
|
||||
"""
|
||||
if channel:
|
||||
if ctx.channel.permissions_for(channel.guild.me).send_messages:
|
||||
await self._config.guild(ctx.guild).channel.set(channel.id)
|
||||
await ctx.send(f"Fake mod events will be sent to {channel.mention}.")
|
||||
else:
|
||||
await ctx.send("Please grant me permission to send message in that channel first.")
|
||||
else:
|
||||
await self._config.guild(ctx.guild).channel.clear()
|
||||
await ctx.send("Fake mod log deactivated.")
|
||||
|
||||
@fakemodlogset.command()
|
||||
async def emoji(self, ctx: commands.Context, action: Action = None, emoji: Emoji = None):
|
||||
"""
|
||||
Set an emoji for a fake mod action.
|
||||
|
||||
The `action` should be either `warn`, `mute`, `kick`, or `ban`.
|
||||
"""
|
||||
|
||||
config = await self._config.guild(ctx.guild).all()
|
||||
if not action:
|
||||
await ctx.send(
|
||||
(
|
||||
f"**__Current Settings__:**\n"
|
||||
f"`Fake Warn :` {config['warn_emoji']}\n"
|
||||
f"`Fake Mute :` {config['mute_emoji']}\n"
|
||||
f"`Fake Kick :` {config['kick_emoji']}\n"
|
||||
f"`Fake Ban :` {config['ban_emoji']}"
|
||||
)
|
||||
)
|
||||
return
|
||||
async with self._config.guild(ctx.guild).all() as guild_settings:
|
||||
guild_settings[f"{action}_emoji"] = str(emoji) if emoji else None
|
||||
if not emoji:
|
||||
await ctx.send(f"The emoji for `fake {action}` has been reset.")
|
||||
return
|
||||
await ctx.send(f"Emoji for `fake {action}` has been set to {emoji}.")
|
||||
|
||||
@fakemodlogset.command()
|
||||
async def resetcases(self, ctx: commands.Context):
|
||||
"""Reset all fake modlog cases in this server."""
|
||||
await ctx.send("Would you like to reset all fake modlog cases in this server? (yes/no)")
|
||||
try:
|
||||
check = MessagePredicate.yes_or_no(ctx, user=ctx.author)
|
||||
await ctx.bot.wait_for("message", check=check, timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("You took too long to respond.")
|
||||
return
|
||||
if check.result:
|
||||
await self._config.guild(ctx.guild).case_id.set(1)
|
||||
await ctx.send("Cases have been reset.")
|
||||
else:
|
||||
await ctx.send("No changes have been made.")
|
||||
|
||||
async def send_fake_modlog(
|
||||
self,
|
||||
guild: discord.Guild,
|
||||
action: Literal["warn", "unwarn", "mute", "unmute", "kick", "ban", "unban"],
|
||||
member: discord.Member,
|
||||
moderator: discord.Member,
|
||||
reason: Optional[str],
|
||||
):
|
||||
config = await self._config.guild(guild).all()
|
||||
if config["channel"] and (fake_modlog := self.bot.get_channel(config["channel"])):
|
||||
case_id: int = config["case_id"]
|
||||
await self._config.guild(guild).case_id.set(case_id + 1)
|
||||
action = action.replace("un", "")
|
||||
emoji = config[f"{action}_emoji"]
|
||||
reason = reason or "Not provided."
|
||||
embed = discord.Embed(
|
||||
title=f"Case #{case_id} | {action.capitalize()} {emoji}",
|
||||
description=f"**Reason:** {reason}",
|
||||
timestamp=discord.utils.utcnow(),
|
||||
)
|
||||
embed.set_author(name=f"{member} ({member.id})")
|
||||
embed.add_field(name="Moderator", value=f"{moderator} ({moderator.id})")
|
||||
embed.set_footer(text="just kidding lol")
|
||||
await fake_modlog.send(embed=embed)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command(name="worn")
|
||||
async def fake_warn(
|
||||
self, ctx: commands.Context, member: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Fake warn a member for the specified reason."""
|
||||
if member == ctx.me:
|
||||
await ctx.send("You can't warn me.")
|
||||
return
|
||||
if member == ctx.author:
|
||||
await ctx.send("You can't warn yourself.")
|
||||
return
|
||||
await ctx.send(f"**{member}** has been warned.")
|
||||
await self.send_fake_modlog(ctx.guild, "warn", member, ctx.author, reason)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command(name="unworn")
|
||||
async def fake_unwarn(
|
||||
self, ctx: commands.Context, member: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Fake unwarn a member for the specified reason."""
|
||||
if member == ctx.author:
|
||||
await ctx.send("You can't unwarn yourself.")
|
||||
return
|
||||
await ctx.send(f"**{member}** has been unwarned.")
|
||||
await self.send_fake_modlog(ctx.guild, "unwarn", member, ctx.author, reason)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command(name="myut", aliases=["moot"])
|
||||
async def fake_mute(
|
||||
self, ctx: commands.Context, member: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Fake mute a member."""
|
||||
if member == ctx.me:
|
||||
await ctx.send("You can't mute me.")
|
||||
return
|
||||
if member == ctx.author:
|
||||
await ctx.send("You can't mute yourself.")
|
||||
return
|
||||
await ctx.send(f"**{member}** has been muted in this server.")
|
||||
await self.send_fake_modlog(ctx.guild, "mute", member, ctx.author, reason)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command(name="unmyut", aliases=["unmoot"])
|
||||
async def fake_unmute(
|
||||
self, ctx: commands.Context, member: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Fake unmute a member."""
|
||||
if member == ctx.author:
|
||||
await ctx.send("You can't unmute yourself.")
|
||||
return
|
||||
await ctx.send(f"**{member}** has been unmuted in this server.")
|
||||
await self.send_fake_modlog(ctx.guild, "unmute", member, ctx.author, reason)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command(name="kik", aliases=["kek", "keck"])
|
||||
async def fake_kick(
|
||||
self, ctx: commands.Context, member: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Fake kick a member."""
|
||||
if member == ctx.me:
|
||||
await ctx.send("You can't kick me.")
|
||||
return
|
||||
if member == ctx.author:
|
||||
await ctx.send("You can't kick yourself.")
|
||||
return
|
||||
await ctx.send(f"**{member}** has been kicked from the server.")
|
||||
await self.send_fake_modlog(ctx.guild, "kick", member, ctx.author, reason)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command(name="ben", aliases=["bam", "bon", "beam", "bean"])
|
||||
async def fake_ban(self, ctx: commands.Context, user: discord.User, *, reason: str = None):
|
||||
"""Fake ban a user."""
|
||||
if user == ctx.me:
|
||||
await ctx.send("You can't ban me.")
|
||||
return
|
||||
if user == ctx.author:
|
||||
await ctx.send("You can't ban yourself.")
|
||||
return
|
||||
await ctx.send(f"**{user}** has been banned from the server.")
|
||||
await self.send_fake_modlog(ctx.guild, "ban", user, ctx.author, reason)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command(name="unben", aliases=["unbam", "unbon", "unbeam", "unbean"])
|
||||
async def fake_unban(self, ctx: commands.Context, user: discord.User, *, reason: str = None):
|
||||
"""Fake unban a user."""
|
||||
if user == ctx.author:
|
||||
await ctx.send("You can't unban yourself.")
|
||||
return
|
||||
await ctx.send(f"**{user}** has been unbanned from the server.")
|
||||
await self.send_fake_modlog(ctx.guild, "unban", user, ctx.author, reason)
|
14
fakemod/info.json
Normal file
14
fakemod/info.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"author": ["Kuro"],
|
||||
"description": "Fake moderation commands for fun!",
|
||||
"disabled": false,
|
||||
"end_user_data_statement": "This cog does not store any end user data.",
|
||||
"hidden": false,
|
||||
"install_msg": "Thanks for installing `FakeMod`! Get started with `[p]help FakeMod`.\nThis cog has docs! Check it out at <https://kuro-cogs.readthedocs.io/en/latest/cogs/fakemod.html>.",
|
||||
"min_bot_version": "3.5.0",
|
||||
"name": "FakeMod",
|
||||
"requirements": ["git+https://github.com/Kuro-Rui/Kuro-Utils"],
|
||||
"short": "Fake moderation commands.",
|
||||
"tags": ["ben", "fake", "fakemod", "kik", "myut", "worn"],
|
||||
"type": "COG"
|
||||
}
|
82
ip/README.rst
Normal file
82
ip/README.rst
Normal file
|
@ -0,0 +1,82 @@
|
|||
.. _ip:
|
||||
==
|
||||
Ip
|
||||
==
|
||||
|
||||
This is the cog guide for the ``Ip`` cog. This guide contains the collection of commands which you can use in the cog.
|
||||
Through this guide, ``[p]`` will always represent your prefix. Replace ``[p]`` with your own prefix when you use these commands in Discord.
|
||||
|
||||
.. note::
|
||||
|
||||
Ensure that you are up to date by running ``[p]cog update ip``.
|
||||
If there is something missing, or something that needs improving in this documentation, feel free to create an issue `here <https://github.com/AAA3A-AAA3A/AAA3A-cogs/issues>`_.
|
||||
This documentation is generated everytime this cog receives an update.
|
||||
|
||||
---------------
|
||||
About this cog:
|
||||
---------------
|
||||
|
||||
A cog to get the ip address of the bot's host machine!
|
||||
|
||||
---------
|
||||
Commands:
|
||||
---------
|
||||
|
||||
Here are all the commands included in this cog (7):
|
||||
|
||||
* ``[p]ip``
|
||||
Commands group for Ip.
|
||||
|
||||
* ``[p]ip ip``
|
||||
Get the ip address of the bot's host machine.
|
||||
|
||||
* ``[p]ip modalconfig [confirmation=False]``
|
||||
Set all settings for the cog with a Discord Modal.
|
||||
|
||||
* ``[p]ip port <port>``
|
||||
Set the port.
|
||||
|
||||
* ``[p]ip resetsetting <setting>``
|
||||
Reset a setting.
|
||||
|
||||
* ``[p]ip showsettings [with_dev=False]``
|
||||
Show all settings for the cog with defaults and values.
|
||||
|
||||
* ``[p]ip website``
|
||||
Get the ip address website of the bot's host machine.
|
||||
|
||||
------------
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you haven't added my repo before, lets add it first. We'll call it "AAA3A-cogs" here.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]repo add AAA3A-cogs https://github.com/AAA3A-AAA3A/AAA3A-cogs
|
||||
|
||||
Now, we can install Ip.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]cog install AAA3A-cogs ip
|
||||
|
||||
Once it's installed, it is not loaded by default. Load it by running the following command:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]load ip
|
||||
|
||||
----------------
|
||||
Further Support:
|
||||
----------------
|
||||
|
||||
Check out my docs `here <https://aaa3a-cogs.readthedocs.io/en/latest/>`_.
|
||||
Mention me in the #support_other-cogs in the `cog support server <https://discord.gg/GET4DVk>`_ if you need any help.
|
||||
Additionally, feel free to open an issue or pull request to this repo.
|
||||
|
||||
--------
|
||||
Credits:
|
||||
--------
|
||||
|
||||
Thanks to Kreusada for the Python code to automatically generate this documentation!
|
46
ip/__init__.py
Normal file
46
ip/__init__.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from redbot.core import errors # isort:skip
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
try:
|
||||
import AAA3A_utils
|
||||
except ModuleNotFoundError:
|
||||
raise errors.CogLoadError(
|
||||
"The needed utils to run the cog were not found. Please execute the command `[p]pipinstall git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
|
||||
)
|
||||
modules = sorted(
|
||||
[module for module in sys.modules if module.split(".")[0] == "AAA3A_utils"], reverse=True
|
||||
)
|
||||
for module in modules:
|
||||
try:
|
||||
importlib.reload(sys.modules[module])
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
del AAA3A_utils
|
||||
# import AAA3A_utils
|
||||
# import json
|
||||
# import os
|
||||
# __version__ = AAA3A_utils.__version__
|
||||
# with open(os.path.join(os.path.dirname(__file__), "utils_version.json"), mode="r") as f:
|
||||
# data = json.load(f)
|
||||
# needed_utils_version = data["needed_utils_version"]
|
||||
# if __version__ > needed_utils_version:
|
||||
# raise errors.CogLoadError(
|
||||
# "The needed utils to run the cog has a higher version than the one supported by this version of the cog. Please update the cogs of the `AAA3A-cogs` repo."
|
||||
# )
|
||||
# elif __version__ < needed_utils_version:
|
||||
# raise errors.CogLoadError(
|
||||
# "The needed utils to run the cog has a lower version than the one supported by this version of the cog. Please execute the command `[p]pipinstall --upgrade git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
|
||||
# )
|
||||
|
||||
from redbot.core.bot import Red # isort:skip
|
||||
from redbot.core.utils import get_end_user_data_statement
|
||||
|
||||
from .ip import Ip
|
||||
|
||||
__red_end_user_data_statement__ = get_end_user_data_statement(file=__file__)
|
||||
|
||||
|
||||
async def setup(bot: Red) -> None:
|
||||
cog = Ip(bot)
|
||||
await bot.add_cog(cog)
|
12
ip/dashboard_integration.py
Normal file
12
ip/dashboard_integration.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from redbot.core import commands # isort:skip
|
||||
from redbot.core.bot import Red # isort:skip
|
||||
|
||||
|
||||
class DashboardIntegration:
|
||||
bot: Red
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_dashboard_cog_add(self, dashboard_cog: commands.Cog) -> None:
|
||||
if hasattr(self, "settings") and hasattr(self.settings, "commands_added"):
|
||||
await self.settings.commands_added.wait()
|
||||
dashboard_cog.rpc.third_parties_handler.add_third_party(self)
|
15
ip/info.json
Normal file
15
ip/info.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"author": ["AAA3A"],
|
||||
"name": "Ip",
|
||||
"install_msg": "This cog allows you to obtain the ip of the bot's host machine with the command `[p]ip`.",
|
||||
"short": "A cog to get the ip address of the bot's host machine!",
|
||||
"description": "A cog to get the ip address of the bot's host machine!",
|
||||
"tags": [
|
||||
"ip",
|
||||
"host",
|
||||
"machine"
|
||||
],
|
||||
"requirements": ["git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git"],
|
||||
"min_bot_version": "3.5.0",
|
||||
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
|
||||
}
|
84
ip/ip.py
Normal file
84
ip/ip.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
from AAA3A_utils import Cog, Settings # isort:skip
|
||||
from redbot.core import commands, Config # isort:skip
|
||||
from redbot.core.bot import Red # isort:skip
|
||||
from redbot.core.i18n import Translator, cog_i18n # isort:skip
|
||||
import typing # isort:skip
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .dashboard_integration import DashboardIntegration
|
||||
|
||||
# import socket
|
||||
|
||||
# Credits:
|
||||
# General repo credits.
|
||||
# Thanks to @AverageGamer on Discord for the cog idea and the code to find the external ip!
|
||||
# Thanks to @Flanisch on GitHub for the use of Wikipedia headers instead of the site found before (https://github.com/AAA3A-AAA3A/AAA3A-cogs/pull/)!
|
||||
|
||||
_: Translator = Translator("Ip", __file__)
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
class Ip(DashboardIntegration, Cog):
|
||||
"""A cog to get the ip address of the bot's host machine!"""
|
||||
|
||||
def __init__(self, bot: Red) -> None:
|
||||
super().__init__(bot=bot)
|
||||
|
||||
self.config: Config = Config.get_conf(
|
||||
self,
|
||||
identifier=205192943327321000143939875896557571750, # 969369062738
|
||||
force_registration=True,
|
||||
)
|
||||
self.config.register_global(port="0000")
|
||||
|
||||
_settings: typing.Dict[
|
||||
str, typing.Dict[str, typing.Union[typing.List[str], bool, str]]
|
||||
] = {
|
||||
"port": {
|
||||
"converter": commands.Range[str, 4, 4],
|
||||
"description": "Set the port.",
|
||||
},
|
||||
}
|
||||
self.settings: Settings = Settings(
|
||||
bot=self.bot,
|
||||
cog=self,
|
||||
config=self.config,
|
||||
group=self.config.GLOBAL,
|
||||
settings=_settings,
|
||||
global_path=[],
|
||||
use_profiles_system=False,
|
||||
can_edit=True,
|
||||
commands_group=self.ip_group,
|
||||
)
|
||||
|
||||
async def cog_load(self) -> None:
|
||||
await super().cog_load()
|
||||
await self.settings.add_commands()
|
||||
|
||||
@commands.is_owner()
|
||||
@commands.hybrid_group(name="ip")
|
||||
async def ip_group(self, ctx: commands.Context) -> None:
|
||||
"""Commands group for Ip."""
|
||||
pass
|
||||
|
||||
@ip_group.command()
|
||||
async def ip(self, ctx: commands.Context) -> None:
|
||||
"""Get the ip address of the bot's host machine."""
|
||||
# hostname = socket.gethostname()
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get("https://www.wikipedia.org", timeout=3) as r:
|
||||
ip = r.headers["X-Client-IP"] # Gives the "public IP" of the Bot client PC
|
||||
await ctx.send(_("The ip address of your bot is `{ip}`.").format(ip=ip))
|
||||
|
||||
@ip_group.command()
|
||||
async def website(self, ctx: commands.Context) -> None:
|
||||
"""Get the ip address website of the bot's host machine."""
|
||||
# hostname = socket.gethostname()
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get("https://www.wikipedia.org", timeout=3) as r:
|
||||
ip = r.headers["X-Client-IP"] # Gives the "public IP" of the Bot client PC
|
||||
port = await self.config.port()
|
||||
await ctx.send(
|
||||
_("The Administrator Panel website is http://{ip}:{port}/.").format(ip=ip, port=port)
|
||||
)
|
42
ip/locales/de-DE.po
Normal file
42
ip/locales/de-DE.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: de_DE\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Ein Zahnrad, um die IP-Adresse des Host-Rechners des Bots zu erhalten!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Ermittelt die IP-Adresse des Host-Rechners des Bots."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "Die IP-Adresse deines Bots lautet `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Ermitteln Sie die IP-Adresse des Host-Rechners des Bots."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Die Website des Administrationspanels ist http://{ip}:{port}/."
|
||||
|
42
ip/locales/el-GR.po
Normal file
42
ip/locales/el-GR.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Greek\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: el\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: el_GR\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Ένα γρανάζι για να λαμβάνετε τη διεύθυνση ip του υπολογιστή-ξενιστή του bot!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Λαμβάνετε τη διεύθυνση ip του κεντρικού υπολογιστή του bot."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "Η διεύθυνση ip του bot σας είναι \"{ip}\"."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Λάβετε τον ιστότοπο με τη διεύθυνση ip του μηχανήματος υποδοχής του bot."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Ο δικτυακός τόπος του πίνακα διαχειριστών είναι http://{ip}:{port}/."
|
||||
|
42
ip/locales/es-ES.po
Normal file
42
ip/locales/es-ES.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: es-ES\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: es_ES\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Un engranaje para obtener la dirección ip de la máquina anfitriona del bot!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Obtener la dirección IP de la máquina host del bot."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "La dirección IP de tu bot es `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Obtén la dirección IP de la máquina anfitriona del bot."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "El sitio web del Panel del Administrador es http://{ip}:{port}/."
|
||||
|
42
ip/locales/fi-FI.po
Normal file
42
ip/locales/fi-FI.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Finnish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: fi\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: fi_FI\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Hammasratas, jolla saat botin isäntäkoneen ip-osoitteen!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Hae botin isäntäkoneen ip-osoite."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "Botin ip-osoite on `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Hanki botin isäntäkoneen ip-osoite."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Hallintopaneelin verkkosivusto on http://{ip}:{port}/."
|
||||
|
42
ip/locales/fr-FR.po
Normal file
42
ip/locales/fr-FR.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: fr\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: fr_FR\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Un rouage pour obtenir l'adresse IP de la machine hôte du bot !"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Obtenir l'adresse IP de la machine hôte du bot."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "L'adresse IP de votre bot est `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Obtenir l'adresse IP du site web de la machine hôte du bot."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Le site web du Panel des administrateurs est le suivant : http://{ip}:{port}/."
|
||||
|
42
ip/locales/it-IT.po
Normal file
42
ip/locales/it-IT.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: it\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: it_IT\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Un ingranaggio per ottenere l'indirizzo ip della macchina host del bot!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Ottenere l'indirizzo IP del computer host del bot."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "L'indirizzo ip del bot è `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Ottenere l'indirizzo IP del sito web del computer host del bot."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Il sito web del Pannello di amministrazione è http://{ip}:{port}/."
|
||||
|
42
ip/locales/ja-JP.po
Normal file
42
ip/locales/ja-JP.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ja\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: ja_JP\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "ボットのホストマシンのipアドレスを取得するためのコグです!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "ボットのホストマシンのipアドレスを取得します。"
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "あなたのボットのipアドレスは `{ip}` です。"
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "ボットのホストマシンのipアドレスのサイトを取得します。"
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Administrator Panelのサイトは、http://{ip}:{port}/ です。"
|
||||
|
35
ip/locales/messages.pot
Normal file
35
ip/locales/messages.pot
Normal file
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2024-12-29 10:43+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr ""
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr ""
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr ""
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr ""
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr ""
|
42
ip/locales/nl-NL.po
Normal file
42
ip/locales/nl-NL.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: nl\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: nl_NL\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Een tandwiel om het ip-adres van de hostmachine van de bot te krijgen!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Verkrijg het ip-adres van de hostmachine van de bot."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "Het ip-adres van je bot is `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Verkrijg de ip-adreswebsite van de hostmachine van de bot."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "De website van het beheerderspaneel is http://{ip}:{port}/."
|
||||
|
42
ip/locales/pl-PL.po
Normal file
42
ip/locales/pl-PL.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pl\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: pl_PL\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Tryb umożliwiający uzyskanie adresu IP komputera hosta bota!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Pobiera adres IP komputera hosta bota."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "Adres ip twojego bota to `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Uzyskaj adres IP strony internetowej komputera hosta bota."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Strona internetowa Panelu Administratora to http://{ip}:{port}/."
|
||||
|
42
ip/locales/pt-BR.po
Normal file
42
ip/locales/pt-BR.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese, Brazilian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pt-BR\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: pt_BR\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Uma engrenagem para obter o endereço IP da máquina anfitriã do bot!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Obtém o endereço IP da máquina anfitriã do bot."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "O endereço ip do seu bot é `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Obter o endereço IP do site da máquina anfitriã do bot."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "O website do Painel de Administrador é{port}{ip}: /."
|
||||
|
42
ip/locales/pt-PT.po
Normal file
42
ip/locales/pt-PT.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pt-PT\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: pt_PT\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Uma engrenagem para obter o endereço IP da máquina anfitriã do bot!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Obtém o endereço IP da máquina anfitriã do bot."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "O endereço ip do seu bot é `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Obter o endereço IP do site da máquina anfitriã do bot."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "O website do Painel de Administrador é{ip}:{port}/."
|
||||
|
42
ip/locales/ro-RO.po
Normal file
42
ip/locales/ro-RO.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Romanian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ro\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: ro_RO\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "O rotiță pentru a obține adresa ip a mașinii gazdă a bot-ului!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Obține adresa IP a mașinii gazdă a robotului."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "Adresa IP a robotului tău este `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Obțineți adresa IP a site-ului web al mașinii gazdă a robotului."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Site-ul web al panoului de administrare este http://{ip}:{port}/."
|
||||
|
42
ip/locales/ru-RU.po
Normal file
42
ip/locales/ru-RU.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ru\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: ru_RU\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Зубец для получения ip-адреса хост-машины бота!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Получите ip-адрес хост-машины бота."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "ip-адрес вашего бота - `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Получите ip-адрес сайта хост-машины бота."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Веб-сайт панели администратора: http://{ip}:{port}/."
|
||||
|
42
ip/locales/tr-TR.po
Normal file
42
ip/locales/tr-TR.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-21 13:27\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: tr\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: tr_TR\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Botun ana makinesinin ip adresini almak için bir cog!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Botun barındırıldığı makinenin IP adresini alın."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "Botunuzun IP adresi `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Botun barındırıldığı makinenin IP adresi web sitesini alın."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Yönetici Paneli web sitesi http://{ip}:{port}/."
|
||||
|
42
ip/locales/uk-UA.po
Normal file
42
ip/locales/uk-UA.po
Normal file
|
@ -0,0 +1,42 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Ukrainian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: uk\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/ip/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 88\n"
|
||||
"Language: uk_UA\n"
|
||||
|
||||
#: ip\ip.py:23
|
||||
#, docstring
|
||||
msgid "A cog to get the ip address of the bot's host machine!"
|
||||
msgstr "Гвинтик для отримання ip-адреси хост-машини бота!"
|
||||
|
||||
#: ip\ip.py:67
|
||||
#, docstring
|
||||
msgid "Get the ip address of the bot's host machine."
|
||||
msgstr "Отримайте ip-адресу хост-машини бота."
|
||||
|
||||
#: ip\ip.py:72
|
||||
msgid "The ip address of your bot is `{ip}`."
|
||||
msgstr "IP-адреса вашого бота - `{ip}`."
|
||||
|
||||
#: ip\ip.py:76
|
||||
#, docstring
|
||||
msgid "Get the ip address website of the bot's host machine."
|
||||
msgstr "Отримайте ip-адресу веб-сайту, на якому працює бот."
|
||||
|
||||
#: ip\ip.py:83
|
||||
msgid "The Administrator Panel website is http://{ip}:{port}/."
|
||||
msgstr "Веб-сторінка Панелі адміністратора знаходиться за адресою http://{ip}:{port}/."
|
||||
|
1
ip/utils_version.json
Normal file
1
ip/utils_version.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"needed_utils_version": 7.0}
|
373
lock/LICENSE.md
Normal file
373
lock/LICENSE.md
Normal file
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
89
lock/README.rst
Normal file
89
lock/README.rst
Normal file
|
@ -0,0 +1,89 @@
|
|||
.. _lock:
|
||||
|
||||
====
|
||||
Lock
|
||||
====
|
||||
|
||||
This is the cog guide for the 'Lock' cog. This guide
|
||||
contains the collection of commands which you can use in the cog.
|
||||
|
||||
Through this guide, ``[p]`` will always represent your prefix. Replace
|
||||
``[p]`` with your own prefix when you use these commands in Discord.
|
||||
|
||||
.. note::
|
||||
|
||||
This guide was last updated for version 2.0.1. Ensure
|
||||
that you are up to date by running ``[p]cog update lock``.
|
||||
|
||||
If there is something missing, or something that needs improving
|
||||
in this documentation, feel free to create an issue `here <https://github.com/Kreusada/Kreusada-Cogs/issues>`_.
|
||||
|
||||
This documentation is auto-generated everytime this cog receives an update.
|
||||
|
||||
--------------
|
||||
About this cog
|
||||
--------------
|
||||
|
||||
Lock `@everyone` from sending messages in channels or the entire guild, and only allow Moderators to talk.
|
||||
|
||||
--------
|
||||
Commands
|
||||
--------
|
||||
|
||||
Here are all the commands included in this cog (10):
|
||||
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| Command | Help |
|
||||
+=========================+==============================================================+
|
||||
| ``[p]lock`` | Lock `@everyone` from sending messages. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]lock server`` | Lock `@everyone` from sending messages in the entire server. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]lockset`` | Various Lock settings. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]lockset ignore`` | Ignore a channel during server lock. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]lockset perms`` | Set if you use roles to access channels. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]lockset role`` | Set role that can lock channels. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]lockset settings`` | See current settings. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]lockset unignore`` | Remove channels from the ignored list. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]unlock`` | Unlock the channel for `@everyone`. |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
| ``[p]unlock server`` | Unlock the entire server for `@everyone` |
|
||||
+-------------------------+--------------------------------------------------------------+
|
||||
|
||||
------------
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you haven't added my repo before, lets add it first. We'll call it
|
||||
"kreusada-cogs" here.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]repo add kreusada-cogs https://github.com/Kreusada/Kreusada-Cogs
|
||||
|
||||
Now, we can install Lock.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]cog install kreusada-cogs lock
|
||||
|
||||
Once it's installed, it is not loaded by default. Load it by running the following
|
||||
command:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]load lock
|
||||
|
||||
---------------
|
||||
Further Support
|
||||
---------------
|
||||
|
||||
For more support, head over to the `cog support server <https://discord.gg/GET4DVk>`_,
|
||||
I have my own channel over there at #support_kreusada-cogs. Feel free to join my
|
||||
`personal server <https://discord.gg/JmCFyq7>`_ whilst you're here.
|
10
lock/__init__.py
Normal file
10
lock/__init__.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from redbot.core.bot import Red
|
||||
from redbot.core.utils import get_end_user_data_statement
|
||||
|
||||
from .lock import Lock
|
||||
|
||||
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
|
||||
|
||||
|
||||
async def setup(bot: Red):
|
||||
await bot.add_cog(Lock(bot))
|
9
lock/info.json
Normal file
9
lock/info.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"author" : ["saurichable", "Kreusada"],
|
||||
"install_msg": "Thanks for installing, have fun. Please refer to my docs if you need any help: https://kreusadacogs.readthedocs.io/en/latest/cog_lock.html",
|
||||
"name" : "Lock",
|
||||
"short" : "Lock `@everyone` from sending messages.",
|
||||
"description" : "Lock `@everyone` from sending messages in channels or the entire guild, and only allow Moderators to talk.",
|
||||
"tags" : ["lockdown", "lock"],
|
||||
"end_user_data_statement": "This cog does not store any user data."
|
||||
}
|
196
lock/lock.py
Normal file
196
lock/lock.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
import datetime
|
||||
import typing
|
||||
|
||||
import discord
|
||||
from redbot.core import Config, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.utils.chat_formatting import humanize_list
|
||||
|
||||
|
||||
class Lock(commands.Cog):
|
||||
"""
|
||||
Lock `@everyone` from sending messages in channels or the entire guild, and only allow Moderators to talk.
|
||||
"""
|
||||
|
||||
__version__ = "2.0.1"
|
||||
__author__ = "saurichable, Kreusada"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=36546565165464, force_registration=True)
|
||||
|
||||
self.config.register_guild(moderator=None, everyone=True, ignore=[])
|
||||
|
||||
async def red_delete_data_for_user(self, *, requester, user_id):
|
||||
# nothing to delete
|
||||
return
|
||||
|
||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||
context = super().format_help_for_context(ctx)
|
||||
return f"{context}\n\nVersion: {self.__version__}\nAuthors : {self.__author__}"
|
||||
|
||||
@commands.group(autohelp=True)
|
||||
@commands.guild_only()
|
||||
@commands.admin()
|
||||
async def lockset(self, ctx: commands.Context):
|
||||
"""Various Lock settings."""
|
||||
|
||||
@lockset.command(name="role")
|
||||
async def lockset_role(self, ctx: commands.Context, role: discord.Role):
|
||||
"""Set role that can lock channels."""
|
||||
await self.config.guild(ctx.guild).moderator.set(role.id)
|
||||
await ctx.tick()
|
||||
|
||||
@lockset.command(name="perms")
|
||||
async def lockset_perms(self, ctx: commands.Context, everyone: bool):
|
||||
"""Set if you use roles to access channels."""
|
||||
await self.config.guild(ctx.guild).everyone.set(not everyone)
|
||||
await ctx.tick()
|
||||
|
||||
@lockset.command(name="ignore")
|
||||
async def lockset_ignore(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||
"""Ignore a channel during server lock."""
|
||||
if channel.id not in await self.config.guild(ctx.guild).ignore():
|
||||
async with self.config.guild(ctx.guild).ignore() as ignore:
|
||||
ignore.append(channel.id)
|
||||
return await ctx.send(
|
||||
f"{channel.mention} has been added into the ignored channels list."
|
||||
)
|
||||
await ctx.send(f"{channel.mention} is already in the ignored channels list.")
|
||||
|
||||
@lockset.command(name="unignore")
|
||||
async def lockset_unignore(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||
"""Remove channels from the ignored list."""
|
||||
if channel.id not in await self.config.guild(ctx.guild).ignore():
|
||||
return await ctx.send(f"{channel.mention} already isn't in the ignored channels list.")
|
||||
async with self.config.guild(ctx.guild).ignore() as ignore:
|
||||
ignore.remove(channel.id)
|
||||
await ctx.send(f"{channel.mention} has been removed from the ignored channels list.")
|
||||
|
||||
@lockset.command(name="settings")
|
||||
async def lockset_settings(self, ctx: commands.Context):
|
||||
"""See current settings."""
|
||||
data = await self.config.guild(ctx.guild).all()
|
||||
mods = ctx.guild.get_role(data["moderator"])
|
||||
mods = "None" if not mods else mods.name
|
||||
|
||||
channels = data["ignore"]
|
||||
c_text = list()
|
||||
if channels == []:
|
||||
c_text = "None"
|
||||
else:
|
||||
for channel in channels:
|
||||
c = ctx.guild.get_channel(channel)
|
||||
if c:
|
||||
c_text.append(c.mention)
|
||||
c_text = humanize_list(c_text)
|
||||
|
||||
embed = discord.Embed(colour=await ctx.embed_colour(), timestamp=datetime.datetime.now())
|
||||
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url if ctx.guild.icon else None)
|
||||
embed.title = "**__Lock settings:__**"
|
||||
embed.set_footer(text="*required to function properly")
|
||||
|
||||
embed.add_field(name="Role that can type after locking*:", value=mods, inline=False)
|
||||
embed.add_field(
|
||||
name="Using roles to access servers:*:",
|
||||
value=str(not data["everyone"]),
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(name="Ignored channels:", value=c_text, inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.mod()
|
||||
@commands.bot_has_permissions(manage_channels=True)
|
||||
@commands.guild_only()
|
||||
@commands.group(invoke_without_command=True)
|
||||
async def lock(self, ctx: commands.Context):
|
||||
"""Lock `@everyone` from sending messages."""
|
||||
mods = ctx.guild.get_role(await self.config.guild(ctx.guild).moderator())
|
||||
which = await self.config.guild(ctx.guild).everyone()
|
||||
|
||||
if not mods:
|
||||
return await ctx.send("Uh oh. Looks like your Admins haven't setup this yet.")
|
||||
if which:
|
||||
await ctx.channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=True, send_messages=False
|
||||
)
|
||||
else:
|
||||
await ctx.channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=False, send_messages=False
|
||||
)
|
||||
await ctx.channel.set_permissions(mods, read_messages=True, send_messages=True)
|
||||
await ctx.send(":lock: Channel locked. Only Moderators can type.")
|
||||
|
||||
@lock.command(name="server")
|
||||
async def lock_server(self, ctx: commands.Context, confirmation: typing.Optional[bool]):
|
||||
"""Lock `@everyone` from sending messages in the entire server."""
|
||||
if not confirmation:
|
||||
return await ctx.send(
|
||||
"This will overwrite every channel's permissions.\n"
|
||||
f"If you're sure, type `{ctx.clean_prefix}lockserver yes` (you can set an alias for this so I don't ask you every time)."
|
||||
)
|
||||
async with ctx.typing():
|
||||
mods = ctx.guild.get_role(await self.config.guild(ctx.guild).moderator())
|
||||
which = await self.config.guild(ctx.guild).everyone()
|
||||
ignore = await self.config.guild(ctx.guild).ignore()
|
||||
|
||||
if not mods:
|
||||
return await ctx.send("Uh oh. Looks like your Admins haven't setup this yet.")
|
||||
for channel in ctx.guild.text_channels:
|
||||
if channel.id in ignore:
|
||||
continue
|
||||
if which:
|
||||
await channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=True, send_messages=False
|
||||
)
|
||||
else:
|
||||
await channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=False, send_messages=False
|
||||
)
|
||||
await channel.set_permissions(mods, read_messages=True, send_messages=True)
|
||||
await ctx.send(":lock: Server locked. Only Moderators can type.")
|
||||
|
||||
@commands.mod()
|
||||
@commands.bot_has_permissions(manage_channels=True)
|
||||
@commands.guild_only()
|
||||
@commands.group(invoke_without_command=True)
|
||||
async def unlock(self, ctx: commands.Context):
|
||||
"""Unlock the channel for `@everyone`."""
|
||||
mods = ctx.guild.get_role(await self.config.guild(ctx.guild).moderator())
|
||||
which = await self.config.guild(ctx.guild).everyone()
|
||||
|
||||
if not mods:
|
||||
return await ctx.send("Uh oh. Looks like your Admins haven't setup this yet.")
|
||||
if which:
|
||||
await ctx.channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=True, send_messages=True
|
||||
)
|
||||
else:
|
||||
await ctx.channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=False, send_messages=True
|
||||
)
|
||||
await ctx.send(":unlock: Channel unlocked.")
|
||||
|
||||
@unlock.command(name="server")
|
||||
async def unlock_server(self, ctx: commands.Context):
|
||||
"""Unlock the entire server for `@everyone`"""
|
||||
async with ctx.typing():
|
||||
mods = ctx.guild.get_role(await self.config.guild(ctx.guild).moderator())
|
||||
which = await self.config.guild(ctx.guild).everyone()
|
||||
ignore = await self.config.guild(ctx.guild).ignore()
|
||||
|
||||
if not mods:
|
||||
return await ctx.send("Uh oh. Looks like your Admins haven't setup this yet.")
|
||||
for channel in ctx.guild.text_channels:
|
||||
if channel.id in ignore:
|
||||
continue
|
||||
if which:
|
||||
await channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=True, send_messages=True
|
||||
)
|
||||
else:
|
||||
await channel.set_permissions(
|
||||
ctx.guild.default_role, read_messages=False, send_messages=True
|
||||
)
|
||||
await ctx.send(":unlock: Server unlocked.")
|
88
minecraft/README.rst
Normal file
88
minecraft/README.rst
Normal file
|
@ -0,0 +1,88 @@
|
|||
.. _minecraft:
|
||||
=========
|
||||
Minecraft
|
||||
=========
|
||||
|
||||
This is the cog guide for the ``Minecraft`` cog. This guide contains the collection of commands which you can use in the cog.
|
||||
Through this guide, ``[p]`` will always represent your prefix. Replace ``[p]`` with your own prefix when you use these commands in Discord.
|
||||
|
||||
.. note::
|
||||
|
||||
Ensure that you are up to date by running ``[p]cog update minecraft``.
|
||||
If there is something missing, or something that needs improving in this documentation, feel free to create an issue `here <https://github.com/AAA3A-AAA3A/AAA3A-cogs/issues>`_.
|
||||
This documentation is generated everytime this cog receives an update.
|
||||
|
||||
---------------
|
||||
About this cog:
|
||||
---------------
|
||||
|
||||
A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!
|
||||
|
||||
---------
|
||||
Commands:
|
||||
---------
|
||||
|
||||
Here are all the commands included in this cog (9):
|
||||
|
||||
* ``[p]minecraft``
|
||||
Get informations about Minecraft Java.
|
||||
|
||||
* ``[p]minecraft addserver [channel] <server_url>``
|
||||
Add a Minecraft Java server in Config to get automatically new status.
|
||||
|
||||
* ``[p]minecraft checkplayers [channel] <state>``
|
||||
Include players joining or leaving the server in notifications.
|
||||
|
||||
* ``[p]minecraft editlastmessage [channel] <state>``
|
||||
Edit the last message sent for changes.
|
||||
|
||||
* ``[p]minecraft forcecheck``
|
||||
Force check Minecraft Java servers in Config.
|
||||
|
||||
* ``[p]minecraft getdebugloopsstatus``
|
||||
Get an embed for check loop status.
|
||||
|
||||
* ``[p]minecraft getplayerskin <player> [overlay=False]``
|
||||
Get Minecraft Java player skin by name.
|
||||
|
||||
* ``[p]minecraft getserver <server_url>``
|
||||
Get informations about a Minecraft Java server.
|
||||
|
||||
* ``[p]minecraft removeserver [channel] <server_url>``
|
||||
Remove a Minecraft Java server in Config.
|
||||
|
||||
------------
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you haven't added my repo before, lets add it first. We'll call it "AAA3A-cogs" here.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]repo add AAA3A-cogs https://github.com/AAA3A-AAA3A/AAA3A-cogs
|
||||
|
||||
Now, we can install Minecraft.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]cog install AAA3A-cogs minecraft
|
||||
|
||||
Once it's installed, it is not loaded by default. Load it by running the following command:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[p]load minecraft
|
||||
|
||||
----------------
|
||||
Further Support:
|
||||
----------------
|
||||
|
||||
Check out my docs `here <https://aaa3a-cogs.readthedocs.io/en/latest/>`_.
|
||||
Mention me in the #support_other-cogs in the `cog support server <https://discord.gg/GET4DVk>`_ if you need any help.
|
||||
Additionally, feel free to open an issue or pull request to this repo.
|
||||
|
||||
--------
|
||||
Credits:
|
||||
--------
|
||||
|
||||
Thanks to Kreusada for the Python code to automatically generate this documentation!
|
46
minecraft/__init__.py
Normal file
46
minecraft/__init__.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from redbot.core import errors # isort:skip
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
try:
|
||||
import AAA3A_utils
|
||||
except ModuleNotFoundError:
|
||||
raise errors.CogLoadError(
|
||||
"The needed utils to run the cog were not found. Please execute the command `[p]pipinstall git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
|
||||
)
|
||||
modules = sorted(
|
||||
[module for module in sys.modules if module.split(".")[0] == "AAA3A_utils"], reverse=True
|
||||
)
|
||||
for module in modules:
|
||||
try:
|
||||
importlib.reload(sys.modules[module])
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
del AAA3A_utils
|
||||
# import AAA3A_utils
|
||||
# import json
|
||||
# import os
|
||||
# __version__ = AAA3A_utils.__version__
|
||||
# with open(os.path.join(os.path.dirname(__file__), "utils_version.json"), mode="r") as f:
|
||||
# data = json.load(f)
|
||||
# needed_utils_version = data["needed_utils_version"]
|
||||
# if __version__ > needed_utils_version:
|
||||
# raise errors.CogLoadError(
|
||||
# "The needed utils to run the cog has a higher version than the one supported by this version of the cog. Please update the cogs of the `AAA3A-cogs` repo."
|
||||
# )
|
||||
# elif __version__ < needed_utils_version:
|
||||
# raise errors.CogLoadError(
|
||||
# "The needed utils to run the cog has a lower version than the one supported by this version of the cog. Please execute the command `[p]pipinstall --upgrade git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
|
||||
# )
|
||||
|
||||
from redbot.core.bot import Red # isort:skip
|
||||
from redbot.core.utils import get_end_user_data_statement
|
||||
|
||||
from .minecraft import Minecraft
|
||||
|
||||
__red_end_user_data_statement__ = get_end_user_data_statement(file=__file__)
|
||||
|
||||
|
||||
async def setup(bot: Red) -> None:
|
||||
cog = Minecraft(bot)
|
||||
await bot.add_cog(cog)
|
16
minecraft/info.json
Normal file
16
minecraft/info.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"author": ["AAA3A"],
|
||||
"name": "Minecraft",
|
||||
"install_msg": "Thank you for installing this cog!\nDo `[p]help CogName` to get the list of commands and their description. If you enjoy my work, please consider donating on [Buy Me a Coffee](<https://www.buymeacoffee.com/aaa3a>) or [Ko-Fi](<https://ko-fi.com/aaa3a>)!",
|
||||
"short": "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!",
|
||||
"description": "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!",
|
||||
"tags": [
|
||||
"minecraft",
|
||||
"game",
|
||||
"server",
|
||||
"notifications"
|
||||
],
|
||||
"requirements": ["git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git", "mcstatus>=9.3.1"],
|
||||
"min_bot_version": "3.5.0",
|
||||
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
|
||||
}
|
140
minecraft/locales/de-DE.po
Normal file
140
minecraft/locales/de-DE.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: de_DE\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Es können keine Daten von der Minecraft API abgerufen werden: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} nicht auf den Mojang-Servern gefunden."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} wird gefunden, hat aber eine falsche UUID."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Ein Cog, das Informationen über Minecraft Java Benutzer und Server anzeigt, und über jede Änderung eines Servers informiert!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Latenzzeit"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Spieler"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Version"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Minecraft Java-Spieler-Skin nach Name abrufen."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Daten von Crafatar können nicht abgerufen werden: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Zur Verfügung gestellt von Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Erhalten Sie Informationen über einen Minecraft Java Server."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Für diesen Minecraft-Server wurden keine Daten gefunden. Vielleicht existiert er nicht oder seine Daten sind vorübergehend nicht verfügbar."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Fügen Sie einen Minecraft Java Server in Config hinzu, um automatisch einen neuen Status zu erhalten."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Ich habe keine ausreichenden Berechtigungen in diesem Kanal, um Nachrichten mit Einbettungen zu senden."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Dieser Server ist bereits hinzugefügt worden."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Server zu diesem Kanal hinzugefügt."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Entfernen Sie einen Minecraft-Java-Server in Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Dieser Server befindet sich nicht in der Config."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Server aus diesem Channel entfernt."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Spieler, die dem Server beitreten oder ihn verlassen, werden in die Benachrichtigungen aufgenommen."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Ich werde die Spieler nicht auf die Benachrichtigungen überprüfen."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Ich werde die Spieler auf die Benachrichtigungen hin überprüfen."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Bearbeiten Sie die zuletzt gesendete Nachricht auf Änderungen."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Ich werde meine letzte Nachricht wegen der Benachrichtigungen nicht bearbeiten."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Ich werde meine letzte Nachricht für die Benachrichtigungen bearbeiten."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Erzwinge die Überprüfung von Minecraft-Java-Servern in Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Server überprüft."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Holen Sie sich eine Einbettung zur Überprüfung des Schleifenstatus."
|
||||
|
140
minecraft/locales/el-GR.po
Normal file
140
minecraft/locales/el-GR.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Greek\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: el\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: el_GR\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Δεν είναι δυνατή η λήψη δεδομένων από το Minecraft API: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} δεν βρέθηκε στους διακομιστές της Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} βρέθηκε, αλλά έχει λανθασμένο UUID."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Ένα γρανάζι για να εμφανίζει πληροφορίες σχετικά με τους χρήστες και τους διακομιστές του Minecraft Java και να ειδοποιεί για κάθε αλλαγή ενός διακομιστή!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Καθυστέρηση"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Παίκτες"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Έκδοση"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Βρείτε το δέρμα παίκτη Minecraft Java με βάση το όνομα."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Αδυναμία λήψης δεδομένων από το Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Παρέχεται από το Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Λάβετε πληροφορίες σχετικά με έναν διακομιστή Minecraft Java."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Δεν βρέθηκαν δεδομένα για αυτόν τον διακομιστή Minecraft. Ίσως δεν υπάρχει ή τα δεδομένα του δεν είναι προσωρινά διαθέσιμα."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Προσθέστε έναν διακομιστή Minecraft Java στο Config για να λάβετε αυτόματα νέα κατάσταση."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Δεν έχω επαρκή δικαιώματα σε αυτό το κανάλι για να στέλνω μηνύματα με ενσωματώσεις."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Αυτός ο διακομιστής έχει ήδη προστεθεί."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Ο διακομιστής προστέθηκε σε αυτό το κανάλι."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Κατάργηση ενός διακομιστή Java του Minecraft στο Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Αυτός ο διακομιστής δεν βρίσκεται στο Config."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Ο διακομιστής αφαιρέθηκε από αυτό το κανάλι."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Συμπεριλάβετε τους παίκτες που προσχωρούν ή αποχωρούν από τον διακομιστή στις ειδοποιήσεις."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Δεν θα ελέγχω τους παίκτες για τις ειδοποιήσεις."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Θα ελέγξω τους παίκτες για τις ειδοποιήσεις."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Επεξεργαστείτε το τελευταίο μήνυμα που εστάλη για αλλαγές."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Δεν θα επεξεργαστώ το τελευταίο μου μήνυμα για τις ειδοποιήσεις."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Θα επεξεργαστώ το τελευταίο μου μήνυμα για τις ειδοποιήσεις."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Αναγκαστικός έλεγχος των διακομιστών Minecraft Java στο Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Οι διακομιστές ελέγχθηκαν."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Λάβετε μια ενσωμάτωση για την κατάσταση του βρόχου ελέγχου."
|
||||
|
140
minecraft/locales/es-ES.po
Normal file
140
minecraft/locales/es-ES.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: es-ES\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: es_ES\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "No se pueden obtener datos de la API de Minecraft: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} no se encuentra en los servidores de Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} pero tiene un UUID incorrecto."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Un engranaje para mostrar información sobre los usuarios y servidores de Minecraft Java, y notificar cada cambio de un servidor!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Latencia"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Jugadores"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versión"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Obtener Minecraft Java jugador de la piel por su nombre."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "No se pueden obtener datos de Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Proporcionado por Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Obtén información sobre un servidor Java de Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "No se han encontrado datos para este servidor. Puede que no exista o los datos no están disponibles temporalmente."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Añade un servidor Java Minecraft en Config para obtener automáticamente un nuevo estado."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "No tengo suficientes permisos en este canal para enviar mensajes con incrustados."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Este servidor ya ha sido añadido."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Servidor añadido a este canal."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Eliminar un servidor Java de Minecraft en Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Este servidor no está en el Config."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Servidor eliminado de este canal."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Incluye jugadores entrando y saliendo del servidor en las notificaciones."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "No comprobaré jugadores para las notificaciones."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Comprobaré jugadores para las notificaciones."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Edita el último mensaje enviado por cambios."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "No editaré mi último mensaje para las notificaciones."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Editaré mi último mensaje para las notificaciones."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Forzar la comprobación de los servidores Java de Minecraft en Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Servidores comprobados."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Obtener un embed para comprobar el estado del bucle."
|
||||
|
140
minecraft/locales/fi-FI.po
Normal file
140
minecraft/locales/fi-FI.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Finnish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: fi\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: fi_FI\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Tietoja ei saada Minecraft API:sta: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} ei löydy Mojangin palvelimilta."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} löytyy, mutta sen UUID-tunnus on väärä."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Tietojen näyttämiseen Minecraft Java -käyttäjistä ja palvelimista, ja ilmoittaa jokaisesta palvelimen muutoksesta!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Viive"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Pelaajat"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versio"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Hae Minecraftin Java-pelaajan iho nimen perusteella."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Tietoja ei saada Crafatarista: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Toimittanut Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Hanki tietoa Minecraft Java -palvelimesta."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Tästä Minecraft-palvelimesta ei löytynyt tietoja. Ehkä sitä ei ole olemassa tai sen tiedot eivät ole tilapäisesti saatavilla."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Lisää Minecraft Java -palvelin Configiin saadaksesi automaattisesti uuden tilan."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Minulla ei ole riittäviä oikeuksia tällä kanavalla lähettää viestejä, joissa on upotuksia."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Tämä palvelin on jo lisätty."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Palvelin lisätty tälle kanavalle."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Poista Minecraftin Java-palvelin Configissa."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Tämä palvelin ei ole Configissa."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Palvelin poistettu tältä kanavalta."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Sisällytä palvelimelle liittyvät tai palvelimelta lähtevät pelaajat ilmoituksiin."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "En tarkista pelaajien ilmoituksia."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Tarkistan pelaajien ilmoitukset."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Muokkaa viimeksi lähetettyä viestiä muutoksia varten."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "En muokkaa viimeistä viestiäni ilmoitusten vuoksi."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Muokkaan viimeistä viestiäni ilmoitusten osalta."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Pakota tarkistamaan Minecraft Java -palvelimet Configissa."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Palvelimet tarkistettu."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Hae upotus silmukan tilan tarkistamista varten."
|
||||
|
140
minecraft/locales/fr-FR.po
Normal file
140
minecraft/locales/fr-FR.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: fr\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: fr_FR\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Impossible d'obtenir des données de l'API Minecraft : {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} introuvable sur les serveurs de Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} est trouvé, mais son UUID est incorrect."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Un cog pour afficher des informations sur les utilisateurs et les serveurs de Minecraft Java, et notifier chaque changement de serveur !"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Temps de latence"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Joueurs"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Version"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Obtenir le skin du joueur Java de Minecraft par nom."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Impossible d'obtenir des données de Crafatar : {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Fourni par Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Obtenir des informations sur un serveur Java Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Aucune donnée n'a été trouvée pour ce serveur Minecraft. Peut-être qu'il n'existe pas ou que ses données sont temporairement indisponibles."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Ajoutez un serveur Minecraft Java dans Config pour obtenir automatiquement un nouveau statut."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Je n'ai pas les autorisations suffisantes dans ce canal pour envoyer des messages avec des liens."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Ce serveur a déjà été ajouté."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Serveur ajouté à ce canal."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Supprimer un serveur Java Minecraft dans Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Ce serveur n'est pas dans la Config."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Serveur retiré de ce canal."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Inclure les joueurs qui rejoignent ou quittent le serveur dans les notifications."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Je ne vérifierai pas les joueurs pour les notifications."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Je consulterai les lecteurs pour les notifications."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Modifier le dernier message envoyé pour y apporter des changements."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Je ne modifierai pas mon dernier message pour les notifications."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Je modifierai mon dernier message pour les notifications."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Forcer la vérification des serveurs Java Minecraft dans Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Serveurs vérifiés."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Obtenir un embed pour vérifier l'état de la boucle."
|
||||
|
140
minecraft/locales/it-IT.po
Normal file
140
minecraft/locales/it-IT.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: it\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: it_IT\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Impossibile ottenere dati da Minecraft API: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} non trovato sui server Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} è stato trovato, ma ha un UUID non corretto."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Un modulo per visualizzare le informazioni sugli utenti e sui server di Minecraft Java, e notificare ogni cambiamento di un server!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Latenza"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Giocatori"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versione"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Ottenere la skin del giocatore Java di Minecraft per nome."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Impossibile ottenere i dati da Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Fornito da Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Ottenere informazioni su un server Java di Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Non sono stati trovati dati per questo server Minecraft. Forse non esiste o i suoi dati non sono temporaneamente disponibili."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Aggiungere un server Minecraft Java in Config per ottenere automaticamente un nuovo stato."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Non ho i permessi sufficienti in questo canale per inviare messaggi con embed."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Questo server è già stato aggiunto."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Server aggiunto a questo canale."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Rimuovere un server Minecraft Java in Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Questo server non è nella configurazione."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Server rimosso da questo canale."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Includere nelle notifiche i giocatori che si uniscono o lasciano il server."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Non controllerò i giocatori per le notifiche."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Controllerò i giocatori per le notifiche."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Modificare l'ultimo messaggio inviato per apportare modifiche."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Non modificherò il mio ultimo messaggio per le notifiche."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Modificherò il mio ultimo messaggio per le notifiche."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Forza il controllo dei server Java di Minecraft in Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Server controllati."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Ottenere un embed per controllare lo stato del ciclo."
|
||||
|
140
minecraft/locales/ja-JP.po
Normal file
140
minecraft/locales/ja-JP.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ja\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: ja_JP\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Minecraft APIからデータを取得できません: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} は、Mojangのサーバーで見つかりませんでした。"
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} が見つかりましたが、UUIDが不正確です。"
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Minecraft Java のユーザーやサーバーの情報を表示し、サーバーの変更などを通知するための歯車です!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "レイテンシー"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "プレーヤーズ"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "バージョン"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Minecraft Javaプレーヤースキンを名前で取得します。"
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Crafatarからデータを取得できない: {}。"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Crafatarの提供です。"
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "MinecraftのJavaサーバーに関する情報を得ることができます。"
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "このMinecraftサーバーのデータが見つかりません。存在しないか、データが一時的に利用できないのかもしれません。"
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "MinecraftのJavaサーバーをConfigに追加すると、自動的に新しいステータスを取得します。"
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "このチャンネルでは、埋め込みメッセージを送信するのに十分な権限がありません。"
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "このサーバーはすでに追加されています。"
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "このチャンネルにサーバーが追加されました。"
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "ConfigでMinecraft Javaサーバーを削除する。"
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "このサーバーはConfigにないんです。"
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "このチャンネルからサーバーが削除された。"
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "サーバーに参加または退会するプレーヤーを通知に含める。"
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "私は通知で選手をチェックすることはない。"
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "選手への通知はチェックしておく。"
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "最後に送信されたメッセージの変更を編集する。"
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "通知のために前回のメッセージを編集するつもりはない。"
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "前回のメッセージを編集してお知らせします。"
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "ConfigでMinecraftのJavaサーバーを強制的にチェックするようにしました。"
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "サーバーをチェックした。"
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "ループの状態を確認するためのエンベデッドを取得します。"
|
||||
|
139
minecraft/locales/messages.pot
Normal file
139
minecraft/locales/messages.pot
Normal file
|
@ -0,0 +1,139 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2025-03-15 23:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid ""
|
||||
"A cog to display informations about Minecraft Java users and servers, and "
|
||||
"notify for each change of a server!"
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Latency"
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:191
|
||||
msgid "Players"
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:211
|
||||
msgid "Version"
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:251
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:274
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:286
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:292
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:298 minecraft\minecraft.py:333
|
||||
msgid ""
|
||||
"No data found for this Minecraft server. Maybe it doesn't exist or its data "
|
||||
"are temporarily unavailable."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:310
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:321
|
||||
msgid ""
|
||||
"I don't have sufficient permissions in this channel to send messages with "
|
||||
"embeds."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:327
|
||||
msgid "This server has already been added."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:341
|
||||
msgid "Server added to this channel."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:348
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:353
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:358
|
||||
msgid "Server removed from this channel."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:365
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:372
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:374
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:381
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:386
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:388
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:393
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:395
|
||||
msgid "Servers checked."
|
||||
msgstr ""
|
||||
|
||||
#: minecraft\minecraft.py:401
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr ""
|
140
minecraft/locales/nl-NL.po
Normal file
140
minecraft/locales/nl-NL.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: nl\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: nl_NL\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Kan geen gegevens ophalen uit Minecraft API: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} niet gevonden op Mojang-servers."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} is gevonden, maar heeft een onjuiste UUID."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Een tandwiel om informatie weer te geven over Minecraft Java gebruikers en servers, en te waarschuwen voor elke verandering van een server!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Latency"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Spelers"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versie"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Haal Minecraft Java speler skin op naam."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Kan geen gegevens ophalen uit Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Geleverd door Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Krijg informatie over een Minecraft Java server."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Geen gegevens gevonden voor deze Minecraft server. Misschien bestaat hij niet of zijn de gegevens tijdelijk niet beschikbaar."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Voeg een Minecraft Java-server toe in Config om automatisch een nieuwe status te krijgen."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Ik heb niet voldoende rechten in dit kanaal om berichten met embeds te versturen."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Deze server is al toegevoegd."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Server toegevoegd aan dit kanaal."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Verwijder een Minecraft Java-server in Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Deze server staat niet in de Config."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Server verwijderd van dit kanaal."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Neem spelers die de server betreden of verlaten op in meldingen."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Ik zal spelers niet controleren op de meldingen."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Ik zal spelers controleren op de meldingen."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Bewerk het laatst verzonden bericht voor wijzigingen."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Ik zal mijn laatste bericht niet bewerken voor de meldingen."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Ik zal mijn laatste bericht bewerken voor de meldingen."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Forceer het controleren van Minecraft Java-servers in Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Servers gecontroleerd."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Een insluiting krijgen om de lusstatus te controleren."
|
||||
|
140
minecraft/locales/pl-PL.po
Normal file
140
minecraft/locales/pl-PL.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:24\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pl\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: pl_PL\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Nie można pobrać danych z interfejsu API Minecraft: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} nie znaleziono na serwerach Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} został znaleziony, ale ma nieprawidłowy identyfikator UUID."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Tryb do wyświetlania informacji o użytkownikach i serwerach Minecraft Java oraz powiadamiania o każdej zmianie serwera!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Opóźnienie"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Gracze"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Wersja"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Pobierz skórkę gracza Minecraft Java według nazwy."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Nie można pobrać danych z Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Dostarczone przez Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Uzyskaj informacje o serwerze Minecraft Java."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Nie znaleziono danych dla tego serwera Minecraft. Być może nie istnieje lub jego dane są tymczasowo niedostępne."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Dodaj serwer Minecraft Java w Config, aby automatycznie uzyskać nowy status."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Nie mam wystarczających uprawnień na tym kanale, aby wysyłać wiadomości z osadzonymi elementami."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Ten serwer został już dodany."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Serwer dodany do tego kanału."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Usunięcie serwera Minecraft Java w Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Ten serwer nie znajduje się w konfiguracji."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Serwer usunięty z tego kanału."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Uwzględnianie graczy dołączających do serwera lub opuszczających go w powiadomieniach."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Nie będę sprawdzał graczy pod kątem powiadomień."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Sprawdzę graczy pod kątem powiadomień."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Edycja ostatnio wysłanej wiadomości w celu wprowadzenia zmian."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Nie będę edytował ostatniej wiadomości dla powiadomień."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Zedytuję moją ostatnią wiadomość dla powiadomień."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Wymuś sprawdzenie serwerów Minecraft Java w konfiguracji."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Serwery sprawdzone."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Uzyskaj embed do sprawdzenia stanu pętli."
|
||||
|
140
minecraft/locales/pt-BR.po
Normal file
140
minecraft/locales/pt-BR.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:24\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese, Brazilian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pt-BR\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: pt_BR\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Não é possível obter dados da API do Minecraft: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} não encontrado nos servidores da Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} é encontrado, mas tem um UUID incorrecto."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Uma engrenagem para mostrar informações sobre os utilizadores e servidores do Minecraft Java, e notificar cada mudança de um servidor!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Latência"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Jogadores"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versão"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Obter a skin do jogador Java do Minecraft por nome."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Não é possível obter dados do Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Fornecido por Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Obter informações sobre um servidor Java do Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Não foram encontrados dados para este servidor do Minecraft. Talvez não exista ou os seus dados estejam temporariamente indisponíveis."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Adicione um servidor Java do Minecraft no Config para obter automaticamente um novo estado."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Não tenho permissões suficientes neste canal para enviar mensagens com incorporações."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Este servidor já foi adicionado."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Servidor adicionado a este canal."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Remover um servidor Java do Minecraft no Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Este servidor não está na Configuração."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Servidor removido deste canal."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Incluir nas notificações os jogadores que entram ou saem do servidor."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Não vou verificar as notificações dos jogadores."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Vou verificar as notificações dos jogadores."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Editar a última mensagem enviada para efetuar alterações."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Não vou editar a minha última mensagem para as notificações."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Vou editar a minha última mensagem para as notificações."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Forçar a verificação dos servidores Java do Minecraft no Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Servidores verificados."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Obter uma incorporação para verificar o estado do laço."
|
||||
|
140
minecraft/locales/pt-PT.po
Normal file
140
minecraft/locales/pt-PT.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:24\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: pt-PT\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: pt_PT\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Não é possível obter dados da API do Minecraft: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} não encontrado nos servidores da Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} é encontrado, mas tem um UUID incorrecto."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Uma engrenagem para mostrar informações sobre os utilizadores e servidores do Minecraft Java, e notificar cada mudança de um servidor!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Latência"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Jogadores"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versão"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Obter a skin do jogador Java do Minecraft por nome."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Não é possível obter dados do Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Fornecido por Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Obter informações sobre um servidor Java do Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Não foram encontrados dados para este servidor do Minecraft. Talvez não exista ou os seus dados estejam temporariamente indisponíveis."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Adicione um servidor Java do Minecraft no Config para obter automaticamente um novo estado."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Não tenho permissões suficientes neste canal para enviar mensagens com incorporações."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Este servidor já foi adicionado."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Servidor adicionado a este canal."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Remover um servidor Java do Minecraft no Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Este servidor não está na Configuração."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Servidor removido deste canal."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Incluir nas notificações os jogadores que entram ou saem do servidor."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Não vou verificar as notificações dos jogadores."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Vou verificar as notificações dos jogadores."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Editar a última mensagem enviada para efetuar alterações."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Não vou editar a minha última mensagem para as notificações."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Vou editar a minha última mensagem para as notificações."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Forçar a verificação dos servidores Java do Minecraft no Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Servidores verificados."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Obter uma incorporação para verificar o estado do laço."
|
||||
|
140
minecraft/locales/ro-RO.po
Normal file
140
minecraft/locales/ro-RO.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Romanian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ro\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: ro_RO\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Nu se poate obține date de la Minecraft API: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} nu se găsește pe serverele Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} este găsit, dar are un UUID incorect."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "O rotiță pentru a afișa informații despre utilizatorii și serverele Minecraft Java și pentru a notifica fiecare schimbare a unui server!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Latență"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Jucători"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versiunea"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Obțineți skin-ul jucătorului Java Minecraft după nume."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Imposibil de a obține date de la Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Furnizat de Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Obțineți informații despre un server Java Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Nu s-au găsit date pentru acest server Minecraft. Poate că nu există sau datele sale sunt temporar indisponibile."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Adăugați un server Java Minecraft în Config pentru a obține automat un nou statut."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Nu am suficiente permisiuni în acest canal pentru a trimite mesaje cu inserții."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Acest server a fost deja adăugat."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Server adăugat la acest canal."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Îndepărtați un server Java Minecraft în Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Acest server nu se află în configurare."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Server eliminat de pe acest canal."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Includeți jucătorii care se alătură sau părăsesc serverul în notificări."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Nu voi verifica jucătorii pentru notificări."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Voi verifica jucătorii pentru notificări."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Editați ultimul mesaj trimis pentru modificări."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Nu voi modifica ultimul meu mesaj pentru notificări."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Voi edita ultimul meu mesaj pentru notificări."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Forțați verificarea serverelor Minecraft Java în Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Serverele au fost verificate."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Obține o inserție pentru verificarea stării buclei."
|
||||
|
140
minecraft/locales/ru-RU.po
Normal file
140
minecraft/locales/ru-RU.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:24\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: ru\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: ru_RU\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Невозможно получить данные из API Minecraft: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} не найдена на серверах Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} найден, но имеет неправильный UUID."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Коготь для отображения информации о пользователях и серверах Minecraft Java, а также уведомление о каждом изменении сервера!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Латентность"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Игроки"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Версия"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Получить скин игрока Minecraft Java по имени."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Невозможно получить данные из Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Предоставлено компанией Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Получите информацию о Java-сервере Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Для этого сервера Minecraft не найдено никаких данных. Возможно, он не существует или его данные временно недоступны."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Добавьте Java-сервер Minecraft в Config, чтобы автоматически получить новый статус."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "У меня нет достаточных прав в этом канале для отправки сообщений с вставками."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Этот сервер уже добавлен."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Сервер добавлен в этот канал."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Удалите Java-сервер Minecraft в Config."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Этого сервера нет в конфиге."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Сервер удален из этого канала."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Включите в уведомления игроков, присоединяющихся к серверу или покидающих его."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Я не буду проверять игроков на наличие уведомлений."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Я проверю игроков на наличие уведомлений."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Редактирование последнего отправленного сообщения для внесения изменений."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Я не буду редактировать свое последнее сообщение из-за уведомлений."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Я отредактирую свое последнее сообщение для уведомлений."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Принудительная проверка Java-серверов Minecraft в Config."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Серверы проверены."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Получить эмбед для проверки состояния цикла."
|
||||
|
140
minecraft/locales/tr-TR.po
Normal file
140
minecraft/locales/tr-TR.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-21 13:27\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: tr\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: tr_TR\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Minecraft API'sinden veri alınamadı: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} Mojang sunucularında bulunamadı."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} bulundu, ancak hatalı bir UUID'ye sahip."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Minecraft Java kullanıcıları ve sunucuları hakkında bilgi görüntülemek ve her sunucu değişikliğinde bildirim yapmak için bir cog!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Gecikme"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Oyuncular"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Versiyon"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Minecraft Java oyuncu görünümünü isme göre alın."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Crafatar'dan veri alınamadı: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Crafatar tarafından sağlanmıştır."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Bir Minecraft Java sunucusu hakkında bilgi alın."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Bu Minecraft sunucusu için veri bulunamadı. Belki de mevcut değil veya verileri geçici olarak kullanılamıyor."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Yeni durumu otomatik olarak almak için Config'e bir Minecraft Java sunucusu ekleyin."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Bu kanalda gömülü mesajlar göndermek için yeterli iznim yok."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Bu sunucu zaten eklenmiş."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Sunucu bu kanala eklendi."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Config'deki bir Minecraft Java sunucusunu kaldırın."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Bu sunucu Config'de değil."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Sunucu bu kanaldan kaldırıldı."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Bildirimlere sunucuya katılan veya sunucudan ayrılan oyuncuları dahil edin."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Bildirimler için oyuncuları kontrol etmeyeceğim."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Bildirimler için oyuncuları kontrol edeceğim."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Değişiklikler için gönderilen son mesajı düzenleyin."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Bildirimler için son mesajımı düzenlemeyeceğim."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Bildirimler için son mesajımı düzenleyeceğim."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Config'deki Minecraft Java sunucularını zorla kontrol edin."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Sunucular kontrol edildi."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Döngü durumunu kontrol etmek için bir gömme alın."
|
||||
|
140
minecraft/locales/uk-UA.po
Normal file
140
minecraft/locales/uk-UA.po
Normal file
|
@ -0,0 +1,140 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: aaa3a-cogs\n"
|
||||
"POT-Creation-Date: 2024-07-20 22:15+0200\n"
|
||||
"PO-Revision-Date: 2024-07-20 20:24\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Ukrainian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: redgettext 3.4.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
"X-Crowdin-Project: aaa3a-cogs\n"
|
||||
"X-Crowdin-Project-ID: 531090\n"
|
||||
"X-Crowdin-Language: uk\n"
|
||||
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/minecraft/locales/messages.pot\n"
|
||||
"X-Crowdin-File-ID: 235\n"
|
||||
"Language: uk_UA\n"
|
||||
|
||||
#: minecraft\minecraft.py:48
|
||||
msgid "Unable to get data from Minecraft API: {e.message}."
|
||||
msgstr "Не вдається отримати дані з API Minecraft: {e.message}."
|
||||
|
||||
#: minecraft\minecraft.py:52
|
||||
msgid "{argument} not found on Mojang servers."
|
||||
msgstr "{argument} не знайдено на серверах Mojang."
|
||||
|
||||
#: minecraft\minecraft.py:60
|
||||
msgid "{argument} is found, but has incorrect UUID."
|
||||
msgstr "{argument} знайдено, але він має неправильний UUID."
|
||||
|
||||
#: minecraft\minecraft.py:66
|
||||
#, docstring
|
||||
msgid "A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"
|
||||
msgstr "Гвинтик для відображення інформації про користувачів та сервери Minecraft Java, а також сповіщення про кожну зміну сервера!"
|
||||
|
||||
#: minecraft\minecraft.py:187
|
||||
msgid "Latency"
|
||||
msgstr "Затримка"
|
||||
|
||||
#: minecraft\minecraft.py:189
|
||||
msgid "Players"
|
||||
msgstr "Гравці"
|
||||
|
||||
#: minecraft\minecraft.py:207
|
||||
msgid "Version"
|
||||
msgstr "Версія"
|
||||
|
||||
#: minecraft\minecraft.py:247
|
||||
#, docstring
|
||||
msgid "Get Minecraft Java player skin by name."
|
||||
msgstr "Отримати скін Minecraft Java-програвача за назвою."
|
||||
|
||||
#: minecraft\minecraft.py:270
|
||||
msgid "Unable to get data from Crafatar: {}"
|
||||
msgstr "Не вдалося отримати дані від Crafatar: {}"
|
||||
|
||||
#: minecraft\minecraft.py:282
|
||||
msgid "Provided by Crafatar."
|
||||
msgstr "Надано Crafatar."
|
||||
|
||||
#: minecraft\minecraft.py:288
|
||||
#, docstring
|
||||
msgid "Get informations about a Minecraft Java server."
|
||||
msgstr "Отримайте інформацію про Java-сервер Minecraft."
|
||||
|
||||
#: minecraft\minecraft.py:294 minecraft\minecraft.py:329
|
||||
msgid "No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
msgstr "Для цього сервера Minecraft даних не знайдено. Можливо, він не існує або його дані тимчасово недоступні."
|
||||
|
||||
#: minecraft\minecraft.py:306
|
||||
#, docstring
|
||||
msgid "Add a Minecraft Java server in Config to get automatically new status."
|
||||
msgstr "Додайте Java-сервер Minecraft у конфігурацію, щоб автоматично отримати новий статус."
|
||||
|
||||
#: minecraft\minecraft.py:317
|
||||
msgid "I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
msgstr "Я не маю достатніх прав у цьому каналі, щоб надсилати повідомлення з вбудовуваннями."
|
||||
|
||||
#: minecraft\minecraft.py:323
|
||||
msgid "This server has already been added."
|
||||
msgstr "Цей сервер вже додано."
|
||||
|
||||
#: minecraft\minecraft.py:337
|
||||
msgid "Server added to this channel."
|
||||
msgstr "Сервер додано до цього каналу."
|
||||
|
||||
#: minecraft\minecraft.py:344
|
||||
#, docstring
|
||||
msgid "Remove a Minecraft Java server in Config."
|
||||
msgstr "Видаліть Java-сервер Minecraft у налаштуваннях."
|
||||
|
||||
#: minecraft\minecraft.py:349
|
||||
msgid "This server isn't in the Config."
|
||||
msgstr "Цього сервера немає в конфігурації."
|
||||
|
||||
#: minecraft\minecraft.py:354
|
||||
msgid "Server removed from this channel."
|
||||
msgstr "Сервер видалено з цього каналу."
|
||||
|
||||
#: minecraft\minecraft.py:361
|
||||
#, docstring
|
||||
msgid "Include players joining or leaving the server in notifications."
|
||||
msgstr "Включати гравців, які приєднуються до сервера або залишають його, у сповіщення."
|
||||
|
||||
#: minecraft\minecraft.py:368
|
||||
msgid "I will not check players for the notifications."
|
||||
msgstr "Я не буду перевіряти гравців на наявність сповіщень."
|
||||
|
||||
#: minecraft\minecraft.py:370
|
||||
msgid "I will check players for the notifications."
|
||||
msgstr "Я перевірю гравців на наявність сповіщень."
|
||||
|
||||
#: minecraft\minecraft.py:377
|
||||
#, docstring
|
||||
msgid "Edit the last message sent for changes."
|
||||
msgstr "Відредагуйте останнє надіслане повідомлення для внесення змін."
|
||||
|
||||
#: minecraft\minecraft.py:382
|
||||
msgid "I will not edit my last message for the notifications."
|
||||
msgstr "Я не буду редагувати своє останнє повідомлення для сповіщень."
|
||||
|
||||
#: minecraft\minecraft.py:384
|
||||
msgid "I will edit my last message for the notifications."
|
||||
msgstr "Я відредагую своє останнє повідомлення для сповіщень."
|
||||
|
||||
#: minecraft\minecraft.py:389
|
||||
#, docstring
|
||||
msgid "Force check Minecraft Java servers in Config."
|
||||
msgstr "Примусова перевірка Java-серверів Minecraft у налаштуваннях."
|
||||
|
||||
#: minecraft\minecraft.py:391
|
||||
msgid "Servers checked."
|
||||
msgstr "Сервери перевірено."
|
||||
|
||||
#: minecraft\minecraft.py:397
|
||||
#, docstring
|
||||
msgid "Get an embed for check loop status."
|
||||
msgstr "Отримайте вбудовування для перевірки стану циклу."
|
||||
|
451
minecraft/minecraft.py
Normal file
451
minecraft/minecraft.py
Normal file
|
@ -0,0 +1,451 @@
|
|||
from AAA3A_utils import Cog, Loop, Menu # isort:skip
|
||||
from redbot.core import commands, Config # isort:skip
|
||||
from redbot.core.bot import Red # isort:skip
|
||||
from redbot.core.i18n import Translator, cog_i18n # isort:skip
|
||||
import discord # isort:skip
|
||||
import typing # isort:skip
|
||||
import typing_extensions # isort:skip
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import re
|
||||
from io import BytesIO
|
||||
from uuid import UUID
|
||||
|
||||
import aiohttp
|
||||
from mcstatus import JavaServer
|
||||
from redbot.core.utils.chat_formatting import box, pagify
|
||||
|
||||
# Credits:
|
||||
# General repo credits.
|
||||
# Thanks to Fixator for the code to get informations about Minecraft servers (https://github.com/fixator10/Fixator10-Cogs/blob/V3/minecraftdata/minecraftdata.py)!
|
||||
|
||||
_: Translator = Translator("Minecraft", __file__)
|
||||
|
||||
|
||||
class MCPlayer:
|
||||
def __init__(self, name: str, uuid: str) -> None:
|
||||
self.name: str = name
|
||||
self.uuid: str = uuid
|
||||
self.dashed_uuid: str = str(UUID(self.uuid))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
async def convert(cls, ctx: commands.Context, argument: str) -> typing_extensions.Self:
|
||||
cog = ctx.bot.get_cog("Minecraft")
|
||||
try:
|
||||
async with cog._session.get(
|
||||
f"https://api.mojang.com/users/profiles/minecraft/{argument}",
|
||||
raise_for_status=True,
|
||||
) as r:
|
||||
response_data = await r.json()
|
||||
except aiohttp.ContentTypeError:
|
||||
response_data = None
|
||||
except aiohttp.ClientResponseError as e:
|
||||
raise commands.BadArgument(
|
||||
_("Unable to get data from Minecraft API: {e.message}.").format(e=e)
|
||||
)
|
||||
if response_data is None or "id" not in response_data:
|
||||
raise commands.BadArgument(
|
||||
_("{argument} not found on Mojang servers.").format(argument=argument)
|
||||
)
|
||||
uuid = str(response_data["id"])
|
||||
name = str(response_data["name"])
|
||||
try:
|
||||
return cls(name=name, uuid=uuid)
|
||||
except ValueError:
|
||||
raise commands.BadArgument(
|
||||
_("{argument} is found, but has incorrect UUID.").format(argument=argument)
|
||||
)
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
class Minecraft(Cog):
|
||||
"""A cog to display informations about Minecraft Java users and servers, and notify for each change of a server!"""
|
||||
|
||||
def __init__(self, bot: Red) -> None:
|
||||
super().__init__(bot=bot)
|
||||
|
||||
self._session: aiohttp.ClientSession = None
|
||||
self.cache: typing.Dict[int, typing.Dict[str, dict]] = {}
|
||||
|
||||
self.config: Config = Config.get_conf(
|
||||
self,
|
||||
identifier=205192943327321000143939875896557571750,
|
||||
force_registration=True,
|
||||
)
|
||||
self.config.register_channel(
|
||||
servers={},
|
||||
check_players=False,
|
||||
edit_last_message=False,
|
||||
)
|
||||
|
||||
async def cog_load(self) -> None:
|
||||
await super().cog_load()
|
||||
self._session: aiohttp.ClientSession = aiohttp.ClientSession()
|
||||
self.loops.append(
|
||||
Loop(
|
||||
cog=self,
|
||||
name="Check Minecraft Servers",
|
||||
function=self.check_servers,
|
||||
minutes=1,
|
||||
)
|
||||
)
|
||||
|
||||
async def cog_unload(self) -> None:
|
||||
await self._session.close()
|
||||
await super().cog_unload()
|
||||
|
||||
async def check_servers(self) -> None:
|
||||
all_channels = await self.config.all_channels()
|
||||
for channel_id in all_channels:
|
||||
channel = self.bot.get_channel(channel_id)
|
||||
if channel is None:
|
||||
continue
|
||||
if channel.id not in self.cache:
|
||||
self.cache[channel.id] = {}
|
||||
servers = all_channels[channel_id]["servers"]
|
||||
check_players = all_channels[channel_id]["check_players"]
|
||||
for server_url in servers:
|
||||
try:
|
||||
server: JavaServer = await JavaServer.async_lookup(address=server_url.lower())
|
||||
status = await server.async_status()
|
||||
except (asyncio.CancelledError, TimeoutError):
|
||||
continue
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"No data found for {server_url} server in {channel.id} channel in {channel.guild.id} guild.",
|
||||
exc_info=e,
|
||||
)
|
||||
continue
|
||||
if check_players and "sample" in status.raw["players"]:
|
||||
players = {player["id"]: player for player in status.raw["players"]["sample"]}
|
||||
players = [players[_id] for _id in set(list(players.keys()))]
|
||||
else:
|
||||
players = {}
|
||||
status.raw["players"]["sample"] = players
|
||||
if server_url not in self.cache[channel.id]:
|
||||
self.cache[channel.id][server_url] = {"server": server, "status": status}
|
||||
continue
|
||||
if status.raw != self.cache[channel.id][server_url]["status"].raw:
|
||||
if "This server is offline." in (
|
||||
await self.clear_mcformatting(status.description)
|
||||
) and "This server is offline." in (
|
||||
await self.clear_mcformatting(
|
||||
self.cache[channel.id][server_url]["status"].description
|
||||
)
|
||||
): # Minecraft ADS
|
||||
continue
|
||||
embed, icon = await self.get_embed(server, status)
|
||||
servers = await self.config.channel(channel).servers()
|
||||
if isinstance(servers, typing.List):
|
||||
servers = {server: None for server in servers}
|
||||
if (
|
||||
await self.config.channel(channel).edit_last_message()
|
||||
and servers[server_url] is not None
|
||||
):
|
||||
try:
|
||||
message = await channel.get_partial_message(servers[server_url]).edit(
|
||||
embed=embed, attachments=[icon]
|
||||
)
|
||||
except discord.HTTPException:
|
||||
message = await channel.send(embed=embed, file=icon)
|
||||
else:
|
||||
message = await channel.send(embed=embed, file=icon)
|
||||
servers[server_url] = message.id
|
||||
await self.config.channel(channel).servers.set(servers)
|
||||
self.cache[channel.id][server_url] = {"server": server, "status": status}
|
||||
|
||||
async def get_embed(self, server: JavaServer, status) -> discord.Embed:
|
||||
server_description = await self.clear_mcformatting(status.description)
|
||||
embed: discord.Embed = discord.Embed(
|
||||
title=f"{server.address.host}:{server.address.port}",
|
||||
description=box(server_description),
|
||||
)
|
||||
embed.color = (
|
||||
discord.Color.red()
|
||||
if "This server is offline." in server_description
|
||||
else (
|
||||
discord.Color.orange()
|
||||
if "This server is currently stopping." in server_description
|
||||
else discord.Color.green()
|
||||
)
|
||||
)
|
||||
icon_file = None
|
||||
icon = (
|
||||
discord.File(
|
||||
icon_file := BytesIO(
|
||||
base64.b64decode(status.icon.removeprefix("data:image/png;base64,"))
|
||||
),
|
||||
filename="icon.png",
|
||||
)
|
||||
if status.icon
|
||||
else None
|
||||
)
|
||||
if icon:
|
||||
embed.set_thumbnail(url="attachment://icon.png")
|
||||
embed.add_field(name=_("Latency"), value=f"{status.latency:.2f} ms")
|
||||
embed.add_field(
|
||||
name=_("Players"),
|
||||
value="{status.players.online}/{status.players.max}\n{players_list}".format(
|
||||
status=status,
|
||||
players_list=(
|
||||
box(
|
||||
list(
|
||||
pagify(
|
||||
await self.clear_mcformatting(
|
||||
"\n".join([p.name for p in status.players.sample])
|
||||
),
|
||||
page_length=992,
|
||||
)
|
||||
)[0]
|
||||
)
|
||||
if status.players.sample
|
||||
else ""
|
||||
),
|
||||
),
|
||||
)
|
||||
embed.add_field(
|
||||
name=_("Version"),
|
||||
value=f"{status.version.name}\nProtocol: {status.version.protocol}",
|
||||
)
|
||||
if icon_file is not None:
|
||||
icon_file.close()
|
||||
return embed, icon
|
||||
|
||||
async def clear_mcformatting(self, formatted_str) -> str:
|
||||
"""Remove Minecraft-formatting"""
|
||||
if not isinstance(formatted_str, dict):
|
||||
return re.sub(r"\xA7[0-9A-FK-OR]", "", formatted_str, flags=re.IGNORECASE)
|
||||
clean = ""
|
||||
async for text in self.gen_dict_extract("text", formatted_str):
|
||||
clean += text
|
||||
return re.sub(r"\xA7[0-9A-FK-OR]", "", clean, flags=re.IGNORECASE)
|
||||
|
||||
async def gen_dict_extract(self, key: str, var: dict) -> str:
|
||||
if not hasattr(var, "items"):
|
||||
return
|
||||
for k, v in var.items():
|
||||
if k == key:
|
||||
yield v
|
||||
if isinstance(v, typing.Dict):
|
||||
async for result in self.gen_dict_extract(key, v):
|
||||
yield result
|
||||
elif isinstance(v, typing.List):
|
||||
for d in v:
|
||||
async for result in self.gen_dict_extract(key, d):
|
||||
yield result
|
||||
|
||||
@commands.hybrid_group()
|
||||
async def minecraft(self, ctx: commands.Context):
|
||||
"""Get informations about Minecraft Java."""
|
||||
pass
|
||||
|
||||
@commands.bot_has_permissions(attach_files=True, embed_links=True)
|
||||
@minecraft.command()
|
||||
async def getplayerskin(
|
||||
self, ctx: commands.Context, player: MCPlayer, overlay: bool = False
|
||||
) -> None:
|
||||
"""Get Minecraft Java player skin by name."""
|
||||
uuid = player.uuid
|
||||
stripname = player.name.strip("_")
|
||||
files = []
|
||||
try:
|
||||
async with self._session.get(
|
||||
f"https://crafatar.com/renders/head/{uuid}",
|
||||
params="overlay" if overlay else None,
|
||||
) as s:
|
||||
files.append(
|
||||
discord.File(BytesIO(await s.read()), filename=f"{stripname}_head.png")
|
||||
)
|
||||
async with self._session.get(f"https://crafatar.com/skins/{uuid}") as s:
|
||||
files.append(discord.File(BytesIO(await s.read()), filename=f"{stripname}.png"))
|
||||
async with self._session.get(
|
||||
f"https://crafatar.com/renders/body/{uuid}.png",
|
||||
params="overlay" if overlay else None,
|
||||
) as s:
|
||||
files.append(
|
||||
discord.File(BytesIO(await s.read()), filename=f"{stripname}_body.png")
|
||||
)
|
||||
except aiohttp.ClientResponseError as e:
|
||||
raise commands.UserFeedbackCheckFailure(
|
||||
_("Unable to get data from Crafatar: {}").format(e.message)
|
||||
)
|
||||
embed: discord.Embed = discord.Embed(
|
||||
timestamp=ctx.message.created_at, color=await ctx.embed_color()
|
||||
)
|
||||
embed.set_author(
|
||||
name=player.name,
|
||||
icon_url=f"attachment://{stripname}_head.png",
|
||||
url=f"https://crafatar.com/skins/{uuid}",
|
||||
)
|
||||
embed.set_thumbnail(url=f"attachment://{stripname}.png")
|
||||
embed.set_image(url=f"attachment://{stripname}_body.png")
|
||||
embed.set_footer(text=_("Provided by Crafatar."), icon_url="https://crafatar.com/logo.png")
|
||||
await ctx.send(embed=embed, files=files)
|
||||
|
||||
@commands.bot_has_permissions(attach_files=True, embed_links=True)
|
||||
@minecraft.command()
|
||||
async def getserver(self, ctx: commands.Context, server_url: str) -> None:
|
||||
"""Get informations about a Minecraft Java server."""
|
||||
try:
|
||||
server: JavaServer = await JavaServer.async_lookup(address=server_url.lower())
|
||||
status = await server.async_status()
|
||||
except Exception:
|
||||
raise commands.UserFeedbackCheckFailure(
|
||||
_(
|
||||
"No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
)
|
||||
)
|
||||
embed, icon = await self.get_embed(server, status)
|
||||
await ctx.send(embed=embed, file=icon)
|
||||
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
@minecraft.command(aliases=["add", "+"])
|
||||
async def addserver(
|
||||
self, ctx: commands.Context, channel: typing.Optional[discord.TextChannel], server_url: str
|
||||
) -> None:
|
||||
"""Add a Minecraft Java server in Config to get automatically new status."""
|
||||
if channel is None:
|
||||
channel = ctx.channel
|
||||
channel_permissions = channel.permissions_for(ctx.me)
|
||||
if (
|
||||
not channel_permissions.view_channel
|
||||
or not channel_permissions.read_messages
|
||||
or not channel_permissions.read_message_history
|
||||
or not channel_permissions.embed_links
|
||||
):
|
||||
raise commands.UserFeedbackCheckFailure(
|
||||
_(
|
||||
"I don't have sufficient permissions in this channel to send messages with embeds."
|
||||
)
|
||||
)
|
||||
servers = await self.config.channel(channel).servers()
|
||||
if server_url.lower() in servers:
|
||||
raise commands.UserFeedbackCheckFailure(_("This server has already been added."))
|
||||
try:
|
||||
server: JavaServer = await JavaServer.async_lookup(address=server_url.lower())
|
||||
await server.async_status()
|
||||
except Exception:
|
||||
raise commands.UserFeedbackCheckFailure(
|
||||
_(
|
||||
"No data found for this Minecraft server. Maybe it doesn't exist or its data are temporarily unavailable."
|
||||
)
|
||||
)
|
||||
if isinstance(servers, typing.List):
|
||||
servers = {server: None for server in servers}
|
||||
servers[server_url.lower()] = None # last message
|
||||
await self.config.channel(channel).servers.set(servers)
|
||||
await ctx.send(_("Server added to this channel."))
|
||||
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
@minecraft.command(aliases=["remove", "-"])
|
||||
async def removeserver(
|
||||
self, ctx: commands.Context, channel: typing.Optional[discord.TextChannel], server_url: str
|
||||
) -> None:
|
||||
"""Remove a Minecraft Java server in Config."""
|
||||
if channel is None:
|
||||
channel = ctx.channel
|
||||
servers = await self.config.channel(channel).servers()
|
||||
if server_url.lower() not in servers:
|
||||
raise commands.UserFeedbackCheckFailure(_("This server isn't in the Config."))
|
||||
if isinstance(servers, typing.List):
|
||||
servers = {server: None for server in servers}
|
||||
del servers[server_url.lower()]
|
||||
await self.config.channel(channel).servers.set(servers)
|
||||
await ctx.send(_("Server removed from this channel."))
|
||||
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
@minecraft.command()
|
||||
async def checkplayers(
|
||||
self, ctx: commands.Context, channel: typing.Optional[discord.TextChannel], state: bool
|
||||
) -> None:
|
||||
"""Include players joining or leaving the server in notifications."""
|
||||
if channel is None:
|
||||
channel = ctx.channel
|
||||
await self.config.channel(channel).check_players.set(state)
|
||||
if not state:
|
||||
for server_url in self.cache[channel.id]:
|
||||
self.cache[channel.id][server_url]["status"].raw["players"]["sample"] = {}
|
||||
await ctx.send(_("I will not check players for the notifications."))
|
||||
else:
|
||||
await ctx.send(_("I will check players for the notifications."))
|
||||
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
@minecraft.command()
|
||||
async def editlastmessage(
|
||||
self, ctx: commands.Context, channel: typing.Optional[discord.TextChannel], state: bool
|
||||
) -> None:
|
||||
"""Edit the last message sent for changes."""
|
||||
if channel is None:
|
||||
channel = ctx.channel
|
||||
await self.config.channel(channel).edit_last_message.set(state)
|
||||
if not state:
|
||||
await ctx.send(_("I will not edit my last message for the notifications."))
|
||||
else:
|
||||
await ctx.send(_("I will edit my last message for the notifications."))
|
||||
|
||||
@commands.is_owner()
|
||||
@minecraft.command(hidden=True)
|
||||
async def forcecheck(self, ctx: commands.Context) -> None:
|
||||
"""Force check Minecraft Java servers in Config."""
|
||||
await self.check_servers()
|
||||
await ctx.send(_("Servers checked."))
|
||||
|
||||
@commands.is_owner()
|
||||
@commands.bot_has_permissions(embed_links=True)
|
||||
@minecraft.command(hidden=True)
|
||||
async def getdebugloopsstatus(self, ctx: commands.Context):
|
||||
"""Get an embed for check loop status."""
|
||||
embeds = [loop.get_debug_embed() for loop in self.loops]
|
||||
await Menu(pages=embeds).start(ctx)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_assistant_cog_add(
|
||||
self, assistant_cog: typing.Optional[commands.Cog] = None
|
||||
) -> None: # Vert's Assistant integration/third party.
|
||||
if assistant_cog is None:
|
||||
return self.get_minecraft_java_server_for_assistant
|
||||
schema = {
|
||||
"name": "get_minecraft_java_server_for_assistant",
|
||||
"description": "Get informations about a Minecraft Java server.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_url": {
|
||||
"type": "string",
|
||||
"description": "The URL of the Minecraft Java server.",
|
||||
},
|
||||
},
|
||||
"required": ["server_url"],
|
||||
},
|
||||
}
|
||||
await assistant_cog.register_function(cog_name=self.qualified_name, schema=schema)
|
||||
|
||||
async def get_minecraft_java_server_for_assistant(self, server_url: str, *args, **kwargs):
|
||||
try:
|
||||
server: JavaServer = await JavaServer.async_lookup(address=server_url.lower())
|
||||
status = await server.async_status()
|
||||
except Exception:
|
||||
return "No data found for this Minecraft Java server."
|
||||
server_description = await self.clear_mcformatting(status.description)
|
||||
data = {
|
||||
"Host & Port": f"{server.address.host}:{server.address.port}",
|
||||
"Description": box(server_description),
|
||||
"Status": (
|
||||
"Offline."
|
||||
if "This server is offline." in server_description
|
||||
else (
|
||||
"Currently stopping."
|
||||
if "This server is currently stopping." in server_description
|
||||
else "Online."
|
||||
)
|
||||
),
|
||||
"Latency": f"{status.latency:.2f} ms",
|
||||
"Players": f"{status.players.online}/{status.players.max}",
|
||||
"Version": status.version.name,
|
||||
"Protocol": status.version.protocol,
|
||||
}
|
||||
return [f"{key}: {value}\n" for key, value in data.items() if value is not None]
|
1
minecraft/utils_version.json
Normal file
1
minecraft/utils_version.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"needed_utils_version": 7.0}
|
13
mod/__init__.py
Normal file
13
mod/__init__.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from .mod import Mod
|
||||
|
||||
__red_end_user_data_statement__ = (
|
||||
"This cog stores user data to actively maintain server moderation.\n"
|
||||
"It will not respect data deletion by end users as the data kept is the minimum "
|
||||
"needed for operation of an anti-abuse measure, nor can end users request "
|
||||
"their data from this cog since it only stores a discord ID.\n"
|
||||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
cog = Mod(bot)
|
||||
await bot.add_cog(cog)
|
59
mod/_tagscript.py
Normal file
59
mod/_tagscript.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from typing import Any, Dict, List
|
||||
|
||||
import TagScriptEngine as tse
|
||||
from redbot.core import commands
|
||||
from redbot.core.utils.chat_formatting import humanize_number
|
||||
|
||||
kick_message: str = "Done. That felt good."
|
||||
ban_message: str = "Done. That felt good."
|
||||
tempban_message: str = "Done. Enough chaos for now."
|
||||
unban_message: str = "Unbanned the user from this server."
|
||||
|
||||
TAGSCRIPT_LIMIT: int = 10_000
|
||||
|
||||
blocks: List[tse.Block] = [
|
||||
tse.LooseVariableGetterBlock(),
|
||||
tse.AssignmentBlock(),
|
||||
tse.EmbedBlock(),
|
||||
]
|
||||
|
||||
tagscript_engine: tse.Interpreter = tse.Interpreter(blocks)
|
||||
|
||||
|
||||
def process_tagscript(content: str, seed_variables: Dict[str, tse.Adapter] = {}) -> Dict[str, Any]:
|
||||
output: tse.Response = tagscript_engine.process(content, seed_variables)
|
||||
kwargs: Dict[str, Any] = {}
|
||||
if output.body:
|
||||
kwargs["content"] = output.body[:2000]
|
||||
if embed := output.actions.get("embed"):
|
||||
kwargs["embed"] = embed
|
||||
return kwargs
|
||||
|
||||
|
||||
def validate_tagscript(tagscript: str) -> bool:
|
||||
length = len(tagscript)
|
||||
if length > TAGSCRIPT_LIMIT:
|
||||
raise TagCharacterLimitReached(TAGSCRIPT_LIMIT, length)
|
||||
return True
|
||||
|
||||
|
||||
class TagError(Exception):
|
||||
"""Base exception class."""
|
||||
|
||||
|
||||
class TagCharacterLimitReached(TagError):
|
||||
"""Taised when the Tagscript character limit is reached."""
|
||||
|
||||
def __init__(self, limit: int, length: int):
|
||||
super().__init__(
|
||||
f"Tagscript cannot be longer than {humanize_number(limit)} (**{humanize_number(length)}**)."
|
||||
)
|
||||
|
||||
|
||||
class TagScriptConverter(commands.Converter[str]):
|
||||
async def convert(self, ctx: commands.Context, argument: str) -> str:
|
||||
try:
|
||||
validate_tagscript(argument)
|
||||
except TagError as e:
|
||||
raise commands.BadArgument(str(e))
|
||||
return argument
|
18
mod/info.json
Normal file
18
mod/info.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"author": [
|
||||
"flare(flare#0001)"
|
||||
],
|
||||
"install_msg": "This cog subclasses core Mod. It may be finicky so be warned. This cog implements the features of Red#5532 until it is merged into core Red.",
|
||||
"name": "Mod",
|
||||
"disabled": false,
|
||||
"short": "Mod with custom messages.",
|
||||
"description": "Core mod with the inclusion of custom messages for banning, kicking and unbanning.",
|
||||
"tags": [
|
||||
"mod"
|
||||
],
|
||||
"requirements": [
|
||||
"AdvancedTagScriptEngine"
|
||||
],
|
||||
"min_bot_version": "3.5.0",
|
||||
"hidden": false
|
||||
}
|
836
mod/mod.py
Normal file
836
mod/mod.py
Normal file
|
@ -0,0 +1,836 @@
|
|||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Literal, Optional, Tuple, Union
|
||||
|
||||
import discord
|
||||
import TagScriptEngine as tse
|
||||
from redbot.cogs.mod.mod import Mod as ModClass
|
||||
from redbot.cogs.mod.utils import is_allowed_by_hierarchy
|
||||
from redbot.core import Config, app_commands, checks, commands, modlog
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.utils.chat_formatting import bold, box, humanize_timedelta
|
||||
from redbot.core.utils.mod import get_audit_reason
|
||||
|
||||
from ._tagscript import (
|
||||
TagScriptConverter,
|
||||
ban_message,
|
||||
kick_message,
|
||||
process_tagscript,
|
||||
tempban_message,
|
||||
unban_message,
|
||||
)
|
||||
|
||||
log = logging.getLogger("red.flarecogs.mod")
|
||||
|
||||
from discord.ext import commands as dpy_commands
|
||||
from discord.ext.commands import BadArgument
|
||||
|
||||
ID_REGEX = re.compile(r"([0-9]{15,20})")
|
||||
USER_MENTION_REGEX = re.compile(r"<@!?([0-9]{15,21})>$")
|
||||
|
||||
|
||||
# https://github.com/flaree/Red-DiscordBot/blob/FR-custom-bankick-msgs/redbot/core/commands/converter.py#L207
|
||||
class RawUserIdConverter(dpy_commands.Converter):
|
||||
"""
|
||||
Converts ID or user mention to an `int`.
|
||||
Useful for commands like ``[p]ban`` or ``[p]unban`` where the bot is not necessarily
|
||||
going to share any servers with the user that a moderator wants to ban/unban.
|
||||
This converter doesn't check if the ID/mention points to an actual user
|
||||
but it won't match IDs and mentions that couldn't possibly be valid.
|
||||
For example, the converter will not match on "123" because the number doesn't have
|
||||
enough digits to be valid ID but, it will match on "12345678901234567" even though
|
||||
there is no user with such ID.
|
||||
"""
|
||||
|
||||
async def convert(self, ctx, argument: str) -> int:
|
||||
# This is for the hackban and unban commands, where we receive IDs that
|
||||
# are most likely not in the guild.
|
||||
# Mentions are supported, but most likely won't ever be in cache.
|
||||
|
||||
if match := ID_REGEX.match(argument) or USER_MENTION_REGEX.match(argument):
|
||||
return int(match.group(1))
|
||||
|
||||
raise BadArgument(("'{input}' doesn't look like a valid user ID.").format(input=argument))
|
||||
|
||||
|
||||
class Mod(ModClass):
|
||||
"""Mod with custom messages."""
|
||||
|
||||
modset = ModClass.modset.copy()
|
||||
|
||||
__version__ = "1.3.0"
|
||||
|
||||
def format_help_for_context(self, ctx: commands.Context):
|
||||
pre_processed = super().format_help_for_context(ctx)
|
||||
return f"{pre_processed}\nCog Version: {self.__version__}"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
super().__init__(bot)
|
||||
self.bot: Red = bot
|
||||
self._config: Config = Config.get_conf(self, 95932766180343808, force_registration=True)
|
||||
self._config.register_guild(
|
||||
**{
|
||||
"kick_message": kick_message,
|
||||
"ban_message": ban_message,
|
||||
"tempban_message": tempban_message,
|
||||
"unban_message": unban_message,
|
||||
"require_reason": False,
|
||||
}
|
||||
)
|
||||
|
||||
async def red_get_data_for_user(self, *, user_id: int):
|
||||
# this cog does not story any data
|
||||
return {}
|
||||
|
||||
async def red_delete_data_for_user(
|
||||
self,
|
||||
*,
|
||||
requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
|
||||
user_id: int,
|
||||
):
|
||||
return None
|
||||
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def kickmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
|
||||
"""Set the message sent when a user is kicked.
|
||||
|
||||
**Blocks:**
|
||||
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
|
||||
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
|
||||
|
||||
**Variables:**
|
||||
- `{user}`: [member that was kicked.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{moderator}`: [modrator that kicked the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{reason}`: reason for the kick.
|
||||
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
|
||||
"""
|
||||
guild = ctx.guild
|
||||
await self._config.guild(guild).kick_message.set(message)
|
||||
await ctx.send("Kick message updated:\n{}".format(box(str(message), lang="json")))
|
||||
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def banmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
|
||||
"""Set the message sent when a user is banned.
|
||||
|
||||
**Blocks:**
|
||||
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
|
||||
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
|
||||
|
||||
**Variables:**
|
||||
- `{user}`: [member that was banned.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{moderator}`: [modrator that banned the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{reason}`: reason for the ban.
|
||||
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
|
||||
- `{days}`: number of days of messages deleted.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
await self._config.guild(guild).ban_message.set(message)
|
||||
await ctx.send("Ban message updated:\n{}".format(box(str(message), lang="json")))
|
||||
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def tempbanmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
|
||||
"""Set the message sent when a user is tempbanned.
|
||||
|
||||
**Blocks:**
|
||||
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
|
||||
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
|
||||
|
||||
**Variables:**
|
||||
- `{user}`: [member that was tempbanned.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{moderator}`: [modrator that tempbanned the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{reason}`: reason for the tempban.
|
||||
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
|
||||
- `{days}`: number of days of messages deleted.
|
||||
- `{duration}`: duration of the tempban.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
await self._config.guild(guild).tempban_message.set(message)
|
||||
await ctx.send("Tempban message updated:\n{}".format(box(str(message), lang="json")))
|
||||
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def unbanmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
|
||||
"""Set the message sent when a user is unbanned.
|
||||
|
||||
**Blocks:**
|
||||
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
|
||||
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
|
||||
|
||||
**Variables:**
|
||||
- `{user}`: [member that was tempbanned.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{moderator}`: [modrator that tempbanned the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
|
||||
- `{reason}`: reason for the tempban.
|
||||
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
|
||||
"""
|
||||
guild = ctx.guild
|
||||
await self._config.guild(guild).unban_message.set(message)
|
||||
await ctx.send("Unban message updated:\n{}".format(box(str(message), lang="json")))
|
||||
|
||||
@modset.command(name="showmessages")
|
||||
async def modset_showmessages(self, ctx: commands.Context):
|
||||
"""Show the current messages for moderation commands."""
|
||||
messageData = await self._config.guild(ctx.guild).all()
|
||||
msg = "Kick Message: {kick_message}\n".format(kick_message=messageData["kick_message"])
|
||||
msg += "Ban Message: {ban_message}\n".format(ban_message=messageData["ban_message"])
|
||||
msg += "Tempban Message: {tempban_message}\n".format(
|
||||
tempban_message=messageData["tempban_message"]
|
||||
)
|
||||
msg += "Unban Message: {unban_message}\n".format(
|
||||
unban_message=messageData["unban_message"]
|
||||
)
|
||||
await ctx.send(box(msg))
|
||||
|
||||
@modset.command(name="reasons")
|
||||
async def modset_require_reason(self, ctx: commands.Context, value: bool):
|
||||
"""Set whether a reason is required for moderation actions."""
|
||||
await self._config.guild(ctx.guild).require_reason.set(value)
|
||||
await ctx.send(f"Reason requirement set to {value}")
|
||||
|
||||
kick = None
|
||||
|
||||
@commands.hybrid_command()
|
||||
@app_commands.describe(member="The member to kick.", reason="The reason for kicking the user.")
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(kick_members=True)
|
||||
@checks.admin_or_permissions(kick_members=True)
|
||||
async def kick(self, ctx: commands.Context, member: discord.Member, *, reason: str = None):
|
||||
"""
|
||||
Kick a user.
|
||||
Examples:
|
||||
- `[p]kick 428675506947227648 wanted to be kicked.`
|
||||
This will kick the user with ID 428675506947227648 from the server.
|
||||
- `[p]kick @Twentysix wanted to be kicked.`
|
||||
This will kick Twentysix from the server.
|
||||
If a reason is specified, it will be the reason that shows up
|
||||
in the audit log.
|
||||
"""
|
||||
require_reason = await self._config.guild(ctx.guild).require_reason()
|
||||
if require_reason and reason is None:
|
||||
await ctx.send("You must provide a reason for this action.")
|
||||
return
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
if author == member:
|
||||
await ctx.send(
|
||||
("I cannot let you do that. Self-harm is bad {emoji}").format(
|
||||
emoji="\N{PENSIVE FACE}"
|
||||
)
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.config, guild, author, member):
|
||||
await ctx.send(
|
||||
(
|
||||
"I cannot let you do that. You are "
|
||||
"not higher than the user in the role "
|
||||
"hierarchy."
|
||||
)
|
||||
)
|
||||
return
|
||||
elif ctx.guild.me.top_role <= member.top_role or member == ctx.guild.owner:
|
||||
await ctx.send(("I cannot do that due to Discord hierarchy rules."))
|
||||
return
|
||||
audit_reason = get_audit_reason(author, reason, shorten=True)
|
||||
toggle = await self.config.guild(guild).dm_on_kickban()
|
||||
if toggle:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
em = discord.Embed(
|
||||
title=bold(("You have been kicked from {guild}.").format(guild=guild)),
|
||||
color=await self.bot.get_embed_color(member),
|
||||
)
|
||||
em.add_field(
|
||||
name=("**Reason**"),
|
||||
value=reason if reason is not None else ("No reason was given."),
|
||||
inline=False,
|
||||
)
|
||||
await member.send(embed=em)
|
||||
try:
|
||||
await guild.kick(member, reason=audit_reason)
|
||||
log.info("{}({}) kicked {}({})".format(author.name, author.id, member.name, member.id))
|
||||
except discord.errors.Forbidden:
|
||||
await ctx.send("I'm not allowed to do that.")
|
||||
except Exception:
|
||||
log.exception(
|
||||
"{}({}) attempted to kick {}({}), but an error occurred.".format(
|
||||
author.name, author.id, member.name, member.id
|
||||
)
|
||||
)
|
||||
else:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"kick",
|
||||
member,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
message = await self._config.guild(ctx.guild).kick_message()
|
||||
kwargs = process_tagscript(
|
||||
message,
|
||||
{
|
||||
"user": tse.MemberAdapter(member),
|
||||
"moderator": tse.MemberAdapter(author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
},
|
||||
)
|
||||
if not kwargs:
|
||||
await self._config.guild(ctx.guild).kick_message.clear()
|
||||
kwargs = process_tagscript(
|
||||
kick_message,
|
||||
{
|
||||
"user": tse.MemberAdapter(member),
|
||||
"moderator": tse.MemberAdapter(author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
},
|
||||
)
|
||||
await ctx.send(**kwargs)
|
||||
|
||||
tempban = None
|
||||
|
||||
@commands.hybrid_command()
|
||||
@app_commands.describe(
|
||||
member="The member to tempban.",
|
||||
reason="The reason for tempbanning the user.",
|
||||
duration="The duration of the tempban.",
|
||||
days="The number of days of messages to delete.",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def tempban(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
member: discord.Member,
|
||||
duration: Optional[commands.TimedeltaConverter] = None,
|
||||
days: Optional[int] = None,
|
||||
*,
|
||||
reason: str = None,
|
||||
):
|
||||
"""Temporarily ban a user from this server.
|
||||
`duration` is the amount of time the user should be banned for.
|
||||
`days` is the amount of days of messages to cleanup on tempban.
|
||||
Examples:
|
||||
- `[p]tempban @Twentysix Because I say so`
|
||||
This will ban Twentysix for the default amount of time set by an administrator.
|
||||
- `[p]tempban @Twentysix 15m You need a timeout`
|
||||
This will ban Twentysix for 15 minutes.
|
||||
- `[p]tempban 428675506947227648 1d2h15m 5 Evil person`
|
||||
This will ban the user with ID 428675506947227648 for 1 day 2 hours 15 minutes and will delete the last 5 days of their messages.
|
||||
"""
|
||||
require_reason = await self._config.guild(ctx.guild).require_reason()
|
||||
if require_reason and reason is None:
|
||||
await ctx.send("You must provide a reason for this action.")
|
||||
return
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
|
||||
if author == member:
|
||||
await ctx.send(
|
||||
("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.config, guild, author, member):
|
||||
await ctx.send(
|
||||
(
|
||||
"I cannot let you do that. You are "
|
||||
"not higher than the user in the role "
|
||||
"hierarchy."
|
||||
)
|
||||
)
|
||||
return
|
||||
elif guild.me.top_role <= member.top_role or member == guild.owner:
|
||||
await ctx.send(("I cannot do that due to Discord hierarchy rules."))
|
||||
return
|
||||
|
||||
guild_data = await self.config.guild(guild).all()
|
||||
|
||||
if duration is None:
|
||||
duration = timedelta(seconds=guild_data["default_tempban_duration"])
|
||||
unban_time = datetime.now(timezone.utc) + duration
|
||||
|
||||
if days is None:
|
||||
days = guild_data["default_days"]
|
||||
|
||||
if not (0 <= days <= 7):
|
||||
await ctx.send(("Invalid days. Must be between 0 and 7."))
|
||||
return
|
||||
invite = await self.get_invite_for_reinvite(ctx, int(duration.total_seconds() + 86400))
|
||||
|
||||
await self.config.member(member).banned_until.set(unban_time.timestamp())
|
||||
async with self.config.guild(guild).current_tempbans() as current_tempbans:
|
||||
current_tempbans.append(member.id)
|
||||
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
# We don't want blocked DMs preventing us from banning
|
||||
msg = ("You have been temporarily banned from {server_name} until {date}.").format(
|
||||
server_name=guild.name, date=discord.utils.format_dt(unban_time)
|
||||
)
|
||||
if guild_data["dm_on_kickban"] and reason:
|
||||
msg += ("\n\n**Reason:** {reason}").format(reason=reason)
|
||||
if invite:
|
||||
msg += ("\n\nHere is an invite for when your ban expires: {invite_link}").format(
|
||||
invite_link=invite
|
||||
)
|
||||
await member.send(msg)
|
||||
|
||||
audit_reason = get_audit_reason(author, reason, shorten=True)
|
||||
|
||||
try:
|
||||
await guild.ban(member, reason=audit_reason, delete_message_days=days)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(("I can't do that for some reason."))
|
||||
except discord.HTTPException:
|
||||
await ctx.send(("Something went wrong while banning."))
|
||||
else:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"tempban",
|
||||
member,
|
||||
author,
|
||||
reason,
|
||||
unban_time,
|
||||
)
|
||||
message = await self._config.guild(ctx.guild).tempban_message()
|
||||
humanized_duration = humanize_timedelta(timedelta=duration)
|
||||
kwargs = process_tagscript(
|
||||
message,
|
||||
{
|
||||
"user": tse.MemberAdapter(member),
|
||||
"moderator": tse.MemberAdapter(author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
"duration": tse.StringAdapter(humanized_duration),
|
||||
"days": tse.IntAdapter(days),
|
||||
},
|
||||
)
|
||||
if not kwargs:
|
||||
await self._config.guild(ctx.guild).tempban_message.clear()
|
||||
kwargs = process_tagscript(
|
||||
tempban_message,
|
||||
{
|
||||
"user": tse.MemberAdapter(member),
|
||||
"moderator": tse.MemberAdapter(author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
"duration": tse.StringAdapter(humanized_duration),
|
||||
"days": tse.IntAdapter(days),
|
||||
},
|
||||
)
|
||||
await ctx.send(**kwargs)
|
||||
|
||||
softban = None
|
||||
|
||||
@commands.hybrid_command()
|
||||
@app_commands.describe(
|
||||
member="The member to softban.", reason="The reason for softbanning the user."
|
||||
)
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def softban(self, ctx: commands.Context, member: discord.Member, *, reason: str = None):
|
||||
"""Kick a user and delete 1 day's worth of their messages."""
|
||||
require_reason = await self._config.guild(ctx.guild).require_reason()
|
||||
if require_reason and reason is None:
|
||||
await ctx.send("You must provide a reason for this action.")
|
||||
return
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
|
||||
if author == member:
|
||||
await ctx.send(
|
||||
("I cannot let you do that. Self-harm is bad {emoji}").format(
|
||||
emoji="\N{PENSIVE FACE}"
|
||||
)
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.config, guild, author, member):
|
||||
await ctx.send(
|
||||
(
|
||||
"I cannot let you do that. You are "
|
||||
"not higher than the user in the role "
|
||||
"hierarchy."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
audit_reason = get_audit_reason(author, reason, shorten=True)
|
||||
|
||||
invite = await self.get_invite_for_reinvite(ctx)
|
||||
|
||||
try: # We don't want blocked DMs preventing us from banning
|
||||
msg = await member.send(
|
||||
(
|
||||
"You have been banned and "
|
||||
"then unbanned as a quick way to delete your messages.\n"
|
||||
"You can now join the server again. {invite_link}"
|
||||
).format(invite_link=invite)
|
||||
)
|
||||
except discord.HTTPException:
|
||||
msg = None
|
||||
try:
|
||||
await guild.ban(member, reason=audit_reason, delete_message_days=1)
|
||||
except discord.errors.Forbidden:
|
||||
await ctx.send(("My role is not high enough to softban that user."))
|
||||
if msg is not None:
|
||||
await msg.delete()
|
||||
return
|
||||
except discord.HTTPException:
|
||||
log.exception(
|
||||
"{}({}) attempted to softban {}({}), but an error occurred trying to ban them.".format(
|
||||
author.name, author.id, member.name, member.id
|
||||
)
|
||||
)
|
||||
return
|
||||
try:
|
||||
await guild.unban(member)
|
||||
except discord.HTTPException:
|
||||
log.exception(
|
||||
"{}({}) attempted to softban {}({}), but an error occurred trying to unban them.".format(
|
||||
author.name, author.id, member.name, member.id
|
||||
)
|
||||
)
|
||||
return
|
||||
else:
|
||||
log.info(
|
||||
"{}({}) softbanned {}({}), deleting 1 day worth "
|
||||
"of messages.".format(author.name, author.id, member.name, member.id)
|
||||
)
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"softban",
|
||||
member,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
message = await self._config.guild(ctx.guild).kick_message()
|
||||
kwargs = process_tagscript(
|
||||
message,
|
||||
{
|
||||
"user": tse.MemberAdapter(member),
|
||||
"moderator": tse.MemberAdapter(author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
},
|
||||
)
|
||||
if not kwargs:
|
||||
await self._config.guild(ctx.guild).kick_message.clear()
|
||||
kwargs = process_tagscript(
|
||||
kick_message,
|
||||
{
|
||||
"user": tse.MemberAdapter(member),
|
||||
"moderator": tse.MemberAdapter(author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
},
|
||||
)
|
||||
await ctx.send(**kwargs)
|
||||
|
||||
@commands.hybrid_command()
|
||||
@app_commands.describe(
|
||||
user="The member to ban.",
|
||||
reason="The reason for banning the user.",
|
||||
days="The number of days of messages to delete.",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@commands.admin_or_permissions(ban_members=True)
|
||||
async def ban(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
user: discord.Member,
|
||||
days: Optional[int] = None,
|
||||
*,
|
||||
reason: str = None,
|
||||
):
|
||||
"""Ban a user from this server and optionally delete days of messages.
|
||||
|
||||
`days` is the amount of days of messages to cleanup on ban.
|
||||
|
||||
Examples:
|
||||
- `[p]ban 428675506947227648 7 Continued to spam after told to stop.`
|
||||
This will ban the user with ID 428675506947227648 and it will delete 7 days worth of messages.
|
||||
- `[p]ban @Twentysix 7 Continued to spam after told to stop.`
|
||||
This will ban Twentysix and it will delete 7 days worth of messages.
|
||||
|
||||
A user ID should be provided if the user is not a member of this server.
|
||||
If days is not a number, it's treated as the first word of the reason.
|
||||
Minimum 0 days, maximum 7. If not specified, the defaultdays setting will be used instead.
|
||||
"""
|
||||
require_reason = await self._config.guild(ctx.guild).require_reason()
|
||||
if require_reason and reason is None:
|
||||
await ctx.send("You must provide a reason for this action.")
|
||||
return
|
||||
guild = ctx.guild
|
||||
if days is None:
|
||||
days = await self.config.guild(guild).default_days()
|
||||
if isinstance(user, int):
|
||||
user = self.bot.get_user(user) or discord.Object(id=user)
|
||||
|
||||
success, message = await self.ban_user(
|
||||
user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
|
||||
)
|
||||
|
||||
if not success:
|
||||
await ctx.send(message)
|
||||
return
|
||||
|
||||
kwargs = process_tagscript(
|
||||
message,
|
||||
{
|
||||
"user": tse.MemberAdapter(user),
|
||||
"moderator": tse.MemberAdapter(ctx.author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
"days": tse.IntAdapter(int(days)),
|
||||
},
|
||||
)
|
||||
if not kwargs:
|
||||
await self._config.guild(ctx.guild).ban_message.clear()
|
||||
kwargs = process_tagscript(
|
||||
ban_message,
|
||||
{
|
||||
"user": tse.MemberAdapter(user),
|
||||
"moderator": tse.MemberAdapter(ctx.author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
"days": tse.IntAdapter(int(days)),
|
||||
},
|
||||
)
|
||||
|
||||
await ctx.send(**kwargs)
|
||||
|
||||
ban_user = None
|
||||
|
||||
async def ban_user(
|
||||
self,
|
||||
user: Union[discord.Member, discord.User, discord.Object],
|
||||
ctx: commands.Context,
|
||||
days: int = 0,
|
||||
reason: str = None,
|
||||
create_modlog_case=False,
|
||||
) -> Tuple[bool, str]:
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
removed_temp = False
|
||||
|
||||
if not (0 <= days <= 7):
|
||||
return False, ("Invalid days. Must be between 0 and 7.")
|
||||
|
||||
if isinstance(user, discord.Member):
|
||||
if author == user:
|
||||
return (
|
||||
False,
|
||||
("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}"),
|
||||
)
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.config, guild, author, user):
|
||||
return (
|
||||
False,
|
||||
(
|
||||
"I cannot let you do that. You are "
|
||||
"not higher than the user in the role "
|
||||
"hierarchy."
|
||||
),
|
||||
)
|
||||
elif guild.me.top_role <= user.top_role or user == guild.owner:
|
||||
return False, ("I cannot do that due to Discord hierarchy rules.")
|
||||
|
||||
toggle = await self.config.guild(guild).dm_on_kickban()
|
||||
if toggle:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
em = discord.Embed(
|
||||
title=bold(("You have been banned from {guild}.").format(guild=guild)),
|
||||
color=await self.bot.get_embed_color(user),
|
||||
)
|
||||
em.add_field(
|
||||
name=("**Reason**"),
|
||||
value=reason if reason is not None else ("No reason was given."),
|
||||
inline=False,
|
||||
)
|
||||
await user.send(embed=em)
|
||||
|
||||
ban_type = "ban"
|
||||
else:
|
||||
tempbans = await self.config.guild(guild).current_tempbans()
|
||||
|
||||
try:
|
||||
await guild.fetch_ban(user)
|
||||
except discord.NotFound:
|
||||
pass
|
||||
else:
|
||||
if user.id in tempbans:
|
||||
async with self.config.guild(guild).current_tempbans() as tempbans:
|
||||
tempbans.remove(user.id)
|
||||
removed_temp = True
|
||||
else:
|
||||
return (
|
||||
False,
|
||||
("User with ID {user_id} is already banned.").format(user_id=user.id),
|
||||
)
|
||||
|
||||
ban_type = "hackban"
|
||||
|
||||
audit_reason = get_audit_reason(author, reason, shorten=True)
|
||||
|
||||
if removed_temp:
|
||||
log.info(
|
||||
"{}({}) upgraded the tempban for {} to a permaban.".format(
|
||||
author.name, author.id, user.id
|
||||
)
|
||||
)
|
||||
success_message = (
|
||||
"User with ID {user(id)} was upgraded from a temporary to a permanent ban."
|
||||
)
|
||||
else:
|
||||
username = user.name if hasattr(user, "name") else "Unknown"
|
||||
try:
|
||||
await guild.ban(user, reason=audit_reason, delete_message_days=days)
|
||||
log.info(
|
||||
"{}({}) {}ned {}({}), deleting {} days worth of messages.".format(
|
||||
author.name, author.id, ban_type, username, user.id, str(days)
|
||||
)
|
||||
)
|
||||
success_message = await self._config.guild(ctx.guild).ban_message()
|
||||
except discord.Forbidden:
|
||||
return False, ("I'm not allowed to do that.")
|
||||
except discord.NotFound:
|
||||
return False, ("User with ID {user_id} not found").format(user_id=user.id)
|
||||
except Exception:
|
||||
log.exception(
|
||||
"{}({}) attempted to {} {}({}), but an error occurred.".format(
|
||||
author.name, author.id, ban_type, username, user.id
|
||||
)
|
||||
)
|
||||
return False, ("An unexpected error occurred.")
|
||||
if create_modlog_case:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
ban_type,
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
|
||||
return True, success_message
|
||||
|
||||
unban = None
|
||||
|
||||
@commands.hybrid_command()
|
||||
@app_commands.describe(
|
||||
user_id="The ID of the user to unban.", reason="The reason for unbanning the user."
|
||||
)
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def unban(
|
||||
self, ctx: commands.Context, user_id: RawUserIdConverter, *, reason: str = None
|
||||
):
|
||||
"""Unban a user from this server.
|
||||
Requires specifying the target user's ID. To find this, you may either:
|
||||
1. Copy it from the mod log case (if one was created), or
|
||||
2. enable developer mode, go to Bans in this server's settings, right-
|
||||
click the user and select 'Copy ID'."""
|
||||
require_reason = await self._config.guild(ctx.guild).require_reason()
|
||||
if require_reason and reason is None:
|
||||
await ctx.send("You must provide a reason for this action.")
|
||||
return
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
audit_reason = get_audit_reason(ctx.author, reason, shorten=True)
|
||||
try:
|
||||
ban_entry = await guild.fetch_ban(discord.Object(user_id))
|
||||
except discord.NotFound:
|
||||
await ctx.send(("It seems that user isn't banned!"))
|
||||
return
|
||||
try:
|
||||
await guild.unban(ban_entry.user, reason=audit_reason)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(("Something went wrong while attempting to unban that user."))
|
||||
return
|
||||
else:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"unban",
|
||||
ban_entry.user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
message = await self._config.guild(ctx.guild).unban_message()
|
||||
kwargs = process_tagscript(
|
||||
message,
|
||||
{
|
||||
"user": tse.MemberAdapter(ban_entry.user),
|
||||
"moderator": tse.MemberAdapter(author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
},
|
||||
)
|
||||
if not kwargs:
|
||||
await self._config.guild(ctx.guild).unban_message.clear()
|
||||
kwargs = process_tagscript(
|
||||
ban_message,
|
||||
{
|
||||
"user": tse.MemberAdapter(user),
|
||||
"moderator": tse.MemberAdapter(ctx.author),
|
||||
"reason": tse.StringAdapter(str(reason)),
|
||||
"guild": tse.GuildAdapter(guild),
|
||||
},
|
||||
)
|
||||
await ctx.send(**kwargs)
|
||||
|
||||
if await self.config.guild(guild).reinvite_on_unban():
|
||||
user = ctx.bot.get_user(user_id)
|
||||
if not user:
|
||||
await ctx.send(
|
||||
("I don't share another server with this user. I can't reinvite them.")
|
||||
)
|
||||
return
|
||||
|
||||
invite = await self.get_invite_for_reinvite(ctx)
|
||||
if invite:
|
||||
try:
|
||||
await user.send(
|
||||
(
|
||||
"You've been unbanned from {server}.\n"
|
||||
"Here is an invite for that server: {invite_link}"
|
||||
).format(server=guild.name, invite_link=invite)
|
||||
)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(
|
||||
(
|
||||
"I failed to send an invite to that user. "
|
||||
"Perhaps you may be able to send it for me?\n"
|
||||
"Here's the invite link: {invite_link}"
|
||||
).format(invite_link=invite)
|
||||
)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(
|
||||
(
|
||||
"Something went wrong when attempting to send that user "
|
||||
"an invite. Here's the link so you can try: {invite_link}"
|
||||
).format(invite_link=invite)
|
||||
)
|
48
onetrueslash/__init__.py
Normal file
48
onetrueslash/__init__.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
import asyncio
|
||||
import logging
|
||||
|
||||
from redbot.core import app_commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.errors import CogLoadError
|
||||
from redbot.core.utils import get_end_user_data_statement_or_raise
|
||||
|
||||
__red_end_user_data_statement__ = get_end_user_data_statement_or_raise(__file__)
|
||||
|
||||
from .commands import onetrueslash
|
||||
from .events import before_hook, on_user_update
|
||||
from .utils import valid_app_name
|
||||
|
||||
LOG = logging.getLogger("red.fluffy.onetrueslash")
|
||||
|
||||
|
||||
async def setup(bot: Red) -> None:
|
||||
bot.before_invoke(before_hook)
|
||||
bot.add_listener(on_user_update)
|
||||
bot.add_dev_env_value("interaction", lambda ctx: getattr(ctx, "interaction", None))
|
||||
asyncio.create_task(_setup(bot)) # noqa: RUF006
|
||||
|
||||
|
||||
async def _setup(bot: Red):
|
||||
await bot.wait_until_red_ready()
|
||||
assert bot.user
|
||||
try:
|
||||
onetrueslash.name = valid_app_name(bot.user.name)
|
||||
bot.tree.add_command(onetrueslash, guild=None)
|
||||
except ValueError:
|
||||
await bot.send_to_owners(
|
||||
f"`onetrueslash` was unable to make the name {bot.user.name!r} "
|
||||
"into a valid slash command name. The command name was left unchanged."
|
||||
)
|
||||
except app_commands.CommandAlreadyRegistered:
|
||||
raise CogLoadError(
|
||||
f"A slash command named {onetrueslash.name} is already registered."
|
||||
) from None
|
||||
except app_commands.CommandLimitReached:
|
||||
raise CogLoadError(
|
||||
f"{bot.user.name} has already reached the maximum of 100 global slash commands."
|
||||
) from None
|
||||
|
||||
|
||||
async def teardown(bot: Red):
|
||||
bot.remove_before_invoke_hook(before_hook)
|
||||
bot.remove_dev_env_value("interaction")
|
43
onetrueslash/channel.py
Normal file
43
onetrueslash/channel.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from typing import TYPE_CHECKING, Union, cast
|
||||
|
||||
import discord
|
||||
|
||||
from .utils import Thinking, contexts
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from discord.context_managers import Typing
|
||||
|
||||
Base = discord.abc.Messageable
|
||||
else:
|
||||
Base = object
|
||||
|
||||
|
||||
class InterChannel(Base):
|
||||
__slots__ = ()
|
||||
|
||||
def permissions_for(
|
||||
self, obj: Union[discord.abc.User, discord.Role], /
|
||||
) -> discord.Permissions:
|
||||
try:
|
||||
ctx = contexts.get()
|
||||
except LookupError:
|
||||
pass
|
||||
else:
|
||||
interaction = ctx._interaction
|
||||
bot_user = cast(discord.ClientUser, ctx.bot.user)
|
||||
if obj.id == interaction.user.id:
|
||||
return ctx.permissions
|
||||
elif obj.id == bot_user.id:
|
||||
return ctx.bot_permissions
|
||||
return super().permissions_for(obj) # type: ignore
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
return contexts.get(super()).send(*args, **kwargs)
|
||||
|
||||
def typing(self) -> Union[Thinking, "Typing"]:
|
||||
try:
|
||||
ctx = contexts.get()
|
||||
except LookupError:
|
||||
return super().typing()
|
||||
else:
|
||||
return Thinking(ctx)
|
141
onetrueslash/commands.py
Normal file
141
onetrueslash/commands.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
import asyncio
|
||||
import functools
|
||||
import heapq
|
||||
import operator
|
||||
from copy import copy
|
||||
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, cast
|
||||
|
||||
import discord
|
||||
from rapidfuzz import fuzz
|
||||
from redbot.core import app_commands, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.commands.help import HelpSettings
|
||||
from redbot.core.i18n import set_contextual_locale
|
||||
|
||||
from .context import InterContext
|
||||
from .utils import walk_aliases
|
||||
|
||||
|
||||
@app_commands.command(extras={"red_force_enable": True})
|
||||
async def onetrueslash(
|
||||
interaction: discord.Interaction,
|
||||
command: str,
|
||||
arguments: Optional[str] = None,
|
||||
attachment: Optional[discord.Attachment] = None,
|
||||
) -> None:
|
||||
"""
|
||||
The one true slash command.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
command: str
|
||||
The text-based command to run.
|
||||
arguments: Optional[str]
|
||||
The arguments to provide to the command, if any.
|
||||
attachment: Optional[Attachment]
|
||||
The attached file to provide to the command, if any.
|
||||
"""
|
||||
assert isinstance(interaction.client, Red)
|
||||
set_contextual_locale(str(interaction.guild_locale or interaction.locale))
|
||||
actual = interaction.client.get_command(command)
|
||||
ctx = await InterContext.from_interaction(interaction, recreate_message=True)
|
||||
error = None
|
||||
if command == "help":
|
||||
ctx._deferring = True
|
||||
# Moving ctx._interaction can cause check errors with some hybrid commands
|
||||
# see https://github.com/Zephyrkul/FluffyCogs/issues/75 for details
|
||||
# ctx.interaction = interaction
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
actual = None
|
||||
if arguments:
|
||||
actual = interaction.client.get_command(arguments)
|
||||
if actual and (signature := actual.signature):
|
||||
actual = copy(actual)
|
||||
actual.usage = f"arguments:{signature}"
|
||||
await interaction.client.send_help_for(
|
||||
ctx, actual or interaction.client, from_help_command=True
|
||||
)
|
||||
else:
|
||||
ferror: asyncio.Task[Tuple[InterContext, commands.CommandError]] = asyncio.create_task(
|
||||
interaction.client.wait_for("command_error", check=lambda c, _: c is ctx)
|
||||
)
|
||||
ferror.add_done_callback(lambda _: setattr(ctx, "interaction", interaction))
|
||||
await interaction.client.invoke(ctx)
|
||||
if not interaction.response.is_done():
|
||||
ctx._deferring = True
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
if ferror.done():
|
||||
error = ferror.exception() or ferror.result()[1]
|
||||
ferror.cancel()
|
||||
if ctx._deferring and not interaction.is_expired():
|
||||
if error is None:
|
||||
if ctx._ticked:
|
||||
await interaction.followup.send(ctx._ticked, ephemeral=True)
|
||||
else:
|
||||
await interaction.delete_original_response()
|
||||
elif isinstance(error, commands.CommandNotFound):
|
||||
await interaction.followup.send(
|
||||
f"❌ Command `{command}` was not found.", ephemeral=True
|
||||
)
|
||||
elif isinstance(error, commands.CheckFailure):
|
||||
await interaction.followup.send(
|
||||
f"❌ You don't have permission to run `{command}`.", ephemeral=True
|
||||
)
|
||||
|
||||
|
||||
@onetrueslash.autocomplete("command")
|
||||
async def onetrueslash_command_autocomplete(
|
||||
interaction: discord.Interaction, current: str
|
||||
) -> List[app_commands.Choice[str]]:
|
||||
assert isinstance(interaction.client, Red)
|
||||
|
||||
if not await interaction.client.allowed_by_whitelist_blacklist(interaction.user):
|
||||
return []
|
||||
|
||||
ctx = await InterContext.from_interaction(interaction)
|
||||
if not await interaction.client.message_eligible_as_command(ctx.message):
|
||||
return []
|
||||
|
||||
help_settings = await HelpSettings.from_context(ctx)
|
||||
if current:
|
||||
extracted = cast(
|
||||
List[str],
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
heapq.nlargest,
|
||||
6,
|
||||
walk_aliases(interaction.client, show_hidden=help_settings.show_hidden),
|
||||
functools.partial(fuzz.token_sort_ratio, current),
|
||||
),
|
||||
)
|
||||
extracted.append("help")
|
||||
else:
|
||||
extracted = ["help"]
|
||||
_filter: Callable[[commands.Command], Awaitable[bool]] = operator.methodcaller(
|
||||
"can_run" if help_settings.show_hidden else "can_see", ctx
|
||||
)
|
||||
matches: Dict[commands.Command, str] = {}
|
||||
for name in extracted:
|
||||
command = interaction.client.get_command(name)
|
||||
if not command or command in matches:
|
||||
continue
|
||||
try:
|
||||
if name == "help" and await command.can_run(ctx) or await _filter(command):
|
||||
if len(name) > 100:
|
||||
name = name[:99] + "\N{HORIZONTAL ELLIPSIS}"
|
||||
matches[command] = name
|
||||
except commands.CommandError:
|
||||
pass
|
||||
return [app_commands.Choice(name=name, value=name) for name in matches.values()]
|
||||
|
||||
|
||||
@onetrueslash.error
|
||||
async def onetrueslash_error(interaction: discord.Interaction, error: Exception):
|
||||
assert isinstance(interaction.client, Red)
|
||||
if isinstance(error, app_commands.CommandInvokeError):
|
||||
error = error.original
|
||||
error = getattr(error, "original", error)
|
||||
await interaction.client.on_command_error(
|
||||
await InterContext.from_interaction(interaction, recreate_message=True),
|
||||
commands.CommandInvokeError(error),
|
||||
)
|
163
onetrueslash/context.py
Normal file
163
onetrueslash/context.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
import inspect
|
||||
import types
|
||||
from copy import copy
|
||||
from typing import Optional, Type, Union
|
||||
|
||||
import discord
|
||||
from discord.ext.commands.view import StringView
|
||||
from redbot.core import commands
|
||||
from redbot.core.bot import Red
|
||||
|
||||
from .message import InterMessage
|
||||
from .utils import Thinking, contexts
|
||||
|
||||
INCOMPATABLE_PARAMETERS_DISCARD = tuple(
|
||||
k
|
||||
for k in inspect.signature(discord.abc.Messageable.send).parameters
|
||||
if k not in inspect.signature(discord.Webhook.send).parameters
|
||||
)
|
||||
|
||||
|
||||
class InterContext(commands.Context):
|
||||
_deferring: bool = False
|
||||
_ticked: Optional[str] = None
|
||||
_interaction: discord.Interaction[Red]
|
||||
message: InterMessage
|
||||
|
||||
@classmethod
|
||||
def _get_type(cls, bot: Red) -> Type["InterContext"]:
|
||||
default = bot.get_context.__kwdefaults__.get("cls", None)
|
||||
if not isinstance(default, type) or default in cls.__mro__:
|
||||
return cls
|
||||
try:
|
||||
return types.new_class(cls.__name__, (cls, default))
|
||||
except Exception:
|
||||
return cls
|
||||
|
||||
@classmethod
|
||||
async def from_interaction(
|
||||
cls: Type["InterContext"],
|
||||
interaction: discord.Interaction[Red],
|
||||
*,
|
||||
recreate_message: bool = False,
|
||||
) -> "InterContext":
|
||||
prefix = f"</{interaction.data['name']}:{interaction.data['id']}> command:"
|
||||
try:
|
||||
self = contexts.get()
|
||||
if recreate_message:
|
||||
assert self.prefix is not None
|
||||
self.message._recreate_from_interaction(interaction, prefix)
|
||||
view = self.view = StringView(self.message.content)
|
||||
view.skip_string(self.prefix)
|
||||
invoker = view.get_word()
|
||||
self.invoked_with = invoker
|
||||
self.command = interaction.client.all_commands.get(invoker)
|
||||
return self
|
||||
except LookupError:
|
||||
pass
|
||||
message = InterMessage._from_interaction(interaction, prefix)
|
||||
view = StringView(message.content)
|
||||
view.skip_string(prefix)
|
||||
invoker = view.get_word()
|
||||
self = cls._get_type(interaction.client)(
|
||||
message=message,
|
||||
prefix=prefix,
|
||||
bot=interaction.client,
|
||||
view=view,
|
||||
invoked_with=invoker,
|
||||
command=interaction.client.all_commands.get(invoker),
|
||||
)
|
||||
# don't set self.interaction so make d.py parses commands the old way
|
||||
self._interaction = interaction
|
||||
interaction._baton = self
|
||||
contexts.set(self)
|
||||
return self
|
||||
|
||||
@property
|
||||
def clean_prefix(self) -> str:
|
||||
return f"/{self._interaction.data['name']} command:"
|
||||
|
||||
async def tick(self, *, message: Optional[str] = None) -> bool:
|
||||
return await super().tick(message="Done." if message is None else message)
|
||||
|
||||
async def react_quietly(
|
||||
self,
|
||||
reaction: Union[discord.Emoji, discord.Reaction, discord.PartialEmoji, str],
|
||||
*,
|
||||
message: Optional[str] = None,
|
||||
) -> bool:
|
||||
self._ticked = f"{reaction} {message}" if message else str(reaction)
|
||||
return False
|
||||
|
||||
async def send(self, *args, **kwargs):
|
||||
interaction = self._interaction
|
||||
if interaction.is_expired():
|
||||
assert interaction.channel
|
||||
return await interaction.channel.send(*args, **kwargs) # type: ignore
|
||||
await self.typing(ephemeral=True)
|
||||
self._deferring = False
|
||||
delete_after = kwargs.pop("delete_after", None)
|
||||
for key in INCOMPATABLE_PARAMETERS_DISCARD:
|
||||
kwargs.pop(key, None)
|
||||
m = await interaction.followup.send(*args, **kwargs)
|
||||
if delete_after:
|
||||
await m.delete(delay=delete_after)
|
||||
return m
|
||||
|
||||
def typing(self, *, ephemeral: bool = False) -> Thinking:
|
||||
return Thinking(self, ephemeral=ephemeral)
|
||||
|
||||
async def defer(self, *, ephemeral: bool = False) -> None:
|
||||
await self._interaction.response.defer(ephemeral=ephemeral)
|
||||
|
||||
async def send_help(
|
||||
self, command: Optional[Union[commands.Command, commands.GroupMixin, str]] = None
|
||||
):
|
||||
command = command or self.command
|
||||
if isinstance(command, str):
|
||||
command = self.bot.get_command(command) or command
|
||||
signature: str
|
||||
if signature := getattr(command, "signature", ""):
|
||||
assert not isinstance(command, str)
|
||||
command = copy(command)
|
||||
command.usage = f"arguments:{signature}"
|
||||
return await super().send_help(command)
|
||||
|
||||
def _apply_implicit_permissions(
|
||||
self, user: discord.abc.User, base: discord.Permissions
|
||||
) -> discord.Permissions:
|
||||
if base.administrator or (self.guild and self.guild.owner_id == user.id):
|
||||
return discord.Permissions.all()
|
||||
|
||||
base = copy(base)
|
||||
if not base.send_messages:
|
||||
base.send_tts_messages = False
|
||||
base.mention_everyone = False
|
||||
base.embed_links = False
|
||||
base.attach_files = False
|
||||
|
||||
if not base.read_messages:
|
||||
base &= ~discord.Permissions.all_channel()
|
||||
|
||||
channel_type = self.channel.type
|
||||
if channel_type in (discord.ChannelType.voice, discord.ChannelType.stage_voice):
|
||||
if not base.connect:
|
||||
denied = discord.Permissions.voice()
|
||||
denied.update(manage_channels=True, manage_roles=True)
|
||||
base &= ~denied
|
||||
else:
|
||||
base &= ~discord.Permissions.voice()
|
||||
|
||||
return base
|
||||
|
||||
@discord.utils.cached_property
|
||||
def permissions(self):
|
||||
if self._interaction._permissions == 0:
|
||||
return discord.Permissions._dm_permissions() # type: ignore
|
||||
return self._apply_implicit_permissions(self.author, self._interaction.permissions)
|
||||
|
||||
@discord.utils.cached_property
|
||||
def bot_permissions(self):
|
||||
return self._apply_implicit_permissions(
|
||||
self.me, self._interaction.app_permissions
|
||||
) | discord.Permissions(send_messages=True, attach_files=True, embed_links=True)
|
44
onetrueslash/events.py
Normal file
44
onetrueslash/events.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from redbot.core import commands as red_commands
|
||||
from redbot.core.bot import Red
|
||||
|
||||
from .commands import onetrueslash
|
||||
from .utils import valid_app_name
|
||||
|
||||
|
||||
async def before_hook(ctx: red_commands.Context):
|
||||
interaction: Optional[discord.Interaction] = getattr(ctx, "_interaction", None)
|
||||
if not interaction or getattr(ctx.command, "__commands_is_hybrid__", False):
|
||||
return
|
||||
ctx.interaction = interaction
|
||||
if not interaction.response.is_done():
|
||||
ctx._deferring = True # type: ignore
|
||||
await interaction.response.defer(ephemeral=False)
|
||||
|
||||
|
||||
async def on_user_update(before: discord.User, after: discord.User):
|
||||
bot: Red = after._state._get_client() # type: ignore # DEP-WARN
|
||||
assert bot.user
|
||||
if after.id != bot.user.id:
|
||||
return
|
||||
if before.name == after.name:
|
||||
return
|
||||
old_name = onetrueslash.name
|
||||
try:
|
||||
onetrueslash.name = valid_app_name(after.name)
|
||||
except ValueError:
|
||||
await bot.send_to_owners(
|
||||
f"`onetrueslash` was unable to make the name {after.name!r} "
|
||||
"into a valid slash command name. The command name was left unchanged."
|
||||
)
|
||||
return
|
||||
bot.tree.remove_command(old_name)
|
||||
bot.tree.add_command(onetrueslash, guild=None)
|
||||
await bot.send_to_owners(
|
||||
"The bot's username has changed. `onetrueslash`'s slash command has been updated to reflect this.\n"
|
||||
"**You will need to re-sync the command tree yourself to see this change.**\n"
|
||||
"It is recommended not to change the bot's name too often with this cog, as this can potentially "
|
||||
"create confusion for users as well as ratelimiting issues for the bot."
|
||||
)
|
19
onetrueslash/info.json
Normal file
19
onetrueslash/info.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"author": [
|
||||
"Zephyrkul (Zephyrkul#1089)"
|
||||
],
|
||||
"install_msg": "Thanks for loading OneTrueSlash! Your one and only slash command will show in all servers your bot has applications.commands permission for within an hour after syncing.\nNote that you will have to sync the associated command on your own, using `[p]slash sync`.\nThis cog has no commands beyond the one true slash command.\nDue to the necessary work to force text commands to work via slash, commands won't necessarily work correctly. No warranty is provided for any damage this cog may cause.",
|
||||
"name": "OneTrueSlash",
|
||||
"short": "Add the one and only slash command you will ever need to your bot!",
|
||||
"description": "Add the one and only slash command you will ever need to your bot!",
|
||||
"min_bot_version": "3.5.0",
|
||||
"tags": [
|
||||
"slash",
|
||||
"dpy2",
|
||||
"utility"
|
||||
],
|
||||
"requirements": [
|
||||
"rapidfuzz"
|
||||
],
|
||||
"end_user_data_statement": "This cog does not persistently store any data or metadata about users."
|
||||
}
|
132
onetrueslash/message.py
Normal file
132
onetrueslash/message.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
import asyncio
|
||||
from copy import copy
|
||||
from typing import TypeVar
|
||||
|
||||
import discord
|
||||
|
||||
from .channel import InterChannel
|
||||
|
||||
_TT = TypeVar("_TT", bound=type)
|
||||
|
||||
|
||||
def __step(*args, **kwargs):
|
||||
# ensure the coro still yields to the event loop
|
||||
return asyncio.sleep(0)
|
||||
|
||||
|
||||
def neuter_coros(cls: _TT) -> _TT:
|
||||
for name in dir(cls):
|
||||
if name in cls.__dict__:
|
||||
continue
|
||||
if (attr := getattr(cls, name, None)) is None:
|
||||
continue
|
||||
if asyncio.iscoroutinefunction(attr):
|
||||
setattr(cls, name, property(lambda self: __step))
|
||||
return cls
|
||||
|
||||
|
||||
@neuter_coros
|
||||
class InterMessage(discord.Message):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
raise RuntimeError
|
||||
|
||||
@classmethod
|
||||
def _from_interaction(cls, interaction: discord.Interaction, prefix: str) -> "InterMessage":
|
||||
assert interaction.data
|
||||
assert interaction.client.user
|
||||
|
||||
self = InterMessage.__new__(InterMessage)
|
||||
self._state = interaction._state
|
||||
self._edited_timestamp = None
|
||||
|
||||
self.tts = False
|
||||
self.webhook_id = None
|
||||
self.mention_everyone = False
|
||||
self.embeds = []
|
||||
self.role_mentions = []
|
||||
self.id = interaction.id
|
||||
self.nonce = None
|
||||
self.pinned = False
|
||||
self.type = discord.MessageType.default
|
||||
self.flags = discord.MessageFlags()
|
||||
self.reactions = []
|
||||
self.reference = None
|
||||
self.application = None
|
||||
self.activity = None
|
||||
self.stickers = []
|
||||
self.components = []
|
||||
self.role_subscription = None
|
||||
self.application_id = None
|
||||
self.position = None
|
||||
|
||||
channel = interaction.channel
|
||||
if not channel:
|
||||
raise RuntimeError("Interaction channel is missing, maybe a Discord bug")
|
||||
|
||||
self.guild = interaction.guild
|
||||
if interaction.guild_id and not interaction.guild:
|
||||
# act as if this is a DMChannel
|
||||
assert isinstance(interaction.user, discord.Member)
|
||||
self.author = interaction.user._user
|
||||
channel = discord.DMChannel(
|
||||
me=interaction.client.user,
|
||||
state=interaction._state,
|
||||
data={
|
||||
"id": channel.id,
|
||||
"name": str(channel),
|
||||
"type": 1,
|
||||
"last_message_id": None,
|
||||
"recipients": [
|
||||
self.author._to_minimal_user_json(),
|
||||
interaction.client.user._to_minimal_user_json(),
|
||||
],
|
||||
}, # type: ignore
|
||||
)
|
||||
else:
|
||||
self.author = interaction.user
|
||||
channel = copy(channel)
|
||||
|
||||
channel.__class__ = type(
|
||||
InterChannel.__name__, (InterChannel, channel.__class__), {"__slots__": ()}
|
||||
)
|
||||
self.channel = channel # type: ignore
|
||||
|
||||
self._recreate_from_interaction(interaction, prefix)
|
||||
return self
|
||||
|
||||
def _recreate_from_interaction(self, interaction: discord.Interaction, prefix: str):
|
||||
assert interaction.data and interaction.client.user
|
||||
|
||||
self.content = f"{prefix}{interaction.namespace.command}"
|
||||
if interaction.namespace.arguments:
|
||||
self.content = f"{self.content} {interaction.namespace.arguments}"
|
||||
if interaction.namespace.attachment:
|
||||
self.attachments = [interaction.namespace.attachment]
|
||||
else:
|
||||
self.attachments = []
|
||||
|
||||
resolved = interaction.data.get("resolved", {})
|
||||
if self.guild:
|
||||
self.mentions = [
|
||||
discord.Member(data=user_data, guild=self.guild, state=self._state)
|
||||
for user_data in resolved.get("members", {}).values()
|
||||
]
|
||||
else:
|
||||
self.mentions = [
|
||||
discord.User(data=user_data, state=self._state)
|
||||
for user_data in resolved.get("users", {}).values()
|
||||
]
|
||||
|
||||
def to_reference(self, *, fail_if_not_exists: bool = True):
|
||||
return None
|
||||
|
||||
def to_message_reference_dict(self):
|
||||
return discord.utils.MISSING
|
||||
|
||||
async def reply(self, *args, **kwargs):
|
||||
return await self.channel.send(*args, **kwargs)
|
||||
|
||||
def edit(self, *args, **kwargs):
|
||||
return asyncio.sleep(0, self)
|
59
onetrueslash/utils.py
Normal file
59
onetrueslash/utils.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from contextvars import ContextVar
|
||||
from typing import TYPE_CHECKING, Any, Generator, Optional
|
||||
|
||||
# discord.ext.commands.GroupMixin has easier typehints to work with
|
||||
from discord.ext.commands import GroupMixin
|
||||
from redbot.core import commands
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .context import InterContext
|
||||
|
||||
|
||||
try:
|
||||
import regex as re
|
||||
except ImportError:
|
||||
import re
|
||||
|
||||
|
||||
contexts = ContextVar["InterContext"]("contexts")
|
||||
|
||||
|
||||
def valid_app_name(name: str) -> str:
|
||||
from discord.app_commands.commands import VALID_SLASH_COMMAND_NAME, validate_name
|
||||
|
||||
name = "_".join(re.findall(VALID_SLASH_COMMAND_NAME.pattern.strip("^$"), name.lower()))
|
||||
return validate_name(name[:32])
|
||||
|
||||
|
||||
class Thinking:
|
||||
def __init__(self, ctx: "InterContext", *, ephemeral: bool = False):
|
||||
self.ctx = ctx
|
||||
self.ephemeral = ephemeral
|
||||
|
||||
def __await__(self) -> Generator[Any, Any, None]:
|
||||
ctx = self.ctx
|
||||
interaction = ctx._interaction
|
||||
if not ctx._deferring and not interaction.response.is_done():
|
||||
# yield from is necessary here to force this function to be a generator
|
||||
# even in the negative case
|
||||
ctx._deferring = True
|
||||
return (yield from interaction.response.defer(ephemeral=self.ephemeral).__await__())
|
||||
|
||||
async def __aenter__(self):
|
||||
await self
|
||||
|
||||
async def __aexit__(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
def walk_aliases(
|
||||
group: GroupMixin[Any], /, *, parent: Optional[str] = "", show_hidden: bool = False
|
||||
) -> Generator[str, None, None]:
|
||||
for name, command in group.all_commands.items():
|
||||
if command.qualified_name == "help":
|
||||
continue
|
||||
if not command.enabled or (not show_hidden and command.hidden):
|
||||
continue
|
||||
yield f"{parent}{name}"
|
||||
if isinstance(command, commands.GroupMixin):
|
||||
yield from walk_aliases(command, parent=f"{parent}{name} ", show_hidden=show_hidden)
|
80
onthisday/README.rst
Normal file
80
onthisday/README.rst
Normal file
|
@ -0,0 +1,80 @@
|
|||
.. _onthisday:
|
||||
|
||||
=========
|
||||
OnThisDay
|
||||
=========
|
||||
|
||||
This is the cog guide for the 'OnThisDay' cog. This guide
|
||||
contains the collection of commands which you can use in the cog.
|
||||
|
||||
Through this guide, ``[p]`` will always represent your prefix. Replace
|
||||
``[p]`` with your own prefix when you use these commands in Discord.
|
||||
|
||||
.. note::
|
||||
|
||||
This guide was last updated for version 2.0.0. Ensure
|
||||
that you are up to date by running ``[p]cog update onthisday``.
|
||||
|
||||
If there is something missing, or something that needs improving
|
||||
in this documentation, feel free to create an issue `here <https://github.com/Kreusada/Kreusada-Cogs/issues>`_.
|
||||
|
||||
This documentation is auto-generated everytime this cog receives an update.
|
||||
|
||||
--------------
|
||||
About this cog
|
||||
--------------
|
||||
|
||||
Find out what happened on a certain day, in multiple different years in history.
|
||||
|
||||
--------
|
||||
Commands
|
||||
--------
|
||||
|
||||
Here are all the commands included in this cog (2):
|
||||
|
||||
+-------------------------+--------------------------------------------------------------------------+
|
||||
| Command | Help |
|
||||
+=========================+==========================================================================+
|
||||
| ``[p]onthisday`` | Find out what happened on this day, in various different years! |
|
||||
| | |
|
||||
| | If you want to specify your own date, you can do so by using |
|
||||
| | `[p]onthisday [date]`. |
|
||||
| | You can also receive a random year by using `[p]onthisday random [day]`. |
|
||||
+-------------------------+--------------------------------------------------------------------------+
|
||||
| ``[p]onthisday random`` | Find out what happened on this day, in a random year. |
|
||||
| | |
|
||||
| | If you want to specify your own date, you can do so by using |
|
||||
| | `[p]onthisday [date]`. |
|
||||
+-------------------------+--------------------------------------------------------------------------+
|
||||
|
||||
------------
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you haven't added my repo before, lets add it first. We'll call it
|
||||
"kreusada-cogs" here.
|
||||
|
||||
.. code-block::
|
||||
|
||||
[p]repo add kreusada-cogs https://github.com/Kreusada/Kreusada-Cogs
|
||||
|
||||
Now, we can install OnThisDay.
|
||||
|
||||
.. code-block::
|
||||
|
||||
[p]cog install kreusada-cogs onthisday
|
||||
|
||||
Once it's installed, it is not loaded by default. Load it by running the following
|
||||
command:
|
||||
|
||||
.. code-block::
|
||||
|
||||
[p]load onthisday
|
||||
|
||||
---------------
|
||||
Further Support
|
||||
---------------
|
||||
|
||||
For more support, head over to the `cog support server <https://discord.gg/GET4DVk>`_,
|
||||
I have my own channel over there at #support_kreusada-cogs. Feel free to join my
|
||||
`personal server <https://discord.gg/JmCFyq7>`_ whilst you're here.
|
309
onthisday/__init__.py
Normal file
309
onthisday/__init__.py
Normal file
|
@ -0,0 +1,309 @@
|
|||
"""The OnThisDay module. Find out what happened on a certain day, in multiple different years in history."""
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import re
|
||||
from random import choice
|
||||
from typing import Dict, Iterable, Optional, Union
|
||||
|
||||
import aiohttp
|
||||
import dateparser
|
||||
import discord
|
||||
from redbot.core import commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.commands import BadArgument, Context, Converter
|
||||
from redbot.core.utils import get_end_user_data_statement
|
||||
from redbot.core.utils.chat_formatting import inline, warning
|
||||
|
||||
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
|
||||
|
||||
ENDPOINT = "https://byabbe.se/on-this-day/{}/events.json"
|
||||
|
||||
DEFAULT_DESCRIPTION = """
|
||||
Please send a valid year from the list of years below.
|
||||
These are the most significant years for events occuring
|
||||
on this day. Only years above 0 are included.
|
||||
"""
|
||||
|
||||
MONTH_MAPPING = {
|
||||
"1": "january",
|
||||
"2": "february",
|
||||
"3": "march",
|
||||
"4": "april",
|
||||
"5": "may",
|
||||
"6": "june",
|
||||
"7": "july",
|
||||
"8": "august",
|
||||
"9": "september",
|
||||
"10": "october",
|
||||
"11": "november",
|
||||
"12": "december",
|
||||
}
|
||||
|
||||
|
||||
def highlight_numerical_data(string):
|
||||
return re.sub(r"(([\d{1}],?)+)", r"**\1**", string)
|
||||
|
||||
|
||||
def retrieve_above_0(year):
|
||||
return year.strip().isdigit()
|
||||
|
||||
|
||||
def columns(years):
|
||||
m = f"{chr(12644)}\n" # padding
|
||||
f = lambda s: inline(s.zfill(4))
|
||||
for x in range(6, len(years), 6):
|
||||
m += "\n" + (" " * 5).join(map(f, years[x - 6 : x]))
|
||||
return m
|
||||
|
||||
|
||||
def date_suffix(number) -> str:
|
||||
number = int(number)
|
||||
suffixes = {0: "th", 1: "st", 2: "nd", 3: "rd"}
|
||||
for i in range(4, 10):
|
||||
suffixes[i] = "th"
|
||||
return str(number) + suffixes[int(str(number)[-1])]
|
||||
|
||||
|
||||
def now() -> datetime.datetime:
|
||||
return datetime.datetime.now()
|
||||
|
||||
|
||||
def current_year() -> int:
|
||||
return datetime.date.today().year
|
||||
|
||||
|
||||
def yield_chunks(l, n):
|
||||
for i in range(0, len(l), n):
|
||||
yield l[i : i + n]
|
||||
|
||||
|
||||
class DateConverter(Converter):
|
||||
"""Date converter which uses dateparser.parse()."""
|
||||
|
||||
async def convert(self, ctx: Context, arg: str) -> datetime.datetime:
|
||||
parsed = dateparser.parse(arg)
|
||||
if parsed is None:
|
||||
raise BadArgument("Unrecognized date/time.")
|
||||
if parsed.strftime("%Y") != str(now().strftime("%Y")):
|
||||
raise BadArgument("Please do not specify a year.")
|
||||
return parsed
|
||||
|
||||
|
||||
class YearDropdown(discord.ui.Select):
|
||||
def __init__(self, otd: "OnThisDay", /):
|
||||
self.otd = otd
|
||||
cy = current_year()
|
||||
options = [
|
||||
discord.SelectOption(
|
||||
label=year,
|
||||
description=f"On this day, {cy - int(year)} years ago",
|
||||
emoji="\N{CLOCK FACE THREE OCLOCK}",
|
||||
)
|
||||
for year in self.otd.year_data
|
||||
if int(year) in self.otd.year_range
|
||||
]
|
||||
|
||||
super().__init__(placeholder="Choose a year...", options=options)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
await self.otd.display_events(
|
||||
interaction,
|
||||
year=self.values[0],
|
||||
)
|
||||
|
||||
|
||||
class YearDropdownView(discord.ui.View):
|
||||
def __init__(self, otd: "OnThisDay"):
|
||||
super().__init__()
|
||||
self.add_item(YearDropdown(otd))
|
||||
|
||||
|
||||
class YearRangeDropdown(discord.ui.Select):
|
||||
"""This exists because the number of years dispatched always
|
||||
exceeds the Select options cap of 25.
|
||||
"""
|
||||
|
||||
YEAR_RANGES = [
|
||||
range(1901),
|
||||
range(1901, 1951),
|
||||
range(1951, 2001),
|
||||
range(2001, current_year() + 1),
|
||||
]
|
||||
|
||||
YEAR_RANGES_HUMANIZED = ["0 - 1900", "1901 - 1950", "1951 - 2000", "2001 - present"]
|
||||
|
||||
def __init__(self, otd: "OnThisDay", /):
|
||||
self.otd = otd
|
||||
|
||||
emojis = [
|
||||
"\N{LARGE RED CIRCLE}",
|
||||
"\N{LARGE ORANGE CIRCLE}",
|
||||
"\N{LARGE YELLOW CIRCLE}",
|
||||
"\N{LARGE GREEN CIRCLE}",
|
||||
]
|
||||
|
||||
options = [
|
||||
discord.SelectOption(
|
||||
label=self.YEAR_RANGES_HUMANIZED[i],
|
||||
value=i,
|
||||
description=f"Get choice from {self.YEAR_RANGES_HUMANIZED[i].replace('-', 'to')}",
|
||||
emoji=emojis[i],
|
||||
)
|
||||
for i in range(4)
|
||||
]
|
||||
|
||||
super().__init__(placeholder="Choose a year range...", options=options)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
self.otd.year_range = self.YEAR_RANGES[int(self.values[0])]
|
||||
await interaction.response.edit_message(
|
||||
content=f"Selected year range: **{self.YEAR_RANGES_HUMANIZED[int(self.values[0])]}**\nSelect a year from the list of available years.",
|
||||
view=YearDropdownView(self.otd),
|
||||
)
|
||||
|
||||
|
||||
class YearRangeDropdownView(discord.ui.View):
|
||||
def __init__(self, otd: "OnThisDay"):
|
||||
super().__init__()
|
||||
self.add_item(YearRangeDropdown(otd))
|
||||
|
||||
|
||||
class ButtonView(discord.ui.View):
|
||||
def __init__(self, buttons: dict[str, str]):
|
||||
super().__init__()
|
||||
for label, url in buttons.items():
|
||||
self.add_item(discord.ui.Button(label=label, url=url))
|
||||
|
||||
|
||||
class OnThisDay(commands.Cog):
|
||||
"""Find out what happened on a certain day, in multiple different years in history."""
|
||||
|
||||
__version__ = "2.0.0"
|
||||
__author__ = "Kreusada"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.session = aiohttp.ClientSession()
|
||||
self.date_number: Optional[str] = None
|
||||
self.month_number: Optional[str] = None
|
||||
self.year: Optional[str] = None
|
||||
self.year_data = {}
|
||||
self.year_range: Optional[range] = None
|
||||
self.date_wiki: Optional[str] = None
|
||||
|
||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||
context = super().format_help_for_context(ctx)
|
||||
return f"{context}\n\nAuthor: {self.__author__}\nVersion: {self.__version__}"
|
||||
|
||||
async def red_delete_data_for_user(self, **kwargs):
|
||||
"""Nothing to delete"""
|
||||
return
|
||||
|
||||
async def cog_unload(self) -> None:
|
||||
await self.session.close()
|
||||
|
||||
async def display_events(
|
||||
self,
|
||||
ctx: Union[commands.Context, discord.Interaction],
|
||||
*,
|
||||
year: str,
|
||||
):
|
||||
event = self.year_data[year]
|
||||
years_ago = int(self.year) - int("".join(filter(str.isdigit, year)))
|
||||
content = (
|
||||
event["content"]
|
||||
+ "\n\n"
|
||||
+ f"This event occured on the __{date_suffix(self.date_number)} of {MONTH_MAPPING[self.month_number].capitalize()}, {year}__."
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title=f"On this day, {years_ago} years ago...",
|
||||
description=highlight_numerical_data(content),
|
||||
color=await self.bot.get_embed_colour(ctx.channel),
|
||||
)
|
||||
embed.set_footer(text="See the below links for related wikipedia articles")
|
||||
_d = {
|
||||
f"The year '{year}'": "https://en.wikipedia.org/wiki/" + year,
|
||||
f"The date '{self.date_number.zfill(2)}/{self.month_number.zfill(2)}'": self.date_wiki,
|
||||
}
|
||||
embed.add_field(
|
||||
name="Other significant events",
|
||||
value="\n".join(f"- [{k}]({v})" for k, v in _d.items()),
|
||||
inline=False,
|
||||
)
|
||||
wiki_dict = {w["title"]: w["wikipedia"] for w in event["wikipedia"]}
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
send_method = ctx.response.edit_message
|
||||
else:
|
||||
send_method = ctx.send
|
||||
await send_method(content=None, embed=embed, view=ButtonView(wiki_dict))
|
||||
|
||||
async def run_otd(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
date: Optional[datetime.datetime],
|
||||
random: bool = False,
|
||||
):
|
||||
self.cache_date(date)
|
||||
try:
|
||||
async with self.session.get(
|
||||
ENDPOINT.format(f"{self.month_number}/{self.date_number}")
|
||||
) as session:
|
||||
if session.status != 200:
|
||||
return await ctx.maybe_send_embed(
|
||||
warning("An error occured whilst retrieving information for this day.")
|
||||
)
|
||||
content = await session.json()
|
||||
except aiohttp.ClientError:
|
||||
return await ctx.maybe_send_embed(
|
||||
warning("An error occured whilst retrieving information for this day.")
|
||||
)
|
||||
else:
|
||||
self.date_wiki = content["wikipedia"]
|
||||
events = filter(lambda x: retrieve_above_0(x["year"]), content["events"])
|
||||
data = {
|
||||
e["year"]: {"content": e["description"], "wikipedia": e["wikipedia"]}
|
||||
for e in events
|
||||
}
|
||||
self.year_data = data
|
||||
if random:
|
||||
await self.display_events(ctx, year=choice(list(data.keys())))
|
||||
else:
|
||||
await ctx.send(
|
||||
"Choose a year range to select from:",
|
||||
view=YearRangeDropdownView(self),
|
||||
)
|
||||
|
||||
def cache_date(self, date: Optional[datetime.datetime], /) -> Dict[str, int]:
|
||||
if date is None:
|
||||
date = now()
|
||||
|
||||
self.month_number = date.strftime(r"%m").lstrip("0")
|
||||
self.date_number = date.strftime(r"%d").lstrip("0")
|
||||
self.year = date.strftime(r"%Y")
|
||||
|
||||
@commands.has_permissions(embed_links=True)
|
||||
@commands.group(invoke_without_command=True, aliases=["otd"])
|
||||
async def onthisday(self, ctx: commands.Context, *, date: DateConverter = None):
|
||||
"""Find out what happened on this day, in various different years!
|
||||
|
||||
If you want to specify your own date, you can do so by using
|
||||
`[p]onthisday [date]`.
|
||||
You can also receive a random year by using `[p]onthisday random [day]`.
|
||||
"""
|
||||
await ctx.typing()
|
||||
await self.run_otd(ctx, date=date, random=False)
|
||||
|
||||
@onthisday.command()
|
||||
async def random(self, ctx: commands.Context, *, date: DateConverter = None):
|
||||
"""Find out what happened on this day, in a random year.
|
||||
|
||||
If you want to specify your own date, you can do so by using
|
||||
`[p]onthisday [date]`.
|
||||
"""
|
||||
await ctx.typing()
|
||||
await self.run_otd(ctx, date=date, random=True)
|
||||
|
||||
|
||||
async def setup(bot: Red):
|
||||
await bot.add_cog(OnThisDay(bot))
|
24
onthisday/info.json
Normal file
24
onthisday/info.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"author": [
|
||||
"Kreusada"
|
||||
],
|
||||
"description": "Find out what happened today, in multiple different years in history.",
|
||||
"disabled": false,
|
||||
"end_user_data_statement": "This cog does not persistently store data or metadata about users.",
|
||||
"hidden": false,
|
||||
"install_msg": "Thanks for installing, have fun. Please refer to my docs if you need any help: https://kreusadacogs.readthedocs.io/en/latest/cog_onthisday.html",
|
||||
"name": "OnThisDay",
|
||||
"required_cogs": {},
|
||||
"requirements": [
|
||||
"dateparser"
|
||||
],
|
||||
"short": "Find out what happened today, in multiple different years in history.",
|
||||
"tags": [
|
||||
"On This Day",
|
||||
"History",
|
||||
"Year",
|
||||
"Time",
|
||||
"Date"
|
||||
],
|
||||
"type": "COG"
|
||||
}
|
6
pokemonduel/__init__.py
Normal file
6
pokemonduel/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from .commands import PokemonDuel
|
||||
|
||||
__red_end_user_data_statement__ = "This cog stores user ids in order to track your party of pokemon."
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(PokemonDuel(bot))
|
491
pokemonduel/battle.py
Normal file
491
pokemonduel/battle.py
Normal file
|
@ -0,0 +1,491 @@
|
|||
import asyncio
|
||||
import random
|
||||
from .buttons import SwapPromptView
|
||||
from .data import generate_main_battle_message, generate_text_battle_message, find
|
||||
from .enums import Ability, DamageClass
|
||||
from .misc import ExpiringEffect, Weather, Terrain
|
||||
|
||||
|
||||
class Battle():
|
||||
"""
|
||||
Represents a battle between two trainers and their pokemon.
|
||||
|
||||
This object holds all necessary information for a battle & runs the battle.
|
||||
"""
|
||||
def __init__(self, ctx, channel, trainer1, trainer2, *, inverse_battle=False):
|
||||
self.ctx = ctx
|
||||
self.channel = channel
|
||||
self.trainer1 = trainer1
|
||||
for poke in trainer1.party:
|
||||
poke.held_item.battle = self
|
||||
self.trainer2 = trainer2
|
||||
for poke in trainer2.party:
|
||||
poke.held_item.battle = self
|
||||
self.bg_num = random.randint(1, 4)
|
||||
self.trick_room = ExpiringEffect(0)
|
||||
self.magic_room = ExpiringEffect(0)
|
||||
self.wonder_room = ExpiringEffect(0)
|
||||
self.gravity = ExpiringEffect(0)
|
||||
self.weather = Weather(self)
|
||||
self.terrain = Terrain(self)
|
||||
self.plasma_fists = False
|
||||
self.turn = 0
|
||||
self.last_move_effect = None
|
||||
self.metronome_moves_raw = []
|
||||
#(AttackerType, DefenderType): Effectiveness
|
||||
self.type_effectiveness = {}
|
||||
self.inverse_battle = inverse_battle
|
||||
self.msg = ""
|
||||
|
||||
async def run(self):
|
||||
"""Runs the duel."""
|
||||
self.msg = ""
|
||||
# Moves which are immune to metronome
|
||||
immune_ids = [
|
||||
68, 102, 119, 144, 165, 166, 168, 173, 182, 194, 197, 203, 214, 243, 264, 266,
|
||||
267, 270, 271, 274, 289, 343, 364, 382, 383, 415, 448, 469, 476, 495, 501, 511,
|
||||
516, 546, 547, 548, 553, 554, 555, 557, 561, 562, 578, 588, 591, 592, 593, 596,
|
||||
606, 607, 614, 615, 617, 621, 661, 671, 689, 690, 704, 705, 712, 720, 721, 722
|
||||
]
|
||||
# Moves which are not coded in the bot
|
||||
uncoded_ids = [
|
||||
266, 270, 476, 495, 502, 511, 597, 602, 603, 607, 622, 623, 624, 625, 626, 627,
|
||||
628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643,
|
||||
644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 671,
|
||||
695, 696, 697, 698, 699, 700, 701, 702, 703, 719, 723, 724, 725, 726, 727, 728,
|
||||
811, 10001, 10002, 10003, 10004, 10005, 10006, 10007, 10008, 10009, 10010, 10011,
|
||||
10012, 10013, 10014, 10015, 10016, 10017, 10018
|
||||
]
|
||||
ignored_ids = list(set(immune_ids) | set(uncoded_ids))
|
||||
self.metronome_moves_raw = await find(self.ctx, "moves", {"id": {"$nin": ignored_ids}})
|
||||
for te in await find(self.ctx, "type_effectiveness", {}):
|
||||
self.type_effectiveness[(te["damage_type_id"], te["target_type_id"])] = te["damage_factor"]
|
||||
#This calculation only uses the primative speed attr as the pokes have not been fully initiaized yet.
|
||||
if self.trainer1.current_pokemon.get_raw_speed() > self.trainer2.current_pokemon.get_raw_speed():
|
||||
self.msg += self.trainer1.current_pokemon.send_out(self.trainer2.current_pokemon, self)
|
||||
self.msg += self.trainer2.current_pokemon.send_out(self.trainer1.current_pokemon, self)
|
||||
else:
|
||||
self.msg += self.trainer2.current_pokemon.send_out(self.trainer1.current_pokemon, self)
|
||||
self.msg += self.trainer1.current_pokemon.send_out(self.trainer2.current_pokemon, self)
|
||||
await self.send_msg()
|
||||
winner = None
|
||||
while True:
|
||||
# Swap pokes for any users w/o an active poke
|
||||
while self.trainer1.current_pokemon is None or self.trainer2.current_pokemon is None:
|
||||
swapped1 = False
|
||||
swapped2 = False
|
||||
|
||||
if self.trainer1.current_pokemon is None:
|
||||
swapped1 = True
|
||||
winner = await self.run_swap(self.trainer1, self.trainer2)
|
||||
if winner:
|
||||
break
|
||||
|
||||
if self.trainer2.current_pokemon is None:
|
||||
swapped2 = True
|
||||
winner = await self.run_swap(self.trainer2, self.trainer1)
|
||||
if winner:
|
||||
break
|
||||
|
||||
# Send out the pokes that were just swapped to
|
||||
if swapped1 and swapped2:
|
||||
if self.trainer1.current_pokemon.get_raw_speed() > self.trainer2.current_pokemon.get_raw_speed():
|
||||
self.msg += self.trainer1.current_pokemon.send_out(self.trainer2.current_pokemon, self)
|
||||
if not self.trainer1.has_alive_pokemon():
|
||||
self.msg += f"{self.trainer2.name} wins!\n"
|
||||
winner = self.trainer2
|
||||
break
|
||||
self.msg += self.trainer2.current_pokemon.send_out(self.trainer1.current_pokemon, self)
|
||||
if not self.trainer2.has_alive_pokemon():
|
||||
self.msg += f"{self.trainer1.name} wins!\n"
|
||||
winner = self.trainer1
|
||||
break
|
||||
else:
|
||||
self.msg += self.trainer2.current_pokemon.send_out(self.trainer1.current_pokemon, self)
|
||||
if not self.trainer2.has_alive_pokemon():
|
||||
self.msg += f"{self.trainer1.name} wins!\n"
|
||||
winner = self.trainer1
|
||||
break
|
||||
self.msg += self.trainer1.current_pokemon.send_out(self.trainer2.current_pokemon, self)
|
||||
if not self.trainer1.has_alive_pokemon():
|
||||
self.msg += f"{self.trainer2.name} wins!\n"
|
||||
winner = self.trainer2
|
||||
break
|
||||
elif swapped1:
|
||||
self.msg += self.trainer1.current_pokemon.send_out(self.trainer2.current_pokemon, self)
|
||||
if not self.trainer1.has_alive_pokemon():
|
||||
self.msg += f"{self.trainer2.name} wins!\n"
|
||||
winner = self.trainer2
|
||||
break
|
||||
elif swapped2:
|
||||
self.msg += self.trainer2.current_pokemon.send_out(self.trainer1.current_pokemon, self)
|
||||
if not self.trainer2.has_alive_pokemon():
|
||||
self.msg += f"{self.trainer1.name} wins!\n"
|
||||
winner = self.trainer1
|
||||
break
|
||||
# Handle breaking out of the main game loop when a winner happens in the poke select loop
|
||||
if winner:
|
||||
break
|
||||
|
||||
# Get trainer actions
|
||||
await self.send_msg()
|
||||
|
||||
self.trainer1.event.clear()
|
||||
self.trainer2.event.clear()
|
||||
if not self.trainer1.is_human():
|
||||
self.trainer1.move(self.trainer2.current_pokemon, self)
|
||||
if not self.trainer2.is_human():
|
||||
self.trainer2.move(self.trainer1.current_pokemon, self)
|
||||
|
||||
battle_view = await generate_main_battle_message(self)
|
||||
await self.trainer1.event.wait()
|
||||
await self.trainer2.event.wait()
|
||||
battle_view.stop()
|
||||
|
||||
# Check for forfeits
|
||||
if self.trainer1.selected_action is None and self.trainer2.selected_action is None:
|
||||
await self.channel.send("Both players forfeited...")
|
||||
return #TODO: ???
|
||||
if self.trainer1.selected_action is None:
|
||||
self.msg += f"{self.trainer1.name} forfeited, {self.trainer2.name} wins!\n"
|
||||
winner = self.trainer2
|
||||
break
|
||||
if self.trainer2.selected_action is None:
|
||||
self.msg += f"{self.trainer2.name} forfeited, {self.trainer1.name} wins!\n"
|
||||
winner = self.trainer1
|
||||
break
|
||||
|
||||
# Run setup for both pokemon
|
||||
t1, t2 = self.who_first()
|
||||
if t1.current_pokemon is not None and t2.current_pokemon is not None:
|
||||
if not isinstance(t1.selected_action, int):
|
||||
self.msg += t1.selected_action.setup(t1.current_pokemon, t2.current_pokemon, self)
|
||||
if not t1.has_alive_pokemon():
|
||||
self.msg += f"{t2.name} wins!\n"
|
||||
winner = t2
|
||||
break
|
||||
if not t2.has_alive_pokemon():
|
||||
self.msg += f"{t1.name} wins!\n"
|
||||
winner = t1
|
||||
break
|
||||
if t1.current_pokemon is not None and t2.current_pokemon is not None:
|
||||
if not isinstance(t2.selected_action, int):
|
||||
self.msg += t2.selected_action.setup(t2.current_pokemon, t1.current_pokemon, self)
|
||||
if not t2.has_alive_pokemon():
|
||||
self.msg += f"{t1.name} wins!\n"
|
||||
winner = t1
|
||||
break
|
||||
if not t1.has_alive_pokemon():
|
||||
self.msg += f"{t2.name} wins!\n"
|
||||
winner = t2
|
||||
break
|
||||
|
||||
# Run moves for both pokemon
|
||||
# Trainer 1's move
|
||||
ran_megas = False
|
||||
if not isinstance(t1.selected_action, int):
|
||||
self.handle_megas(t1, t2)
|
||||
ran_megas = True
|
||||
|
||||
if t1.current_pokemon is not None and t2.current_pokemon is not None:
|
||||
if isinstance(t1.selected_action, int):
|
||||
self.msg += t1.current_pokemon.remove(self)
|
||||
t1.switch_poke(t1.selected_action, mid_turn=True)
|
||||
self.msg += t1.current_pokemon.send_out(t2.current_pokemon, self)
|
||||
if t1.current_pokemon is not None:
|
||||
t1.current_pokemon.has_moved = True
|
||||
else:
|
||||
self.msg += t1.selected_action.use(t1.current_pokemon, t2.current_pokemon, self)
|
||||
if not t1.has_alive_pokemon():
|
||||
self.msg += f"{t2.name} wins!\n"
|
||||
winner = t2
|
||||
break
|
||||
if not t2.has_alive_pokemon():
|
||||
self.msg += f"{t1.name} wins!\n"
|
||||
winner = t1
|
||||
break
|
||||
|
||||
# Pokes who die do NOT get attacked, but pokes who retreat *do*
|
||||
if t1.mid_turn_remove:
|
||||
winner = await self.run_swap(t1, t2, mid_turn=True)
|
||||
if winner:
|
||||
break
|
||||
|
||||
# EDGE CASE - Moves that DO NOT target the opponent (and swapping) SHOULD run
|
||||
# even if there is no other poke on the field. Right now everything is hardcoded
|
||||
# to require two pokes to work, on a rewrite the `use` function should be the
|
||||
# one handling the job of checking if the attacked poke is `None` before using a
|
||||
# move that targets opponents.
|
||||
if (
|
||||
t1.current_pokemon is None and t2.current_pokemon is not None
|
||||
and (isinstance(t2.selected_action, int) or not t2.selected_action.targets_opponent())
|
||||
):
|
||||
winner = await self.run_swap(t1, t2, mid_turn=True)
|
||||
if winner:
|
||||
break
|
||||
|
||||
self.msg += "\n"
|
||||
|
||||
# Trainer 2's move
|
||||
if not ran_megas and not isinstance(t2.selected_action, int):
|
||||
self.handle_megas(t1, t2)
|
||||
ran_megas = True
|
||||
|
||||
if t1.current_pokemon is not None and t2.current_pokemon is not None:
|
||||
if isinstance(t2.selected_action, int):
|
||||
self.msg += t2.current_pokemon.remove(self)
|
||||
t2.switch_poke(t2.selected_action, mid_turn=True)
|
||||
self.msg += t2.current_pokemon.send_out(t1.current_pokemon, self)
|
||||
if t2.current_pokemon is not None:
|
||||
t2.current_pokemon.has_moved = True
|
||||
else:
|
||||
self.msg += t2.selected_action.use(t2.current_pokemon, t1.current_pokemon, self)
|
||||
if not t2.has_alive_pokemon():
|
||||
self.msg += f"{t1.name} wins!\n"
|
||||
winner = t1
|
||||
break
|
||||
if not t1.has_alive_pokemon():
|
||||
self.msg += f"{t2.name} wins!\n"
|
||||
winner = t2
|
||||
break
|
||||
|
||||
self.msg += "\n"
|
||||
if t2.mid_turn_remove:
|
||||
# This DOES need to be here, otherwise end of turn effects aren't handled right
|
||||
winner = await self.run_swap(t2, t1, mid_turn=True)
|
||||
if winner:
|
||||
break
|
||||
|
||||
if not t2.has_alive_pokemon():
|
||||
self.msg += f"{t1.name} wins!\n"
|
||||
winner = t1
|
||||
break
|
||||
if not t1.has_alive_pokemon():
|
||||
self.msg += f"{t2.name} wins!\n"
|
||||
winner = t2
|
||||
break
|
||||
|
||||
if not ran_megas:
|
||||
self.handle_megas(t1, t2)
|
||||
|
||||
#Progress turns
|
||||
self.turn += 1
|
||||
self.plasma_fists = False
|
||||
if self.weather.next_turn():
|
||||
self.msg += "The weather cleared!\n"
|
||||
if self.terrain.next_turn():
|
||||
self.msg += "The terrain cleared!\n"
|
||||
self.last_move_effect = None
|
||||
t1, t2 = self.who_first(False)
|
||||
self.msg += t1.next_turn(self)
|
||||
if t1.current_pokemon is not None:
|
||||
self.msg += t1.current_pokemon.next_turn(t2.current_pokemon, self)
|
||||
if not t1.has_alive_pokemon():
|
||||
self.msg += f"{t2.name} wins!\n"
|
||||
winner = t2
|
||||
break
|
||||
if not t2.has_alive_pokemon():
|
||||
self.msg += f"{t1.name} wins!\n"
|
||||
winner = t1
|
||||
break
|
||||
self.msg += t2.next_turn(self)
|
||||
if t2.current_pokemon is not None:
|
||||
self.msg += t2.current_pokemon.next_turn(t1.current_pokemon, self)
|
||||
if not t2.has_alive_pokemon():
|
||||
self.msg += f"{t1.name} wins!\n"
|
||||
winner = t1
|
||||
break
|
||||
if not t1.has_alive_pokemon():
|
||||
self.msg += f"{t2.name} wins!\n"
|
||||
winner = t2
|
||||
break
|
||||
if self.trick_room.next_turn():
|
||||
self.msg += "The Dimensions returned back to normal!\n"
|
||||
if self.gravity.next_turn():
|
||||
self.msg += "Gravity returns to normal!\n"
|
||||
if self.magic_room.next_turn():
|
||||
self.msg += "The room returns to normal, and held items regain their effect!\n"
|
||||
if self.wonder_room.next_turn():
|
||||
self.msg += "The room returns to normal, and stats swap back to what they were before!\n"
|
||||
|
||||
#The game is over, and we broke out before sending, send the remaining cache
|
||||
await self.send_msg()
|
||||
return winner
|
||||
|
||||
def who_first(self, check_move=True):
|
||||
"""
|
||||
Determines which move should go.
|
||||
|
||||
Returns the two trainers and their moves, in the order they should go.
|
||||
"""
|
||||
T1FIRST = (self.trainer1, self.trainer2)
|
||||
T2FIRST = (self.trainer2, self.trainer1)
|
||||
|
||||
if self.trainer1.current_pokemon is None or self.trainer2.current_pokemon is None:
|
||||
return T1FIRST
|
||||
|
||||
speed1 = self.trainer1.current_pokemon.get_speed(self)
|
||||
speed2 = self.trainer2.current_pokemon.get_speed(self)
|
||||
|
||||
#Pokes that are switching go before pokes making other moves
|
||||
if check_move:
|
||||
if isinstance(self.trainer1.selected_action, int) and isinstance(self.trainer2.selected_action, int):
|
||||
if self.trainer1.current_pokemon.get_raw_speed() > self.trainer2.current_pokemon.get_raw_speed():
|
||||
return T1FIRST
|
||||
return T2FIRST
|
||||
if isinstance(self.trainer1.selected_action, int):
|
||||
return T1FIRST
|
||||
if isinstance(self.trainer2.selected_action, int):
|
||||
return T2FIRST
|
||||
|
||||
#Priority brackets & abilities
|
||||
if check_move:
|
||||
prio1 = self.trainer1.selected_action.get_priority(self.trainer1.current_pokemon, self.trainer2.current_pokemon, self)
|
||||
prio2 = self.trainer2.selected_action.get_priority(self.trainer2.current_pokemon, self.trainer1.current_pokemon, self)
|
||||
if prio1 > prio2:
|
||||
return T1FIRST
|
||||
if prio2 > prio1:
|
||||
return T2FIRST
|
||||
t1_quick = False
|
||||
t2_quick = False
|
||||
# Quick draw/claw
|
||||
if (
|
||||
self.trainer1.current_pokemon.ability() == Ability.QUICK_DRAW
|
||||
and self.trainer1.selected_action.damage_class != DamageClass.STATUS
|
||||
and random.randint(1, 100) <= 30
|
||||
):
|
||||
t1_quick = True
|
||||
if (
|
||||
self.trainer2.current_pokemon.ability() == Ability.QUICK_DRAW
|
||||
and self.trainer2.selected_action.damage_class != DamageClass.STATUS
|
||||
and random.randint(1, 100) <= 30
|
||||
):
|
||||
t2_quick = True
|
||||
if (
|
||||
self.trainer1.current_pokemon.held_item == "quick-claw"
|
||||
and random.randint(1, 100) <= 20
|
||||
):
|
||||
t1_quick = True
|
||||
if (
|
||||
self.trainer2.current_pokemon.held_item == "quick-claw"
|
||||
and random.randint(1, 100) <= 20
|
||||
):
|
||||
t2_quick = True
|
||||
#if both pokemon activate a quick, priority bracket proceeds as normal
|
||||
if t1_quick and not t2_quick:
|
||||
return T1FIRST
|
||||
if t2_quick and not t1_quick:
|
||||
return T2FIRST
|
||||
# Move last in prio bracket
|
||||
t1_slow = False
|
||||
t2_slow = False
|
||||
if self.trainer1.current_pokemon.ability() == Ability.STALL:
|
||||
t1_slow = True
|
||||
if (
|
||||
self.trainer1.current_pokemon.ability() == Ability.MYCELIUM_MIGHT
|
||||
and self.trainer1.selected_action.damage_class == DamageClass.STATUS
|
||||
):
|
||||
t1_slow = True
|
||||
if self.trainer2.current_pokemon.ability() == Ability.STALL:
|
||||
t2_slow = True
|
||||
if (
|
||||
self.trainer2.current_pokemon.ability() == Ability.MYCELIUM_MIGHT
|
||||
and self.trainer2.selected_action.damage_class == DamageClass.STATUS
|
||||
):
|
||||
t2_slow = True
|
||||
if t1_slow and t2_slow:
|
||||
if speed1 == speed2:
|
||||
return random.choice([T1FIRST, T2FIRST])
|
||||
if speed1 > speed2:
|
||||
return T2FIRST
|
||||
return T1FIRST
|
||||
if t1_slow:
|
||||
return T2FIRST
|
||||
if t2_slow:
|
||||
return T1FIRST
|
||||
|
||||
#Equal speed
|
||||
if speed1 == speed2:
|
||||
return random.choice([T1FIRST, T2FIRST])
|
||||
|
||||
#Trick room
|
||||
if self.trick_room.active():
|
||||
if speed1 > speed2:
|
||||
return T2FIRST
|
||||
return T1FIRST
|
||||
|
||||
#Default handling
|
||||
if speed1 > speed2:
|
||||
return T1FIRST
|
||||
return T2FIRST
|
||||
|
||||
async def send_msg(self):
|
||||
"""
|
||||
Send the msg in a boilerplate embed.
|
||||
|
||||
Handles the message being too long.
|
||||
"""
|
||||
await generate_text_battle_message(self)
|
||||
|
||||
async def run_swap(self, swapper, othertrainer, *, mid_turn=False):
|
||||
"""
|
||||
Called when swapper does not have a pokemon selected, and needs a new one.
|
||||
|
||||
Prompts the swapper to pick a pokemon.
|
||||
If mid_turn is set to True, the pokemon is being swapped in the middle of a turn (NOT at the start of a turn).
|
||||
Returns None if the trainer swapped, and the trainer that won if they did not.
|
||||
"""
|
||||
await self.send_msg()
|
||||
|
||||
swapper.event.clear()
|
||||
if swapper.is_human():
|
||||
swap_view = SwapPromptView(swapper, othertrainer, self, mid_turn=mid_turn)
|
||||
await self.channel.send(
|
||||
f"{swapper.name}, pick a pokemon to swap to!",
|
||||
view=swap_view
|
||||
)
|
||||
else:
|
||||
swapper.swap(othertrainer, self, mid_turn=mid_turn)
|
||||
|
||||
try:
|
||||
await swapper.event.wait()
|
||||
except asyncio.TimeoutError:
|
||||
self.msg += f"{swapper.name} did not select a poke, {othertrainer.name} wins!\n"
|
||||
return othertrainer
|
||||
|
||||
if swapper.is_human():
|
||||
swap_view.stop()
|
||||
|
||||
if swapper.current_pokemon is None:
|
||||
self.msg += f"{swapper.name} did not select a poke, {othertrainer.name} wins!\n"
|
||||
return othertrainer
|
||||
|
||||
if mid_turn:
|
||||
self.msg += swapper.current_pokemon.send_out(othertrainer.current_pokemon, self)
|
||||
if swapper.current_pokemon is not None:
|
||||
swapper.current_pokemon.has_moved = True
|
||||
|
||||
return None
|
||||
|
||||
def handle_megas(self, t1, t2):
|
||||
"""Handle mega evolving pokemon who mega evolve this turn."""
|
||||
for at, dt in ((t1, t2), (t2, t1)):
|
||||
if at.current_pokemon is not None and at.current_pokemon.should_mega_evolve:
|
||||
# Bit of a hack, since it is in its mega form and dashes are removed from `name`, it will show as "<poke> mega evolved!".
|
||||
if (at.current_pokemon.held_item == "mega-stone" or at.current_pokemon._name == "Rayquaza") and at.current_pokemon.form(at.current_pokemon._name + "-mega"):
|
||||
self.msg += f"{at.current_pokemon.name} evolved!\n"
|
||||
elif at.current_pokemon.held_item == "mega-stone-x" and at.current_pokemon.form(at.current_pokemon._name + "-mega-x"):
|
||||
self.msg += f"{at.current_pokemon.name} evolved!\n"
|
||||
elif at.current_pokemon.held_item == "mega-stone-y" and at.current_pokemon.form(at.current_pokemon._name + "-mega-y"):
|
||||
self.msg += f"{at.current_pokemon.name} evolved!\n"
|
||||
else:
|
||||
raise ValueError("expected to mega evolve but no valid mega condition")
|
||||
at.current_pokemon.ability_id = at.current_pokemon.mega_ability_id
|
||||
at.current_pokemon.starting_ability_id = at.current_pokemon.mega_ability_id
|
||||
at.current_pokemon.type_ids = at.current_pokemon.mega_type_ids.copy()
|
||||
at.current_pokemon.starting_type_ids = at.current_pokemon.mega_type_ids.copy()
|
||||
self.msg += at.current_pokemon.send_out_ability(dt.current_pokemon, self)
|
||||
at.has_mega_evolved = True
|
||||
|
||||
def __repr__(self):
|
||||
return f"Battle(trainer1={self.trainer1!r}, trainer2={self.trainer2!r})"
|
377
pokemonduel/buttons.py
Normal file
377
pokemonduel/buttons.py
Normal file
|
@ -0,0 +1,377 @@
|
|||
import discord
|
||||
import asyncio
|
||||
from .move import Move
|
||||
|
||||
# pylint: disable=C0116,W0613,W0221
|
||||
BUTTON_TIMEOUT = 60
|
||||
|
||||
|
||||
class DuelAcceptView(discord.ui.View):
|
||||
"""View to accept a duel."""
|
||||
def __init__(self, ctx: "commands.Context", opponent: discord.Member):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.ctx = ctx
|
||||
self.confirm = False
|
||||
self.event = asyncio.Event()
|
||||
self.opponent = opponent
|
||||
|
||||
@discord.ui.button(label="Accept", style=discord.ButtonStyle.green)
|
||||
async def accept(self, interaction, button):
|
||||
self.confirm = True
|
||||
await interaction.response.edit_message(view=None)
|
||||
self.stop()
|
||||
|
||||
@discord.ui.button(label="Reject", style=discord.ButtonStyle.red)
|
||||
async def reject(self, interaction, button):
|
||||
await interaction.response.edit_message(view=None)
|
||||
self.stop()
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user.id != self.opponent.id:
|
||||
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def on_timeout(self):
|
||||
await self.message.edit(view=None)
|
||||
self.stop()
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
await self.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
|
||||
class PreviewPromptView(discord.ui.View):
|
||||
"""Prompts a user to select their lead pokemon when previewing both player's parties."""
|
||||
def __init__(self, battle):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.battle = battle
|
||||
self.child_views = []
|
||||
|
||||
@discord.ui.button(label="Select a lead pokemon", style=discord.ButtonStyle.primary)
|
||||
async def actions(self, interaction, button):
|
||||
if interaction.user.id == self.battle.trainer1.id:
|
||||
trainer = self.battle.trainer1
|
||||
else:
|
||||
trainer = self.battle.trainer2
|
||||
view = LeadView(trainer, self.battle)
|
||||
self.child_views.append(view)
|
||||
await interaction.response.send_message(content="Pick a pokemon to lead with:", view=view, ephemeral=True)
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user.id == self.battle.trainer1.id:
|
||||
if self.battle.trainer1.event.is_set():
|
||||
await interaction.response.send_message(content="You have already selected a lead.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
if interaction.user.id == self.battle.trainer2.id:
|
||||
if self.battle.trainer2.event.is_set():
|
||||
await interaction.response.send_message(content="You have already selected a lead.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
async def on_timeout(self):
|
||||
self.battle.trainer1.event.set()
|
||||
self.battle.trainer2.event.set()
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
def stop(self):
|
||||
"""Override to stop child views when this view is stopped."""
|
||||
for view in self.child_views:
|
||||
view.stop()
|
||||
super().stop()
|
||||
|
||||
|
||||
class LeadView(discord.ui.View):
|
||||
"""Shows the user their pokemon, allowing them to click one to make their lead to."""
|
||||
def __init__(self, trainer, battle):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.trainer = trainer
|
||||
self.battle = battle
|
||||
for poke in trainer.party:
|
||||
self.add_item(LeadButton(poke))
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
|
||||
class LeadButton(discord.ui.Button):
|
||||
"""A button that makes the pokemon the user's lead when pressed."""
|
||||
def __init__(self, poke):
|
||||
super().__init__(style=discord.ButtonStyle.secondary, label=f"{poke._name} | {poke.hp}hp")
|
||||
self.poke = poke
|
||||
|
||||
async def callback(self, interaction):
|
||||
content = f"You will lead with {self.poke.name}. Waiting for opponent."
|
||||
self.view.trainer.switch_poke(self.view.trainer.party.index(self.poke))
|
||||
self.view.trainer.event.set()
|
||||
await interaction.response.edit_message(content=content, view=None)
|
||||
|
||||
|
||||
class BattlePromptView(discord.ui.View):
|
||||
"""Prompts users to select an action for their turn."""
|
||||
def __init__(self, battle):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.battle = battle
|
||||
self.turn = battle.turn
|
||||
self.child_views = []
|
||||
|
||||
@discord.ui.button(label="View your actions", style=discord.ButtonStyle.primary)
|
||||
async def actions(self, interaction, button):
|
||||
if interaction.user.id == self.battle.trainer1.id:
|
||||
trainer = self.battle.trainer1
|
||||
opponent = self.battle.trainer2
|
||||
else:
|
||||
trainer = self.battle.trainer2
|
||||
opponent = self.battle.trainer1
|
||||
view = MoveSelectView(self.battle, trainer, opponent)
|
||||
self.child_views.append(view)
|
||||
await interaction.response.send_message(content="Pick an action:", view=view, ephemeral=True)
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if self.battle.turn != self.turn:
|
||||
await interaction.response.send_message(content="This button has expired.", ephemeral=True)
|
||||
return False
|
||||
if self.battle.trainer1.is_human() and interaction.user.id == self.battle.trainer1.id:
|
||||
if self.battle.trainer1.selected_action is not None:
|
||||
await interaction.response.send_message(content="You have already selected an action.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
if self.battle.trainer2.is_human() and interaction.user.id == self.battle.trainer2.id:
|
||||
if self.battle.trainer2.selected_action is not None:
|
||||
await interaction.response.send_message(content="You have already selected an action.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
async def on_timeout(self):
|
||||
self.battle.trainer1.event.set()
|
||||
self.battle.trainer2.event.set()
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
def stop(self):
|
||||
"""Override to stop child views when this view is stopped."""
|
||||
for view in self.child_views:
|
||||
view.stop()
|
||||
super().stop()
|
||||
|
||||
|
||||
class MoveSelectView(discord.ui.View):
|
||||
"""Prompts the user to pick a move, enter the swap pokes view, or cancel the duel."""
|
||||
def __init__(self, battle, trainer, opponent):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.battle = battle
|
||||
self.turn = battle.turn
|
||||
self.trainer = trainer
|
||||
self.opponent = opponent
|
||||
self.child_views = []
|
||||
status_code, movedata = trainer.valid_moves(opponent.current_pokemon)
|
||||
if status_code == "forced":
|
||||
trainer.selected_action = movedata
|
||||
trainer.event.set()
|
||||
self.add_item(discord.ui.Button(style=discord.ButtonStyle.secondary, label="You were forced to play:", disabled=True))
|
||||
self.add_item(MoveButton(movedata, disabled=True))
|
||||
return
|
||||
swapdata = trainer.valid_swaps(opponent.current_pokemon, battle)
|
||||
if status_code == "struggle":
|
||||
self.add_item(MoveButton(Move.struggle()))
|
||||
self.add_item(SwapRequestButton(disabled=not swapdata))
|
||||
self.add_item(ForfeitButton(row=0))
|
||||
return
|
||||
|
||||
for idx, move in enumerate(trainer.current_pokemon.moves):
|
||||
self.add_item(MoveButton(move, disabled=idx not in movedata, row=idx // 2))
|
||||
self.add_item(SwapRequestButton(disabled=not swapdata))
|
||||
self.add_item(ForfeitButton())
|
||||
if (
|
||||
trainer.current_pokemon is not None
|
||||
and trainer.current_pokemon.mega_type_ids is not None
|
||||
and not trainer.has_mega_evolved
|
||||
):
|
||||
self.add_item(MegaEvolveButton())
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if self.battle.turn != self.turn:
|
||||
await interaction.response.send_message(content="This button has expired.", ephemeral=True)
|
||||
return False
|
||||
# Should never be hit, but just in case :P
|
||||
if interaction.user.id != self.trainer.id:
|
||||
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
|
||||
return False
|
||||
if self.trainer.selected_action is not None:
|
||||
await interaction.response.send_message(content="You have already selected an action.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
def stop(self):
|
||||
"""Override to stop child views when this view is stopped."""
|
||||
for view in self.child_views:
|
||||
view.stop()
|
||||
super().stop()
|
||||
|
||||
|
||||
class MoveButton(discord.ui.Button):
|
||||
"""A button that represents a selection of a specific move."""
|
||||
def __init__(self, move, *, disabled=False, row=0):
|
||||
label = f"{move.pretty_name}"
|
||||
if move.id != 165:
|
||||
label += f" | {move.pp}pp"
|
||||
super().__init__(style=discord.ButtonStyle.secondary, label=label, disabled=disabled, row=row)
|
||||
self.move = move
|
||||
|
||||
async def callback(self, interaction):
|
||||
self.view.trainer.selected_action = self.move
|
||||
self.view.trainer.event.set()
|
||||
await interaction.response.edit_message(content=f"You picked {self.move.pretty_name}. Waiting for opponent.", view=None)
|
||||
|
||||
|
||||
class SwapRequestButton(discord.ui.Button):
|
||||
"""A button that represents a request to swap pokemon."""
|
||||
def __init__(self, *, disabled=False):
|
||||
super().__init__(style=discord.ButtonStyle.primary, label="Swap pokemon", disabled=disabled)
|
||||
|
||||
async def callback(self, interaction):
|
||||
view = SwapView(self.view.trainer, self.view.opponent, self.view.battle, set_move=True)
|
||||
self.view.child_views.append(view)
|
||||
await interaction.response.edit_message(content="Pick a pokemon:", view=view)
|
||||
|
||||
|
||||
class DuelForfeitView(discord.ui.View):
|
||||
"""View to forfeit a duel."""
|
||||
def __init__(self, trainer):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.trainer = trainer
|
||||
self.confirm = False
|
||||
|
||||
@discord.ui.button(label="Forfeit", style=discord.ButtonStyle.red)
|
||||
async def actuallyforfeit(self, interaction, button):
|
||||
await interaction.response.edit_message(content="Forfeited.", view=None)
|
||||
self.confirm = True
|
||||
self.stop()
|
||||
|
||||
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.secondary)
|
||||
async def cancel(self, interaction, button):
|
||||
await interaction.response.edit_message(content="Not forfeiting.", view=None)
|
||||
self.stop()
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user.id != self.trainer.id:
|
||||
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
await self.trainer.party[0].held_item.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
|
||||
class ForfeitButton(discord.ui.Button):
|
||||
"""A button that forfeits the game when pressed."""
|
||||
def __init__(self, *, row=1):
|
||||
super().__init__(style=discord.ButtonStyle.danger, label="Forfeit duel", row=row)
|
||||
|
||||
async def callback(self, interaction):
|
||||
view = DuelForfeitView(self.view.trainer)
|
||||
self.view.child_views.append(view)
|
||||
await interaction.response.send_message(content="Are you sure you want to forfeit?", view=view, ephemeral=True)
|
||||
await view.wait()
|
||||
if view.confirm:
|
||||
self.view.trainer.event.set()
|
||||
|
||||
|
||||
class SwapPromptView(discord.ui.View):
|
||||
"""Prompts the trainer to view their pokemon."""
|
||||
def __init__(self, trainer, opponent, battle, *, mid_turn=False):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.trainer = trainer
|
||||
self.opponent = opponent
|
||||
self.battle = battle
|
||||
self.turn = battle.turn
|
||||
self.mid_turn = mid_turn
|
||||
self.child_views = []
|
||||
|
||||
@discord.ui.button(label="View your pokemon", style=discord.ButtonStyle.primary)
|
||||
async def swap(self, interaction, button):
|
||||
view = SwapView(self.trainer, self.opponent, self.battle, mid_turn=self.mid_turn)
|
||||
self.child_views.append(view)
|
||||
await interaction.response.send_message(
|
||||
content="Pick a pokemon to swap to:",
|
||||
view=view,
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if self.battle.turn != self.turn:
|
||||
await interaction.response.send_message(content="This button has expired.", ephemeral=True)
|
||||
return False
|
||||
if interaction.user.id != self.trainer.id:
|
||||
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
async def on_timeout(self):
|
||||
self.battle.trainer1.event.set()
|
||||
self.battle.trainer2.event.set()
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
def stop(self):
|
||||
"""Override to stop child views when this view is stopped."""
|
||||
for view in self.child_views:
|
||||
view.stop()
|
||||
super().stop()
|
||||
|
||||
|
||||
class SwapView(discord.ui.View):
|
||||
"""Shows the user their pokemon, allowing them to click one to swap to."""
|
||||
def __init__(self, trainer, opponent, battle, *, set_move=False, mid_turn=False):
|
||||
super().__init__(timeout=BUTTON_TIMEOUT)
|
||||
self.trainer = trainer
|
||||
self.opponent = opponent
|
||||
self.battle = battle
|
||||
self.set_move = set_move
|
||||
self.mid_turn = mid_turn
|
||||
swapdata = trainer.valid_swaps(opponent.current_pokemon, battle, check_trap=set_move)
|
||||
for idx, poke in enumerate(trainer.party):
|
||||
self.add_item(SwapButton(poke, disabled=idx not in swapdata))
|
||||
|
||||
async def on_error(self, interaction, error, item):
|
||||
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
|
||||
|
||||
|
||||
class SwapButton(discord.ui.Button):
|
||||
"""A button that swaps to that pokemon when pressed."""
|
||||
def __init__(self, poke, *, disabled=False):
|
||||
super().__init__(style=discord.ButtonStyle.secondary, label=f"{poke._name} | {poke.hp}hp", disabled=disabled)
|
||||
self.poke = poke
|
||||
|
||||
async def callback(self, interaction):
|
||||
content = f"You picked {self.poke.name}."
|
||||
if self.view.set_move:
|
||||
self.view.trainer.selected_action = self.view.trainer.party.index(self.poke)
|
||||
content += " Waiting for opponent."
|
||||
else:
|
||||
self.view.trainer.switch_poke(self.view.trainer.party.index(self.poke), mid_turn=self.view.mid_turn)
|
||||
self.view.trainer.event.set()
|
||||
await interaction.response.edit_message(content=content, view=None)
|
||||
|
||||
|
||||
class MegaEvolveButton(discord.ui.Button):
|
||||
"""A button that toggles whether the trainer's pokemon should mega evolve this turn."""
|
||||
def __init__(self):
|
||||
super().__init__(style=discord.ButtonStyle.gray, label="Mega Evolve", row=0)
|
||||
|
||||
def get_color(self):
|
||||
return [discord.ButtonStyle.gray, discord.ButtonStyle.green][self.view.trainer.current_pokemon.should_mega_evolve]
|
||||
|
||||
async def callback(self, interaction):
|
||||
self.view.trainer.current_pokemon.should_mega_evolve = not self.view.trainer.current_pokemon.should_mega_evolve
|
||||
self.style = self.get_color()
|
||||
await interaction.response.edit_message(view=self.view)
|
483
pokemonduel/commands.py
Normal file
483
pokemonduel/commands.py
Normal file
|
@ -0,0 +1,483 @@
|
|||
import discord
|
||||
from redbot.core import commands
|
||||
from redbot.core import Config
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import logging
|
||||
from .battle import Battle
|
||||
from .buttons import DuelAcceptView
|
||||
from .pokemon import DuelPokemon
|
||||
from .data import generate_team_preview, find, find_one
|
||||
from .trainer import MemberTrainer, NPCTrainer
|
||||
|
||||
|
||||
class TeambuilderReadException(Exception):
|
||||
"""Generic exception raised when failing to parse a teambuilder export string."""
|
||||
pass
|
||||
|
||||
|
||||
class PokemonDuel(commands.Cog):
|
||||
"""Battle in a Pokemon Duel with another member of your server."""
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.log = logging.getLogger('red.flamecogs.pokemonduel')
|
||||
self.games = {}
|
||||
self.config = Config.get_conf(self, identifier=145519400223506432)
|
||||
self.config.register_member(
|
||||
party = [],
|
||||
)
|
||||
self.config.register_guild(
|
||||
useThreads = False,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def party_from_teambuilder(ctx, teambuilder):
|
||||
"""
|
||||
Builds a party from an exported pokemon showdown teambuilder team.
|
||||
https://play.pokemonshowdown.com/teambuilder
|
||||
"""
|
||||
teambuilder = teambuilder.strip()
|
||||
party = []
|
||||
for raw in teambuilder.split("\n\n"):
|
||||
raw = raw.strip()
|
||||
lines = raw.split("\n")
|
||||
|
||||
# FIRST LINE
|
||||
nameraw = lines.pop(0)
|
||||
item = "None"
|
||||
if "@" in nameraw:
|
||||
nameraw, item = nameraw.split("@")
|
||||
item = item.strip().replace(" ", "-").lower()
|
||||
gender = "-m"
|
||||
if "(M)" in nameraw:
|
||||
nameraw = nameraw.replace("(M)", "")
|
||||
gender = "-m"
|
||||
elif "(F)" in nameraw:
|
||||
nameraw = nameraw.replace("(F)", "")
|
||||
gender = "-f"
|
||||
nick = "None"
|
||||
if "(" in nameraw:
|
||||
nick, nameraw = nameraw.split("(")
|
||||
nameraw = nameraw.strip()[:-1]
|
||||
nick = nick.strip()
|
||||
|
||||
pokname = nameraw.strip().replace(" ", "-").capitalize()
|
||||
if pokname == "nidoran":
|
||||
name += gender
|
||||
|
||||
forms = await find_one(ctx, "forms", {"identifier": pokname.lower()})
|
||||
if forms is None:
|
||||
raise TeambuilderReadException(f"`{pokname}` is not a valid pokemon.")
|
||||
pfile = await find_one(ctx, "pfile", {"id": forms["base_id"]})
|
||||
if pfile is None:
|
||||
raise TeambuilderReadException(f"Could not find a `pfile` entry for `{pokname}`. Please report this bug.")
|
||||
gender_rate = pfile["gender_rate"]
|
||||
if gender_rate == 0:
|
||||
gender = "-m"
|
||||
elif gender_rate == 8:
|
||||
gender = "-f"
|
||||
elif gender_rate == -1:
|
||||
gender = "-x"
|
||||
|
||||
# REST OF THE LINES
|
||||
hpiv = 31
|
||||
atkiv = 31
|
||||
defiv = 31
|
||||
spatkiv = 31
|
||||
spdefiv = 31
|
||||
speediv = 31
|
||||
hpev = 0
|
||||
atkev = 0
|
||||
defev = 0
|
||||
spatkev = 0
|
||||
spdefev = 0
|
||||
speedev = 0
|
||||
level = 100
|
||||
happiness = 255
|
||||
ability_index = 0
|
||||
shiny = False
|
||||
nature = "Hardy"
|
||||
moves = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith("IVs:"):
|
||||
line = line[4:].strip()
|
||||
ivs = line.split("/")
|
||||
for iv in ivs:
|
||||
amount, iv = iv.strip().split(" ")
|
||||
amount = int(amount)
|
||||
iv = iv.lower()
|
||||
if iv == "hp":
|
||||
hpiv = amount
|
||||
elif iv == "atk":
|
||||
atkiv = amount
|
||||
elif iv == "def":
|
||||
defiv = amount
|
||||
elif iv == "spa":
|
||||
spatkiv = amount
|
||||
elif iv == "spd":
|
||||
spdefiv = amount
|
||||
elif iv == "spe":
|
||||
speediv = amount
|
||||
elif line.startswith("EVs:"):
|
||||
line = line[4:].strip()
|
||||
evs = line.split("/")
|
||||
for ev in evs:
|
||||
amount, ev = ev.strip().split(" ")
|
||||
amount = int(amount)
|
||||
ev = ev.lower()
|
||||
if ev == "hp":
|
||||
hpev = amount
|
||||
elif ev == "atk":
|
||||
atkev = amount
|
||||
elif ev == "def":
|
||||
defev = amount
|
||||
elif ev == "spa":
|
||||
spatkev = amount
|
||||
elif ev == "spd":
|
||||
spdefev = amount
|
||||
elif ev == "spe":
|
||||
speedev = amount
|
||||
elif line.startswith("Shiny:"):
|
||||
if "Yes" in line:
|
||||
shiny = True
|
||||
elif line.startswith("Level:"):
|
||||
line = line[6:].strip()
|
||||
level = int(line)
|
||||
elif line.startswith("Happiness:"):
|
||||
line = line[10:].strip()
|
||||
happiness = int(line)
|
||||
elif line.endswith("Nature"):
|
||||
line = line[:-6].strip()
|
||||
nature = line.capitalize()
|
||||
elif line.startswith("Ability:"):
|
||||
ability = line[8:].strip().lower().replace(" ", "-")
|
||||
ability_raw = await find_one(ctx, "abilities", {"identifier": ability})
|
||||
if ability_raw is None:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given an ability `{ability}` which does not exist.")
|
||||
ability_id = ability_raw["id"]
|
||||
abilities = await find(ctx, "poke_abilities", {"pokemon_id": forms["pokemon_id"]})
|
||||
abilities = [a["ability_id"] for a in abilities]
|
||||
if ability_id not in abilities:
|
||||
raise TeambuilderReadException(f"`{pokname}` can not have the ability `{ability}`.")
|
||||
ability_index = abilities.index(ability_id)
|
||||
elif line.startswith("-"):
|
||||
line = line[1:].split("/")[0].strip()
|
||||
move = line.lower().replace(" ", "-")
|
||||
if move.startswith("hidden-power"):
|
||||
move = "hidden-power"
|
||||
if await find_one(ctx, "moves", {"identifier": move}) is None:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given a move `{move}` which does not exist.")
|
||||
moves.append(move)
|
||||
elif line.startswith("Hidden Power:"):
|
||||
pass
|
||||
elif line.startswith("Tera Type:"):
|
||||
pass # TODO: figure out how to handle teras
|
||||
else:
|
||||
raise TeambuilderReadException(f"Data line `{line[:200]}` is not properly formatted.")
|
||||
if len(moves) != 4:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given {len(moves)} moves. It must have exactly 4 moves.")
|
||||
evsum = sum([hpev, atkev, defev, spatkev, spdefev, speedev])
|
||||
if evsum > 510:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given {evsum} EV points. It must have no more than 510 EV points.")
|
||||
for s in [hpiv, atkiv, defiv, spatkiv, spdefiv, speediv]:
|
||||
if s > 31 or s < 0:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given an IV stat of {s}. IVs must be between 0 and 31.")
|
||||
for s in [hpev, atkev, defev, spatkev, spdefev, speedev]:
|
||||
if s > 252 or s < 0:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given an EV stat of {s}. EVs must be between 0 and 252.")
|
||||
if item != "None":
|
||||
item_raw = await find_one(ctx, "items", {"identifier": item})
|
||||
if item_raw is None:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given an item `{item}` which does not exist.")
|
||||
if item in (
|
||||
"venusaurite", "blastoisinite", "alakazite", "gengarite", "kangaskhanite", "pinsirite",
|
||||
"gyaradosite", "aerodactylite", "ampharosite", "scizorite", "heracronite", "houndoominite",
|
||||
"tyranitarite", "blazikenite", "gardevoirite", "mawilite", "aggronite", "medichamite",
|
||||
"manectite", "banettite", "absolite", "latiasite", "latiosite", "garchompite", "lucarionite",
|
||||
"abomasite", "beedrillite", "pidgeotite", "slowbronite", "steelixite", "sceptilite",
|
||||
"swampertite", "sablenite", "sharpedonite", "cameruptite", "altarianite", "glalitite",
|
||||
"salamencite", "metagrossite", "lopunnite", "galladite", "audinite", "diancite",
|
||||
):
|
||||
item = "mega-stone"
|
||||
elif item in ("charizardite-x", "mewtwonite-x"):
|
||||
item = "mega-stone-x"
|
||||
elif item in ("charizardite-y", "mewtwonite-y"):
|
||||
item = "mega-stone-y"
|
||||
if nature not in (
|
||||
"Hardy", "Bold", "Modest", "Calm", "Timid", "Lonely", "Docile", "Mild", "Gentle", "Hasty", "Adamant", "Impish", "Bashful",
|
||||
"Careful", "Jolly", "Naughty", "Lax", "Rash", "Quirky", "Naive", "Brave", "Relaxed", "Quiet", "Sassy", "Serious",
|
||||
):
|
||||
raise TeambuilderReadException(f"`{pokname}` was given a nature `{nature}` which does not exist.")
|
||||
if level > 100 or level < 1:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given a level of {level}. Its level must be between 1 and 100.")
|
||||
if happiness < 0:
|
||||
raise TeambuilderReadException(f"`{pokname}` was given a happiness of {happiness}. Its happiness must be at least 0.")
|
||||
pokemon = {
|
||||
'id': 0,
|
||||
'pokname': pokname,
|
||||
'hpiv': hpiv,
|
||||
'atkiv': atkiv,
|
||||
'defiv': defiv,
|
||||
'spatkiv': spatkiv,
|
||||
'spdefiv': spdefiv,
|
||||
'speediv': speediv,
|
||||
'hpev': hpev,
|
||||
'atkev': atkev,
|
||||
'defev': defev,
|
||||
'spatkev': spatkev,
|
||||
'spdefev': spdefev,
|
||||
'speedev': speedev,
|
||||
'moves': moves,
|
||||
'hitem': item,
|
||||
'nature': nature,
|
||||
'poknick': nick,
|
||||
'pokelevel': level,
|
||||
'happiness': happiness,
|
||||
'ability_index': ability_index,
|
||||
'gender': gender,
|
||||
'shiny': shiny,
|
||||
'radiant': False,
|
||||
'skin': None,
|
||||
}
|
||||
party.append(pokemon)
|
||||
if len(party) < 1 or len(party) > 6:
|
||||
raise TeambuilderReadException(f"Your party has {len(party)} pokemon. It must have 1 to 6 pokemon.")
|
||||
return party
|
||||
|
||||
async def wrapped_run(self, battle):
|
||||
"""
|
||||
Runs the provided battle, handling any errors that are raised.
|
||||
|
||||
Returns the output of the battle, or None if the battle errored.
|
||||
"""
|
||||
self.games[int(battle.ctx.message.id)] = battle
|
||||
try:
|
||||
winner = await battle.run()
|
||||
except (aiohttp.client_exceptions.ClientOSError, asyncio.TimeoutError):
|
||||
await battle.channel.send(
|
||||
"The bot encountered an unexpected network issue, "
|
||||
"and the duel could not continue. "
|
||||
"Please try again in a few moments.\n"
|
||||
"Note: Do not report this as a bug."
|
||||
)
|
||||
return None
|
||||
except Exception as exc:
|
||||
msg = 'Error in PokemonDuel.\n'
|
||||
self.log.exception(msg)
|
||||
self.bot.dispatch('flamecogs_game_error', battle, exc)
|
||||
await battle.channel.send(
|
||||
'A fatal error has occurred, shutting down.\n'
|
||||
'Please have the bot owner copy the error from console '
|
||||
'and post it in the support channel of <https://discord.gg/bYqCjvu>.'
|
||||
)
|
||||
return None
|
||||
else:
|
||||
if int(battle.ctx.message.id) in self.games:
|
||||
del self.games[int(battle.ctx.message.id)]
|
||||
return winner
|
||||
|
||||
@commands.group(aliases=["pokeduel"], invoke_without_command=True)
|
||||
@commands.bot_has_permissions(attach_files=True, embed_links=True)
|
||||
async def pokemonduel(self, ctx, opponent: discord.Member):
|
||||
"""Battle in a Pokemon Duel with another member of your server."""
|
||||
await self._start_duel(ctx, opponent)
|
||||
|
||||
@pokemonduel.command()
|
||||
async def inverse(self, ctx, opponent: discord.Member):
|
||||
"""Battle in an Inverse Duel with another member of your server."""
|
||||
await self._start_duel(ctx, opponent, inverse_battle=True)
|
||||
|
||||
async def _start_duel(self, ctx, opponent: discord.Member, *, inverse_battle=False):
|
||||
"""Runs a duel."""
|
||||
if opponent.id == ctx.author.id:
|
||||
await ctx.send("You cannot duel yourself!")
|
||||
return
|
||||
if opponent.bot:
|
||||
await ctx.send("You cannot duel a bot!")
|
||||
return
|
||||
|
||||
view = DuelAcceptView(ctx, opponent)
|
||||
battle_type = "an inverse battle" if inverse_battle else "a duel"
|
||||
initial_message = await ctx.send(
|
||||
f"{opponent.mention} You have been challenged to {battle_type} by {ctx.author.name}!\n",
|
||||
view=view
|
||||
)
|
||||
view.message = initial_message
|
||||
channel = ctx.channel
|
||||
if (
|
||||
await self.config.guild(ctx.guild).useThreads()
|
||||
and ctx.channel.permissions_for(ctx.guild.me).create_public_threads
|
||||
and ctx.channel.type is discord.ChannelType.text
|
||||
):
|
||||
try:
|
||||
channel = await initial_message.create_thread(
|
||||
name='PokemonDuel',
|
||||
reason='Automated thread for PokemonDuel.',
|
||||
)
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
await view.wait()
|
||||
if not view.confirm:
|
||||
return
|
||||
trainers = []
|
||||
for player in (ctx.author, opponent):
|
||||
party = await self.config.member(player).party()
|
||||
if not party:
|
||||
await channel.send(f"{player} has not setup their party yet!\nSet one with `{ctx.prefix}pokemonduel party set`.")
|
||||
return
|
||||
party = [await DuelPokemon.create(ctx, p) for p in party]
|
||||
trainers.append(MemberTrainer(player, party))
|
||||
battle = Battle(ctx, channel, *trainers, inverse_battle=inverse_battle) # pylint: disable=E1120
|
||||
preview_view = await generate_team_preview(battle)
|
||||
await battle.trainer1.event.wait()
|
||||
await battle.trainer2.event.wait()
|
||||
preview_view.stop()
|
||||
winner = await self.wrapped_run(battle)
|
||||
|
||||
@pokemonduel.group()
|
||||
async def party(self, ctx):
|
||||
"""Manage your party of pokemon."""
|
||||
pass
|
||||
|
||||
@party.command(name="set")
|
||||
async def party_set(self, ctx, *, pokemon_data):
|
||||
"""
|
||||
Set your party of pokemon.
|
||||
|
||||
In order to set your party, you will need to create a team on Pokemon Showdown Team Builder.
|
||||
1. Go to the [Team Builder site](https://play.pokemonshowdown.com/teambuilder).
|
||||
2. Click the "New Team" button.
|
||||
3. Select the format "Anything Goes".
|
||||
4. Use the "Add Pokemon" button to create a new pokemon.
|
||||
5. Pick its moves, ability, gender, level, etc.
|
||||
6. Repeat steps 4 and 5 for up to 6 total pokemon
|
||||
7. On the team view, select the "Import/Export" button at the TOP.
|
||||
8. Copy the text provided, and pass that to this command.
|
||||
"""
|
||||
try:
|
||||
party = await self.party_from_teambuilder(ctx, pokemon_data)
|
||||
except TeambuilderReadException as e:
|
||||
await ctx.send(f"Couldn't validate your team.\n{e}")
|
||||
return
|
||||
except Exception:
|
||||
await ctx.send("Couldn't properly parse your team. Make sure you follow the format provided by Showdown's Team Builder. An error has been logged to console to debug this issue.")
|
||||
self.log.exception("Failed to read a teambuilder team string.")
|
||||
return
|
||||
await self.config.member(ctx.author).party.set(party)
|
||||
embed = discord.Embed(
|
||||
title="Your new party",
|
||||
color=await ctx.embed_color(),
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
await self.gen_party_embed(ctx, party, embed)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@party.command(name="pokecord", hidden=True)
|
||||
async def party_pokecord(self, ctx, *ids: int):
|
||||
"""Create a party of pokemon imported from Pokecord."""
|
||||
pass
|
||||
|
||||
@party.command(name="list", aliases=["view"])
|
||||
async def party_list(self, ctx):
|
||||
"""View the pokemon currently in your party."""
|
||||
party = await self.config.member(ctx.author).party()
|
||||
if len(party) == 0:
|
||||
await ctx.send(f"You haven't setup your party yet!\nSet one with `{ctx.prefix}pokemonduel party set`.")
|
||||
return
|
||||
embed = discord.Embed(
|
||||
title=f"{ctx.author.display_name}'s Party",
|
||||
color=await ctx.embed_color(),
|
||||
)
|
||||
embed.set_thumbnail(url=ctx.author.display_avatar.url)
|
||||
await self.gen_party_embed(ctx, party, embed)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@staticmethod
|
||||
async def gen_party_embed(ctx, party, embed):
|
||||
"""Adds fields to the provided `embed` that are rendered descriptors of the pokemon in the provided `party`."""
|
||||
for idx, pokemon in enumerate(party):
|
||||
pokname = pokemon["pokname"]
|
||||
poknick = pokemon["poknick"]
|
||||
gender = pokemon["gender"]
|
||||
if gender == "-m":
|
||||
gender = "Male"
|
||||
elif gender == "-f":
|
||||
gender = "Female"
|
||||
elif gender == "-x":
|
||||
gender = "Genderless"
|
||||
moves = pokemon["moves"]
|
||||
moves = "|".join([f"`{x}`" for x in moves])
|
||||
ability_index = pokemon["ability_index"]
|
||||
form_info = await find_one(ctx, "forms", {"identifier": pokname.lower()})
|
||||
ab_ids = []
|
||||
for record in await find(ctx, "poke_abilities", {"pokemon_id": form_info["pokemon_id"]}):
|
||||
ab_ids.append(record["ability_id"])
|
||||
try:
|
||||
ab_id = ab_ids[ability_index]
|
||||
except IndexError:
|
||||
ab_id = ab_ids[0]
|
||||
ability = await find_one(ctx, "abilities", {"id": ab_id})
|
||||
ability = ability["identifier"]
|
||||
hitem = pokemon["hitem"]
|
||||
nature = pokemon["nature"].lower()
|
||||
happiness = pokemon["happiness"]
|
||||
hpiv = pokemon["hpiv"]
|
||||
atkiv = pokemon["atkiv"]
|
||||
defiv = pokemon["defiv"]
|
||||
spatkiv = pokemon["spatkiv"]
|
||||
spdefiv = pokemon["spdefiv"]
|
||||
speediv = pokemon["speediv"]
|
||||
hpev = pokemon["hpev"]
|
||||
atkev = pokemon["atkev"]
|
||||
defev = pokemon["defev"]
|
||||
spatkev = pokemon["spatkev"]
|
||||
spdefev = pokemon["spdefev"]
|
||||
speedev = pokemon["speedev"]
|
||||
|
||||
title = f"{gender} {pokname} "
|
||||
if poknick != "None":
|
||||
title += f"({poknick}) "
|
||||
|
||||
desc = f"{moves}\nAbility `{ability}`"
|
||||
if hitem != "None":
|
||||
desc += f" | Holding `{hitem}`"
|
||||
desc += f"\nNature `{nature}` | Happiness `{happiness}`\n"
|
||||
desc += "` `|` hp`|`atk`|`def`|`spa`|`spd`|`spe`\n"
|
||||
desc += f"`IVs:`|`{hpiv:3d}`|`{atkiv:3d}`|`{defiv:3d}`|`{spatkiv:3d}`|`{spdefiv:3d}`|`{speediv:3d}`\n"
|
||||
desc += f"`EVs:`|`{hpev:3d}`|`{atkev:3d}`|`{defev:3d}`|`{spatkev:3d}`|`{spdefev:3d}`|`{speedev:3d}`\n"
|
||||
|
||||
embed.add_field(name=title, value=desc, inline=bool(idx % 2))
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.guildowner()
|
||||
@commands.group(invoke_without_command=True)
|
||||
async def pokemonduelset(self, ctx):
|
||||
"""Config options for pokemon duels."""
|
||||
await ctx.send_help()
|
||||
cfg = await self.config.guild(ctx.guild).all()
|
||||
msg = (
|
||||
"Game contained to a thread: {useThreads}\n"
|
||||
).format_map(cfg)
|
||||
await ctx.send(f"```py\n{msg}```")
|
||||
|
||||
@pokemonduelset.command()
|
||||
async def thread(self, ctx, value: bool=None):
|
||||
"""
|
||||
Set if a thread should be created per-game to contain game messages.
|
||||
|
||||
Defaults to False.
|
||||
This value is server specific.
|
||||
"""
|
||||
if value is None:
|
||||
v = await self.config.guild(ctx.guild).useThreads()
|
||||
if v:
|
||||
await ctx.send("The game is currently run in a per-game thread.")
|
||||
else:
|
||||
await ctx.send("The game is not currently run in a thread.")
|
||||
else:
|
||||
await self.config.guild(ctx.guild).useThreads.set(value)
|
||||
if value:
|
||||
await ctx.send("The game will now be run in a per-game thread.")
|
||||
else:
|
||||
await ctx.send("The game will not be run in a thread.")
|
107
pokemonduel/data.py
Normal file
107
pokemonduel/data.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
import discord
|
||||
from redbot.core.data_manager import bundled_data_path
|
||||
import json
|
||||
from .buttons import BattlePromptView, PreviewPromptView
|
||||
|
||||
|
||||
async def find(ctx, db, filter):
|
||||
"""Fetch all matching rows from a data file."""
|
||||
path = str(bundled_data_path(ctx.cog) / db) + ".json"
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
results = []
|
||||
for item in data:
|
||||
success = True
|
||||
for key, value in filter.items():
|
||||
if isinstance(value, dict):
|
||||
if "$nin" in value:
|
||||
if item[key] in value["$nin"]:
|
||||
success = False
|
||||
break
|
||||
else:
|
||||
if item[key] != value:
|
||||
success = False
|
||||
break
|
||||
if success:
|
||||
results.append(item)
|
||||
return results
|
||||
|
||||
async def find_one(ctx, db, filter):
|
||||
"""Fetch the first matching row from a data file."""
|
||||
results = await find(ctx, db, filter)
|
||||
if results:
|
||||
return results[0]
|
||||
return None
|
||||
|
||||
async def generate_team_preview(battle):
|
||||
"""Generates a message for trainers to preview their team."""
|
||||
preview_view = PreviewPromptView(battle)
|
||||
await battle.channel.send("Select a lead pokemon:", view=preview_view)
|
||||
return preview_view
|
||||
|
||||
async def generate_main_battle_message(battle):
|
||||
"""Generates a message representing the current state of the battle."""
|
||||
desc = ""
|
||||
|
||||
if battle.weather._weather_type:
|
||||
desc += f"Weather: {battle.weather._weather_type.title()}\n" # TODO: pretty this output
|
||||
if battle.terrain.item:
|
||||
desc += f"Terrain: {battle.terrain.item.title()}\n" # TODO: pretty this output
|
||||
if battle.trick_room.active():
|
||||
desc += "Trick Room: Active\n"
|
||||
|
||||
desc += "\n"
|
||||
desc += f"{battle.trainer1.name}'s {battle.trainer1.current_pokemon.name}\n"
|
||||
desc += f" HP: {battle.trainer1.current_pokemon.hp}/{battle.trainer1.current_pokemon.starting_hp}\n"
|
||||
if battle.trainer1.current_pokemon.nv.current:
|
||||
desc += f" Status: {battle.trainer1.current_pokemon.nv.current}\n"
|
||||
if battle.trainer1.current_pokemon.substitute:
|
||||
desc += " Behind a substitute!\n"
|
||||
|
||||
desc += "\n"
|
||||
desc += f"{battle.trainer2.name}'s {battle.trainer2.current_pokemon.name}\n"
|
||||
desc += f" HP: {battle.trainer2.current_pokemon.hp}/{battle.trainer2.current_pokemon.starting_hp}\n"
|
||||
if battle.trainer2.current_pokemon.nv.current:
|
||||
desc += f" Status: {battle.trainer2.current_pokemon.nv.current}\n"
|
||||
if battle.trainer2.current_pokemon.substitute:
|
||||
desc += " Behind a substitute!\n"
|
||||
|
||||
desc = f"```\n{desc.strip()}```"
|
||||
e = discord.Embed(
|
||||
title=f"Battle between {battle.trainer1.name} and {battle.trainer2.name}",
|
||||
color=await battle.ctx.embed_color(),
|
||||
description = desc,
|
||||
)
|
||||
e.set_footer(text="Who Wins!?")
|
||||
try: #aiohttp 3.7 introduced a bug in dpy which causes this to error when rate limited. This catch just lets the bot continue when that happens.
|
||||
battle_view = BattlePromptView(battle)
|
||||
await battle.channel.send(embed=e, view=battle_view)
|
||||
except RuntimeError:
|
||||
pass
|
||||
return battle_view
|
||||
|
||||
async def generate_text_battle_message(battle):
|
||||
"""
|
||||
Send battle.msg in a boilerplate embed.
|
||||
|
||||
Handles the message being too long.
|
||||
"""
|
||||
page = ""
|
||||
pages = []
|
||||
base_embed = discord.Embed(color=await battle.ctx.embed_color())
|
||||
raw = battle.msg.strip().split("\n")
|
||||
for part in raw:
|
||||
if len(page + part) > 2000:
|
||||
embed = base_embed.copy()
|
||||
embed.description = page.strip()
|
||||
pages.append(embed)
|
||||
page = ""
|
||||
page += part + "\n"
|
||||
page = page.strip()
|
||||
if page:
|
||||
embed = base_embed.copy()
|
||||
embed.description = page
|
||||
pages.append(embed)
|
||||
for page in pages:
|
||||
await battle.channel.send(embed=page)
|
||||
battle.msg = ""
|
2222
pokemonduel/data/abilities.json
Normal file
2222
pokemonduel/data/abilities.json
Normal file
File diff suppressed because it is too large
Load diff
19728
pokemonduel/data/forms.json
Normal file
19728
pokemonduel/data/forms.json
Normal file
File diff suppressed because it is too large
Load diff
8118
pokemonduel/data/items.json
Normal file
8118
pokemonduel/data/items.json
Normal file
File diff suppressed because it is too large
Load diff
15319
pokemonduel/data/moves.json
Normal file
15319
pokemonduel/data/moves.json
Normal file
File diff suppressed because it is too large
Load diff
227
pokemonduel/data/natures.json
Normal file
227
pokemonduel/data/natures.json
Normal file
|
@ -0,0 +1,227 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"identifier": "hardy",
|
||||
"decreased_stat_id": 2,
|
||||
"increased_stat_id": 2,
|
||||
"hates_flavor_id": 1,
|
||||
"likes_flavor_id": 1,
|
||||
"game_index": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"identifier": "bold",
|
||||
"decreased_stat_id": 2,
|
||||
"increased_stat_id": 3,
|
||||
"hates_flavor_id": 1,
|
||||
"likes_flavor_id": 5,
|
||||
"game_index": 5
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"identifier": "modest",
|
||||
"decreased_stat_id": 2,
|
||||
"increased_stat_id": 4,
|
||||
"hates_flavor_id": 1,
|
||||
"likes_flavor_id": 2,
|
||||
"game_index": 15
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"identifier": "calm",
|
||||
"decreased_stat_id": 2,
|
||||
"increased_stat_id": 5,
|
||||
"hates_flavor_id": 1,
|
||||
"likes_flavor_id": 4,
|
||||
"game_index": 20
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"identifier": "timid",
|
||||
"decreased_stat_id": 2,
|
||||
"increased_stat_id": 6,
|
||||
"hates_flavor_id": 1,
|
||||
"likes_flavor_id": 3,
|
||||
"game_index": 10
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"identifier": "lonely",
|
||||
"decreased_stat_id": 3,
|
||||
"increased_stat_id": 2,
|
||||
"hates_flavor_id": 5,
|
||||
"likes_flavor_id": 1,
|
||||
"game_index": 1
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"identifier": "docile",
|
||||
"decreased_stat_id": 3,
|
||||
"increased_stat_id": 3,
|
||||
"hates_flavor_id": 5,
|
||||
"likes_flavor_id": 5,
|
||||
"game_index": 6
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"identifier": "mild",
|
||||
"decreased_stat_id": 3,
|
||||
"increased_stat_id": 4,
|
||||
"hates_flavor_id": 5,
|
||||
"likes_flavor_id": 2,
|
||||
"game_index": 16
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"identifier": "gentle",
|
||||
"decreased_stat_id": 3,
|
||||
"increased_stat_id": 5,
|
||||
"hates_flavor_id": 5,
|
||||
"likes_flavor_id": 4,
|
||||
"game_index": 21
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"identifier": "hasty",
|
||||
"decreased_stat_id": 3,
|
||||
"increased_stat_id": 6,
|
||||
"hates_flavor_id": 5,
|
||||
"likes_flavor_id": 3,
|
||||
"game_index": 11
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"identifier": "adamant",
|
||||
"decreased_stat_id": 4,
|
||||
"increased_stat_id": 2,
|
||||
"hates_flavor_id": 2,
|
||||
"likes_flavor_id": 1,
|
||||
"game_index": 3
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"identifier": "impish",
|
||||
"decreased_stat_id": 4,
|
||||
"increased_stat_id": 3,
|
||||
"hates_flavor_id": 2,
|
||||
"likes_flavor_id": 5,
|
||||
"game_index": 8
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"identifier": "bashful",
|
||||
"decreased_stat_id": 4,
|
||||
"increased_stat_id": 4,
|
||||
"hates_flavor_id": 2,
|
||||
"likes_flavor_id": 2,
|
||||
"game_index": 18
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"identifier": "careful",
|
||||
"decreased_stat_id": 4,
|
||||
"increased_stat_id": 5,
|
||||
"hates_flavor_id": 2,
|
||||
"likes_flavor_id": 4,
|
||||
"game_index": 23
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"identifier": "rash",
|
||||
"decreased_stat_id": 5,
|
||||
"increased_stat_id": 4,
|
||||
"hates_flavor_id": 4,
|
||||
"likes_flavor_id": 2,
|
||||
"game_index": 19
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"identifier": "jolly",
|
||||
"decreased_stat_id": 4,
|
||||
"increased_stat_id": 6,
|
||||
"hates_flavor_id": 2,
|
||||
"likes_flavor_id": 3,
|
||||
"game_index": 13
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"identifier": "naughty",
|
||||
"decreased_stat_id": 5,
|
||||
"increased_stat_id": 2,
|
||||
"hates_flavor_id": 4,
|
||||
"likes_flavor_id": 1,
|
||||
"game_index": 4
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"identifier": "lax",
|
||||
"decreased_stat_id": 5,
|
||||
"increased_stat_id": 3,
|
||||
"hates_flavor_id": 4,
|
||||
"likes_flavor_id": 5,
|
||||
"game_index": 9
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"identifier": "quirky",
|
||||
"decreased_stat_id": 5,
|
||||
"increased_stat_id": 5,
|
||||
"hates_flavor_id": 4,
|
||||
"likes_flavor_id": 4,
|
||||
"game_index": 24
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"identifier": "naive",
|
||||
"decreased_stat_id": 5,
|
||||
"increased_stat_id": 6,
|
||||
"hates_flavor_id": 4,
|
||||
"likes_flavor_id": 3,
|
||||
"game_index": 14
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"identifier": "brave",
|
||||
"decreased_stat_id": 6,
|
||||
"increased_stat_id": 2,
|
||||
"hates_flavor_id": 3,
|
||||
"likes_flavor_id": 1,
|
||||
"game_index": 2
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"identifier": "relaxed",
|
||||
"decreased_stat_id": 6,
|
||||
"increased_stat_id": 3,
|
||||
"hates_flavor_id": 3,
|
||||
"likes_flavor_id": 5,
|
||||
"game_index": 7
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"identifier": "quiet",
|
||||
"decreased_stat_id": 6,
|
||||
"increased_stat_id": 4,
|
||||
"hates_flavor_id": 3,
|
||||
"likes_flavor_id": 2,
|
||||
"game_index": 17
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"identifier": "sassy",
|
||||
"decreased_stat_id": 6,
|
||||
"increased_stat_id": 5,
|
||||
"hates_flavor_id": 3,
|
||||
"likes_flavor_id": 4,
|
||||
"game_index": 22
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"identifier": "serious",
|
||||
"decreased_stat_id": 6,
|
||||
"increased_stat_id": 6,
|
||||
"hates_flavor_id": 3,
|
||||
"likes_flavor_id": 3,
|
||||
"game_index": 12
|
||||
}
|
||||
]
|
21762
pokemonduel/data/pfile.json
Normal file
21762
pokemonduel/data/pfile.json
Normal file
File diff suppressed because it is too large
Load diff
16950
pokemonduel/data/poke_abilities.json
Normal file
16950
pokemonduel/data/poke_abilities.json
Normal file
File diff suppressed because it is too large
Load diff
14104
pokemonduel/data/pokemon_stats.json
Normal file
14104
pokemonduel/data/pokemon_stats.json
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue