This commit is contained in:
Valerie 2025-04-02 22:57:51 -04:00
parent 787a367d13
commit 676bcc8b10
170 changed files with 141166 additions and 0 deletions

64
dictionary/README.rst Normal file
View 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!

View 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"

View 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 "Δείτε την πηγή"

View 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"

View 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"

View 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"

View 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"

View 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 "ソースを見る"

View 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 ""

View 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"

View 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"

View 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"

View 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"

View 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"

View 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 "Просмотреть источник"

View 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"

View 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
View 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

View file

@ -0,0 +1 @@
{"needed_utils_version": 7.0}

120
dictionary/view.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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)

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
{"needed_utils_version": 7.0}

373
lock/LICENSE.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 "ループの状態を確認するためのエンベデッドを取得します。"

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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]

View file

@ -0,0 +1 @@
{"needed_utils_version": 7.0}

13
mod/__init__.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 = ""

File diff suppressed because it is too large Load diff

19728
pokemonduel/data/forms.json Normal file

File diff suppressed because it is too large Load diff

8118
pokemonduel/data/items.json Normal file

File diff suppressed because it is too large Load diff

15319
pokemonduel/data/moves.json Normal file

File diff suppressed because it is too large Load diff

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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