diff --git a/.github/workflows/deploy_gipsy-beta.yaml b/.github/workflows/deploy_gipsy-beta.yaml new file mode 100644 index 00000000..53621802 --- /dev/null +++ b/.github/workflows/deploy_gipsy-beta.yaml @@ -0,0 +1,38 @@ +name: Deploy Gipsy Beta + +on: + workflow_dispatch: + push: + branches: + - beta + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 # fetch all history + submodules: recursive + - name: Deploy to server + uses: swznd/sftp-deploy@master + with: + host: ${{ secrets.BETA_SFTP_IP }} + port: ${{ secrets.BETA_SFTP_PORT }} + user: ${{ secrets.BETA_SFTP_USER }} + password: ${{ secrets.BETA_SFTP_PASSWORD }} + ignore: .github/**,.gitignore,**/.gitignore,**/.git/** + - name: Restart running bot + env: + GIPSY_RESTART_BETA_ENDPOINT: ${{ secrets.GIPSY_RESTART_BETA_ENDPOINT }} + GIPSY_RESTART_BETA_BEARER: ${{ secrets.GIPSY_RESTART_BETA_BEARER }} + run: | + curl $GIPSY_RESTART_BETA_ENDPOINT \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $GIPSY_RESTART_BETA_BEARER" \ + -X POST \ + -d '{ + "signal": "restart" + }' diff --git a/.github/workflows/deploy_gipsy-prod.yaml b/.github/workflows/deploy_gipsy-prod.yaml new file mode 100644 index 00000000..0ef9075a --- /dev/null +++ b/.github/workflows/deploy_gipsy-prod.yaml @@ -0,0 +1,38 @@ +name: Deploy Gipsy Prod + +on: + workflow_dispatch: + #push: + # branches: + # - master + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 # fetch all history + submodules: recursive + - name: Deploy to server + uses: swznd/sftp-deploy@master + with: + host: ${{ secrets.PROD_SFTP_IP }} + port: ${{ secrets.PROD_SFTP_PORT }} + user: ${{ secrets.PROD_SFTP_USER }} + password: ${{ secrets.PROD_SFTP_PASSWORD }} + ignore: .github/**,.gitignore,**/.gitignore,**/.git/** + - name: Restart running bot + env: + GIPSY_RESTART_PROD_ENDPOINT: ${{ secrets.GIPSY_RESTART_PROD_ENDPOINT }} + GIPSY_RESTART_PROD_BEARER: ${{ secrets.GIPSY_RESTART_PROD_BEARER }} + run: | + curl $GIPSY_RESTART_PROD_ENDPOINT \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer $GIPSY_RESTART_PROD_BEARER' \ + -X POST \ + -d '{ + "signal": "restart" + }' \ No newline at end of file diff --git a/.github/workflows/python-linter-pr.yaml b/.github/workflows/python-linter-pr.yaml index 270874a7..69bc43f4 100644 --- a/.github/workflows/python-linter-pr.yaml +++ b/.github/workflows/python-linter-pr.yaml @@ -29,6 +29,8 @@ jobs: steps: - name: Check out Git repository uses: actions/checkout@v3 + with: + submodules: recursive - name: Set up Python uses: actions/setup-python@v4 @@ -36,6 +38,9 @@ jobs: python-version: '3.10' - name: Install Python dependencies + run: pip install -r requirements.txt + + - name: Install Linters run: pip install black pylint - name: Run linters diff --git a/.github/workflows/python-linter-push.yaml b/.github/workflows/python-linter-push.yaml index 777d1a63..8f14405c 100644 --- a/.github/workflows/python-linter-push.yaml +++ b/.github/workflows/python-linter-push.yaml @@ -26,6 +26,8 @@ jobs: steps: - name: Check out Git repository uses: actions/checkout@v3 + with: + submodules: recursive - name: Set up Python uses: actions/setup-python@v4 @@ -33,6 +35,9 @@ jobs: python-version: '3.10' - name: Install Python dependencies + run: pip install -r requirements.txt + + - name: Install Linters run: pip install black pylint - name: Run linters @@ -42,6 +47,6 @@ jobs: pylint: true pylint_args: "--disable=C,R,I" auto_fix: true - git_name: "GuniLint" - git_email: "linter@gunivers.net" + git_name: "GuniBot" + git_email: "gunibot@noreply.github.com" commit_message: "🌟 style: fix code style issues with ${linter}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1b5e6cdc..fdf82969 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .vscode __pycache__ configs/*.json -logs/ +/config.yaml +/logs/ .DS_Store data/*.db* config.json @@ -12,7 +13,8 @@ config.json .idea/ venv/ env/ -docs/_* -docs/Makefile -docs/make.bat -require.json \ No newline at end of file +docs/_templates +docs/_build +docs/build +require.json +docs/plugins diff --git a/LICENSE.md b/LICENSE.md index 0820eb51..cc70a345 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,3 +1,549 @@ -# License +For the English version of the licence, see [LICENSE_en.md](./LICENSE_en.md) -Nothing here... \ No newline at end of file + CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL + +Version 2.1 du 2013-06-21 + + + Avertissement + +Ce contrat est une licence de logiciel libre issue d'une concertation +entre ses auteurs afin que le respect de deux grands principes préside à +sa rédaction: + + * d'une part, le respect des principes de diffusion des logiciels + libres: accès au code source, droits étendus conférés aux utilisateurs, + * d'autre part, la désignation d'un droit applicable, le droit + français, auquel elle est conforme, tant au regard du droit de la + responsabilité civile que du droit de la propriété intellectuelle et + de la protection qu'il offre aux auteurs et titulaires des droits + patrimoniaux sur un logiciel. + +Les auteurs de la licence CeCILL (Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +sont: + +Commissariat à l'énergie atomique et aux énergies alternatives - CEA, +établissement public de recherche à caractère scientifique, technique et +industriel, dont le siège est situé 25 rue Leblanc, immeuble Le Ponant +D, 75015 Paris. + +Centre National de la Recherche Scientifique - CNRS, établissement +public à caractère scientifique et technologique, dont le siège est +situé 3 rue Michel-Ange, 75794 Paris cedex 16. + +Institut National de Recherche en Informatique et en Automatique - +Inria, établissement public à caractère scientifique et technologique, +dont le siège est situé Domaine de Voluceau, Rocquencourt, BP 105, 78153 +Le Chesnay cedex. + + + Préambule + +Ce contrat est une licence de logiciel libre dont l'objectif est de +conférer aux utilisateurs la liberté de modification et de +redistribution du logiciel régi par cette licence dans le cadre d'un +modèle de diffusion en logiciel libre. + +L'exercice de ces libertés est assorti de certains devoirs à la charge +des utilisateurs afin de préserver ce statut au cours des +redistributions ultérieures. + +L'accessibilité au code source et les droits de copie, de modification +et de redistribution qui en découlent ont pour contrepartie de n'offrir +aux utilisateurs qu'une garantie limitée et de ne faire peser sur +l'auteur du logiciel, le titulaire des droits patrimoniaux et les +concédants successifs qu'une responsabilité restreinte. + +A cet égard l'attention de l'utilisateur est attirée sur les risques +associés au chargement, à l'utilisation, à la modification et/ou au +développement et à la reproduction du logiciel par l'utilisateur étant +donné sa spécificité de logiciel libre, qui peut le rendre complexe à +manipuler et qui le réserve donc à des développeurs ou des +professionnels avertis possédant des connaissances informatiques +approfondies. Les utilisateurs sont donc invités à charger et tester +l'adéquation du logiciel à leurs besoins dans des conditions permettant +d'assurer la sécurité de leurs systèmes et/ou de leurs données et, plus +généralement, à l'utiliser et l'exploiter dans les mêmes conditions de +sécurité. Ce contrat peut être reproduit et diffusé librement, sous +réserve de le conserver en l'état, sans ajout ni suppression de clauses. + +Ce contrat est susceptible de s'appliquer à tout logiciel dont le +titulaire des droits patrimoniaux décide de soumettre l'exploitation aux +dispositions qu'il contient. + +Une liste de questions fréquemment posées se trouve sur le site web +officiel de la famille des licences CeCILL +(http://www.cecill.info/index.fr.html) pour toute clarification qui +serait nécessaire. + + + Article 1 - DEFINITIONS + +Dans ce contrat, les termes suivants, lorsqu'ils seront écrits avec une +lettre capitale, auront la signification suivante: + +Contrat: désigne le présent contrat de licence, ses éventuelles versions +postérieures et annexes. + +Logiciel: désigne le logiciel sous sa forme de Code Objet et/ou de Code +Source et le cas échéant sa documentation, dans leur état au moment de +l'acceptation du Contrat par le Licencié. + +Logiciel Initial: désigne le Logiciel sous sa forme de Code Source et +éventuellement de Code Objet et le cas échéant sa documentation, dans +leur état au moment de leur première diffusion sous les termes du Contrat. + +Logiciel Modifié: désigne le Logiciel modifié par au moins une +Contribution. + +Code Source: désigne l'ensemble des instructions et des lignes de +programme du Logiciel et auquel l'accès est nécessaire en vue de +modifier le Logiciel. + +Code Objet: désigne les fichiers binaires issus de la compilation du +Code Source. + +Titulaire: désigne le ou les détenteurs des droits patrimoniaux d'auteur +sur le Logiciel Initial. + +Licencié: désigne le ou les utilisateurs du Logiciel ayant accepté le +Contrat. + +Contributeur: désigne le Licencié auteur d'au moins une Contribution. + +Concédant: désigne le Titulaire ou toute personne physique ou morale +distribuant le Logiciel sous le Contrat. + +Contribution: désigne l'ensemble des modifications, corrections, +traductions, adaptations et/ou nouvelles fonctionnalités intégrées dans +le Logiciel par tout Contributeur, ainsi que tout Module Interne. + +Module: désigne un ensemble de fichiers sources y compris leur +documentation qui permet de réaliser des fonctionnalités ou services +supplémentaires à ceux fournis par le Logiciel. + +Module Externe: désigne tout Module, non dérivé du Logiciel, tel que ce +Module et le Logiciel s'exécutent dans des espaces d'adressage +différents, l'un appelant l'autre au moment de leur exécution. + +Module Interne: désigne tout Module lié au Logiciel de telle sorte +qu'ils s'exécutent dans le même espace d'adressage. + +GNU GPL: désigne la GNU General Public License dans sa version 2 ou +toute version ultérieure, telle que publiée par Free Software Foundation +Inc. + +GNU Affero GPL: désigne la GNU Affero General Public License dans sa +version 3 ou toute version ultérieure, telle que publiée par Free +Software Foundation Inc. + +EUPL: désigne la Licence Publique de l'Union européenne dans sa version +1.1 ou toute version ultérieure, telle que publiée par la Commission +Européenne. + +Parties: désigne collectivement le Licencié et le Concédant. + +Ces termes s'entendent au singulier comme au pluriel. + + + Article 2 - OBJET + +Le Contrat a pour objet la concession par le Concédant au Licencié d'une +licence non exclusive, cessible et mondiale du Logiciel telle que +définie ci-après à l'article 5 <#etendue> pour toute la durée de +protection des droits portant sur ce Logiciel. + + + Article 3 - ACCEPTATION + +3.1 L'acceptation par le Licencié des termes du Contrat est réputée +acquise du fait du premier des faits suivants: + + * (i) le chargement du Logiciel par tout moyen notamment par + téléchargement à partir d'un serveur distant ou par chargement à + partir d'un support physique; + * (ii) le premier exercice par le Licencié de l'un quelconque des + droits concédés par le Contrat. + +3.2 Un exemplaire du Contrat, contenant notamment un avertissement +relatif aux spécificités du Logiciel, à la restriction de garantie et à +la limitation à un usage par des utilisateurs expérimentés a été mis à +disposition du Licencié préalablement à son acceptation telle que +définie à l'article 3.1 <#acceptation-acquise> ci dessus et le Licencié +reconnaît en avoir pris connaissance. + + + Article 4 - ENTREE EN VIGUEUR ET DUREE + + + 4.1 ENTREE EN VIGUEUR + +Le Contrat entre en vigueur à la date de son acceptation par le Licencié +telle que définie en 3.1 <#acceptation-acquise>. + + + 4.2 DUREE + +Le Contrat produira ses effets pendant toute la durée légale de +protection des droits patrimoniaux portant sur le Logiciel. + + + Article 5 - ETENDUE DES DROITS CONCEDES + +Le Concédant concède au Licencié, qui accepte, les droits suivants sur +le Logiciel pour toutes destinations et pour la durée du Contrat dans +les conditions ci-après détaillées. + +Par ailleurs, si le Concédant détient ou venait à détenir un ou +plusieurs brevets d'invention protégeant tout ou partie des +fonctionnalités du Logiciel ou de ses composants, il s'engage à ne pas +opposer les éventuels droits conférés par ces brevets aux Licenciés +successifs qui utiliseraient, exploiteraient ou modifieraient le +Logiciel. En cas de cession de ces brevets, le Concédant s'engage à +faire reprendre les obligations du présent alinéa aux cessionnaires. + + + 5.1 DROIT D'UTILISATION + +Le Licencié est autorisé à utiliser le Logiciel, sans restriction quant +aux domaines d'application, étant ci-après précisé que cela comporte: + + 1. + + la reproduction permanente ou provisoire du Logiciel en tout ou + partie par tout moyen et sous toute forme. + + 2. + + le chargement, l'affichage, l'exécution, ou le stockage du Logiciel + sur tout support. + + 3. + + la possibilité d'en observer, d'en étudier, ou d'en tester le + fonctionnement afin de déterminer les idées et principes qui sont à + la base de n'importe quel élément de ce Logiciel; et ceci, lorsque + le Licencié effectue toute opération de chargement, d'affichage, + d'exécution, de transmission ou de stockage du Logiciel qu'il est en + droit d'effectuer en vertu du Contrat. + + + 5.2 DROIT D'APPORTER DES CONTRIBUTIONS + +Le droit d'apporter des Contributions comporte le droit de traduire, +d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel +et le droit de reproduire le logiciel en résultant. + +Le Licencié est autorisé à apporter toute Contribution au Logiciel sous +réserve de mentionner, de façon explicite, son nom en tant qu'auteur de +cette Contribution et la date de création de celle-ci. + + + 5.3 DROIT DE DISTRIBUTION + +Le droit de distribution comporte notamment le droit de diffuser, de +transmettre et de communiquer le Logiciel au public sur tout support et +par tout moyen ainsi que le droit de mettre sur le marché à titre +onéreux ou gratuit, un ou des exemplaires du Logiciel par tout procédé. + +Le Licencié est autorisé à distribuer des copies du Logiciel, modifié ou +non, à des tiers dans les conditions ci-après détaillées. + + + 5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION + +Le Licencié est autorisé à distribuer des copies conformes du Logiciel, +sous forme de Code Source ou de Code Objet, à condition que cette +distribution respecte les dispositions du Contrat dans leur totalité et +soit accompagnée: + + 1. + + d'un exemplaire du Contrat, + + 2. + + d'un avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que prévue aux articles 8 + <#responsabilite> et 9 <#garantie>, + +et que, dans le cas où seul le Code Objet du Logiciel est redistribué, +le Licencié permette un accès effectif au Code Source complet du +Logiciel pour une durée d'au moins 3 ans à compter de la distribution du +logiciel, étant entendu que le coût additionnel d'acquisition du Code +Source ne devra pas excéder le simple coût de transfert des données. + + + 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE + +Lorsque le Licencié apporte une Contribution au Logiciel, les conditions +de distribution du Logiciel Modifié en résultant sont alors soumises à +l'intégralité des dispositions du Contrat. + +Le Licencié est autorisé à distribuer le Logiciel Modifié, sous forme de +code source ou de code objet, à condition que cette distribution +respecte les dispositions du Contrat dans leur totalité et soit +accompagnée: + + 1. + + d'un exemplaire du Contrat, + + 2. + + d'un avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que prévue aux articles 8 + <#responsabilite> et 9 <#garantie>, + +et, dans le cas où seul le code objet du Logiciel Modifié est redistribué, + + 3. + + d'une note précisant les conditions d'accès effectif au code source + complet du Logiciel Modifié, pendant une période d'au moins 3 ans à + compter de la distribution du Logiciel Modifié, étant entendu que le + coût additionnel d'acquisition du code source ne devra pas excéder + le simple coût de transfert des données. + + + 5.3.3 DISTRIBUTION DES MODULES EXTERNES + +Lorsque le Licencié a développé un Module Externe les conditions du +Contrat ne s'appliquent pas à ce Module Externe, qui peut être distribué +sous un contrat de licence différent. + + + 5.3.4 COMPATIBILITE AVEC D'AUTRES LICENCES + +Le Licencié peut inclure un code soumis aux dispositions d'une des +versions de la licence GNU GPL, GNU Affero GPL et/ou EUPL dans le +Logiciel modifié ou non et distribuer l'ensemble sous les conditions de +la même version de la licence GNU GPL, GNU Affero GPL et/ou EUPL. + +Le Licencié peut inclure le Logiciel modifié ou non dans un code soumis +aux dispositions d'une des versions de la licence GNU GPL, GNU Affero +GPL et/ou EUPL et distribuer l'ensemble sous les conditions de la même +version de la licence GNU GPL, GNU Affero GPL et/ou EUPL. + + + Article 6 - PROPRIETE INTELLECTUELLE + + + 6.1 SUR LE LOGICIEL INITIAL + +Le Titulaire est détenteur des droits patrimoniaux sur le Logiciel +Initial. Toute utilisation du Logiciel Initial est soumise au respect +des conditions dans lesquelles le Titulaire a choisi de diffuser son +oeuvre et nul autre n'a la faculté de modifier les conditions de +diffusion de ce Logiciel Initial. + +Le Titulaire s'engage à ce que le Logiciel Initial reste au moins régi +par le Contrat et ce, pour la durée visée à l'article 4.2 <#duree>. + + + 6.2 SUR LES CONTRIBUTIONS + +Le Licencié qui a développé une Contribution est titulaire sur celle-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable. + + + 6.3 SUR LES MODULES EXTERNES + +Le Licencié qui a développé un Module Externe est titulaire sur celui-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable et reste libre du choix du contrat régissant +sa diffusion. + + + 6.4 DISPOSITIONS COMMUNES + +Le Licencié s'engage expressément: + + 1. + + à ne pas supprimer ou modifier de quelque manière que ce soit les + mentions de propriété intellectuelle apposées sur le Logiciel; + + 2. + + à reproduire à l'identique lesdites mentions de propriété + intellectuelle sur les copies du Logiciel modifié ou non. + +Le Licencié s'engage à ne pas porter atteinte, directement ou +indirectement, aux droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs sur le Logiciel et à prendre, le cas échéant, à +l'égard de son personnel toutes les mesures nécessaires pour assurer le +respect des dits droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs. + + + Article 7 - SERVICES ASSOCIES + +7.1 Le Contrat n'oblige en aucun cas le Concédant à la réalisation de +prestations d'assistance technique ou de maintenance du Logiciel. + +Cependant le Concédant reste libre de proposer ce type de services. Les +termes et conditions d'une telle assistance technique et/ou d'une telle +maintenance seront alors déterminés dans un acte séparé. Ces actes de +maintenance et/ou assistance technique n'engageront que la seule +responsabilité du Concédant qui les propose. + +7.2 De même, tout Concédant est libre de proposer, sous sa seule +responsabilité, à ses licenciés une garantie, qui n'engagera que lui, +lors de la redistribution du Logiciel et/ou du Logiciel Modifié et ce, +dans les conditions qu'il souhaite. Cette garantie et les modalités +financières de son application feront l'objet d'un acte séparé entre le +Concédant et le Licencié. + + + Article 8 - RESPONSABILITE + +8.1 Sous réserve des dispositions de l'article 8.2 +<#limite-responsabilite>, le Licencié a la faculté, sous réserve de +prouver la faute du Concédant concerné, de solliciter la réparation du +préjudice direct qu'il subirait du fait du Logiciel et dont il apportera +la preuve. + +8.2 La responsabilité du Concédant est limitée aux engagements pris en +application du Contrat et ne saurait être engagée en raison notamment: +(i) des dommages dus à l'inexécution, totale ou partielle, de ses +obligations par le Licencié, (ii) des dommages directs ou indirects +découlant de l'utilisation ou des performances du Logiciel subis par le +Licencié et (iii) plus généralement d'un quelconque dommage indirect. En +particulier, les Parties conviennent expressément que tout préjudice +financier ou commercial (par exemple perte de données, perte de +bénéfices, perte d'exploitation, perte de clientèle ou de commandes, +manque à gagner, trouble commercial quelconque) ou toute action dirigée +contre le Licencié par un tiers, constitue un dommage indirect et +n'ouvre pas droit à réparation par le Concédant. + + + Article 9 - GARANTIE + +9.1 Le Licencié reconnaît que l'état actuel des connaissances +scientifiques et techniques au moment de la mise en circulation du +Logiciel ne permet pas d'en tester et d'en vérifier toutes les +utilisations ni de détecter l'existence d'éventuels défauts. L'attention +du Licencié a été attirée sur ce point sur les risques associés au +chargement, à l'utilisation, la modification et/ou au développement et à +la reproduction du Logiciel qui sont réservés à des utilisateurs avertis. + +Il relève de la responsabilité du Licencié de contrôler, par tous +moyens, l'adéquation du produit à ses besoins, son bon fonctionnement et +de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens. + +9.2 Le Concédant déclare de bonne foi être en droit de concéder +l'ensemble des droits attachés au Logiciel (comprenant notamment les +droits visés à l'article 5 <#etendue>). + +9.3 Le Licencié reconnaît que le Logiciel est fourni "en l'état" par le +Concédant sans autre garantie, expresse ou tacite, que celle prévue à +l'article 9.2 <#bonne-foi> et notamment sans aucune garantie sur sa +valeur commerciale, son caractère sécurisé, innovant ou pertinent. + +En particulier, le Concédant ne garantit pas que le Logiciel est exempt +d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible +avec l'équipement du Licencié et sa configuration logicielle ni qu'il +remplira les besoins du Licencié. + +9.4 Le Concédant ne garantit pas, de manière expresse ou tacite, que le +Logiciel ne porte pas atteinte à un quelconque droit de propriété +intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout +autre droit de propriété. Ainsi, le Concédant exclut toute garantie au +profit du Licencié contre les actions en contrefaçon qui pourraient être +diligentées au titre de l'utilisation, de la modification, et de la +redistribution du Logiciel. Néanmoins, si de telles actions sont +exercées contre le Licencié, le Concédant lui apportera son expertise +technique et juridique pour sa défense. Cette expertise technique et +juridique est déterminée au cas par cas entre le Concédant concerné et +le Licencié dans le cadre d'un protocole d'accord. Le Concédant dégage +toute responsabilité quant à l'utilisation de la dénomination du +Logiciel par le Licencié. Aucune garantie n'est apportée quant à +l'existence de droits antérieurs sur le nom du Logiciel et sur +l'existence d'une marque. + + + Article 10 - RESILIATION + +10.1 En cas de manquement par le Licencié aux obligations mises à sa +charge par le Contrat, le Concédant pourra résilier de plein droit le +Contrat trente (30) jours après notification adressée au Licencié et +restée sans effet. + +10.2 Le Licencié dont le Contrat est résilié n'est plus autorisé à +utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les +licences qu'il aura concédées antérieurement à la résiliation du Contrat +resteront valides sous réserve qu'elles aient été effectuées en +conformité avec le Contrat. + + + Article 11 - DISPOSITIONS DIVERSES + + + 11.1 CAUSE EXTERIEURE + +Aucune des Parties ne sera responsable d'un retard ou d'une défaillance +d'exécution du Contrat qui serait dû à un cas de force majeure, un cas +fortuit ou une cause extérieure, telle que, notamment, le mauvais +fonctionnement ou les interruptions du réseau électrique ou de +télécommunication, la paralysie du réseau liée à une attaque +informatique, l'intervention des autorités gouvernementales, les +catastrophes naturelles, les dégâts des eaux, les tremblements de terre, +le feu, les explosions, les grèves et les conflits sociaux, l'état de +guerre... + +11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou +plusieurs occasions de se prévaloir d'une ou plusieurs dispositions du +Contrat, ne pourra en aucun cas impliquer renonciation par la Partie +intéressée à s'en prévaloir ultérieurement. + +11.3 Le Contrat annule et remplace toute convention antérieure, écrite +ou orale, entre les Parties sur le même objet et constitue l'accord +entier entre les Parties sur cet objet. Aucune addition ou modification +aux termes du Contrat n'aura d'effet à l'égard des Parties à moins +d'être faite par écrit et signée par leurs représentants dûment habilités. + +11.4 Dans l'hypothèse où une ou plusieurs des dispositions du Contrat +s'avèrerait contraire à une loi ou à un texte applicable, existants ou +futurs, cette loi ou ce texte prévaudrait, et les Parties feraient les +amendements nécessaires pour se conformer à cette loi ou à ce texte. +Toutes les autres dispositions resteront en vigueur. De même, la +nullité, pour quelque raison que ce soit, d'une des dispositions du +Contrat ne saurait entraîner la nullité de l'ensemble du Contrat. + + + 11.5 LANGUE + +Le Contrat est rédigé en langue française et en langue anglaise, ces +deux versions faisant également foi. + + + Article 12 - NOUVELLES VERSIONS DU CONTRAT + +12.1 Toute personne est autorisée à copier et distribuer des copies de +ce Contrat. + +12.2 Afin d'en préserver la cohérence, le texte du Contrat est protégé +et ne peut être modifié que par les auteurs de la licence, lesquels se +réservent le droit de publier périodiquement des mises à jour ou de +nouvelles versions du Contrat, qui posséderont chacune un numéro +distinct. Ces versions ultérieures seront susceptibles de prendre en +compte de nouvelles problématiques rencontrées par les logiciels libres. + +12.3 Tout Logiciel diffusé sous une version donnée du Contrat ne pourra +faire l'objet d'une diffusion ultérieure que sous la même version du +Contrat ou une version postérieure, sous réserve des dispositions de +l'article 5.3.4 <#compatibilite>. + + + Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE + +13.1 Le Contrat est régi par la loi française. Les Parties conviennent +de tenter de régler à l'amiable les différends ou litiges qui +viendraient à se produire par suite ou à l'occasion du Contrat. + +13.2 A défaut d'accord amiable dans un délai de deux (2) mois à compter +de leur survenance et sauf situation relevant d'une procédure d'urgence, +les différends ou litiges seront portés par la Partie la plus diligente +devant les Tribunaux compétents de Paris. \ No newline at end of file diff --git a/LICENSE_en.md b/LICENSE_en.md new file mode 100644 index 00000000..2abecc53 --- /dev/null +++ b/LICENSE_en.md @@ -0,0 +1,519 @@ +Pour la version française de ce document, voir [LICENSE.md](./LICENSE.md) + + CeCILL FREE SOFTWARE LICENSE AGREEMENT + +Version 2.1 dated 2013-06-21 + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to users, + * secondly, the election of a governing law, French law, with which it + is conformant, both as regards the law of torts and intellectual + property law, and the protection that it offers to both authors and + holders of the economic rights over software. + +The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +license are: + +Commissariat à l'énergie atomique et aux énergies alternatives - CEA, a +public scientific, technical and industrial research establishment, +having its principal place of business at 25 rue Leblanc, immeuble Le +Ponant D, 75015 Paris, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange, 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +Inria, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +The purpose of this Free Software license agreement is to grant users +the right to modify and redistribute the software governed by this +license within the framework of an open source distribution model. + +The exercising of this right is conditional upon certain obligations for +users so as to preserve this status for all subsequent redistributions. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +suitability of the software as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + +Frequently asked questions can be found on the official website of the +CeCILL licenses family (http://www.cecill.info/index.en.html) for any +necessary clarification. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Contribution: means any or all modifications, corrections, translations, +adaptations and/or new functions integrated into the Software by any or +all Contributors, as well as any or all Internal Modules. + +Module: means a set of sources files including their documentation that +enables supplementary functions or services in addition to those offered +by the Software. + +External Module: means any or all Modules, not derived from the +Software, so that this Module and the Software run in separate address +spaces, with one calling the other when they are run. + +Internal Module: means any or all Module, connected to the Software so +that they both execute in the same address space. + +GNU GPL: means the GNU General Public License version 2 or any +subsequent version, as published by the Free Software Foundation Inc. + +GNU Affero GPL: means the GNU Affero General Public License version 3 or +any subsequent version, as published by the Free Software Foundation Inc. + +EUPL: means the European Union Public License version 1.1 or any +subsequent version, as published by the European Commission. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 <#scope> hereinafter for the whole +term of the protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical medium; + * (ii) the first time the Licensee exercises any of the rights granted + hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +<#accepting> hereinabove, and the Licensee hereby acknowledges that it +has read and understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1 <#accepting>. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or all + medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission or + storage operation as regards the Software, that it is entitled to + carry out hereunder. + + + 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS + +The right to make Contributions includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting software. + +The Licensee is authorized to make any or all Contributions to the +Software provided that it includes an explicit notice that it is the +author of said Contribution and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's warranty + and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows effective access to the full Source +Code of the Software for a period of at least three years from the +distribution of the Software, it being understood that the additional +acquisition cost of the Source Code shall not exceed the cost of the +data transfer. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +When the Licensee makes a Contribution to the Software, the terms and +conditions for the distribution of the resulting Modified Software +become subject to all the provisions of this Agreement. + +The Licensee is authorized to distribute the Modified Software, in +source code or object code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's warranty + and liability as set forth in Articles 8 and 9, + +and, in the event that only the object code of the Modified Software is +redistributed, + + 3. a note stating the conditions of effective access to the full source + code of the Modified Software for a period of at least three years + from the distribution of the Modified Software, it being understood + that the additional acquisition cost of the source code shall not + exceed the cost of the data transfer. + + + 5.3.3 DISTRIBUTION OF EXTERNAL MODULES + +When the Licensee has developed an External Module, the terms and +conditions of this Agreement do not apply to said External Module, that +may be distributed under a separate license agreement. + + + 5.3.4 COMPATIBILITY WITH OTHER LICENSES + +The Licensee can include a code that is subject to the provisions of one +of the versions of the GNU GPL, GNU Affero GPL and/or EUPL in the +Modified or unmodified Software, and distribute that entire code under +the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. + +The Licensee can include the Modified or unmodified Software in a code +that is subject to the provisions of one of the versions of the GNU GPL, +GNU Affero GPL and/or EUPL and distribute that entire code under the +terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by this Agreement, for the duration set forth in Article 4.2 <#term>. + + + 6.2 OVER THE CONTRIBUTIONS + +The Licensee who develops a Contribution is the owner of the +intellectual property rights over this Contribution as defined by +applicable law. + + + 6.3 OVER THE EXTERNAL MODULES + +The Licensee who develops an External Module is the owner of the +intellectual property rights over this External Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution. + + + 6.4 JOINT PROVISIONS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies of + the Software modified or not. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights on the Software of the Holder and/or +Contributors, and to take, where applicable, vis-à-vis its staff, any +and all measures required to ensure respect of said intellectual +property rights of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the suitability of the product for its requirements, its good working +order, and for ensuring that it shall not cause damage to either persons +or properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5 <#scope>). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 <#good-faith> and, in particular, +without any warranty as to its commercial value, its secured, safe, +innovative or relevant nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +expertise for its defense. Such technical and legal expertise shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version, subject to the provisions of Article 5.3.4 +<#compatibility>. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. \ No newline at end of file diff --git a/README.md b/README.md index 4df1395d..08bc04ed 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,23 @@ -# Gipsy + -Gipsy is a multifunction bot managed by the [Gunivers](https://gunivers.net) community. +
-Please use at least **Python 3.9** to run this project. +[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/Gunivers/Gipsy?color=orange&label=average%20contributions&style=for-the-badge)](#) [![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/Gunivers/Gipsy?color=orange&style=for-the-badge)](#) [![GitHub Repo stars](https://img.shields.io/github/stars/Gunivers/Gipsy?color=orange&style=for-the-badge)](#) +[![Discord](https://img.shields.io/discord/125723125685026816?color=blue&label=Discord&style=for-the-badge&logo=Discord)](https://discord.gg/E8qq6tN) -Use `pip install -r requirements.txt` in the directory to install dependencies. +# 👻 Gipsy -## **Description** +Gipsy is modular, free and open-source Discord bot whose focus on accessibility and customization. It maintained with ❤️ by volunteers inside the [Gunivers](https://gunivers.net/) community. It is written in Python and uses the Discord.py library and the project is open to experimented people as well as beginners! -Gipsy is a Discord bot whose first objective is to meet the expectations of the Gunivers community. However, if we want to create new features, we might as well let those who might be interested in them enjoy them ! -You can invite the bot, learn what it can do and follow its evolution. + +Meet Gipsy + -## **Invite** +*This button redirect you to a more complete presentation with invitation links, list of features, self-hosting instructions and more.* -You can invite the bot by [![link](uploads/32dc3a164398f67799a6cfe7206c12ca/link.png) clicking here.](http://utip.io/s/1yhs7W) - -You can also invite the bot in beta version to enjoy the latest features added. Be careful though: the bot in beta version may contain security holes and many bugs. It may also stop working suddenly and for long periods. If you want to invite it though, [click here](https://discordapp.com/oauth2/authorize?client_id=813836349147840513&scope=bot&permissions=8) - -## **Features** - - -## **Add a Gunibot service on linux** - -You can create a service for your gunibot instance, which will allow you to start and stop the bot using commands like `systemctl start gunibot`, or `service gunibot stop`. The bot will also reboot automatically after a crash. - -For this method, you need to have screen installed, which allows you to create detached shell: - -`sudo apt install screen` (debian) - -First, create a file in /etc/systemd/system, where `gunibot` is the name of your service: - -/etc/systemd/system/gunibot.service -```ini -[Unit] -Description=Gunibot -After=network.target - -[Service] -WorkingDirectory=[/path/to/your/gunibot/folder] - -User=[the user which owns the gunibot folder] -Group=[the user group which owns the gunibot folder] - -Restart=always - -ExecStart=/usr/bin/screen -dmS gunibot python3.9 start.py --beta - -ExecStop=/usr/bin/screen -p 0 -S gunibot -X eval 'stuff "^C"' - -[Install] -WantedBy=multi-user.target -``` - -Make sure to replace `WorkingDirectory`, `User` and `Group` with the correct value. You can also set the description as you want. - -In the `ExecStart` command, we create a detached screen with -dmS parameters: - -`-dmS name Start as daemon: Screen session in detached mode.` (from screen help) - -In the `ExecStop` command, we write the input "^C" in the screen session, to stop the bot. - -You can replace gunibot in the `ExecStart` and `ExecStop` command with any value, this is going to be the name of the screen. - -To access the bot command line, you can simply use `screen -r gunibot` where gunibot is the name of the screen. - -You can use these commands to start and stop the bot : - -* start the bot: `sudo systemctl start gunibot` or `sudo service gunibot start` where gunibot is the name of the .service file -* stop the bot: `sudo systemctl stop gunibot` or `sudo service gunibot stop` -* reload the bot: `sudo systemctl restart gunibot` or `sudo service gunibot restart` +
\ No newline at end of file diff --git a/bot/TO_REMOVE.md b/bot/TO_REMOVE.md new file mode 100644 index 00000000..a420af7b --- /dev/null +++ b/bot/TO_REMOVE.md @@ -0,0 +1 @@ +# This folder is deprecated and have to be removed in future versions. Most of files here will be moved (and adapted) in `core/` folder. \ No newline at end of file diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 00000000..778e3fa6 --- /dev/null +++ b/bot/__init__.py @@ -0,0 +1,6 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" \ No newline at end of file diff --git a/bot/args.py b/bot/args.py index 5790ba5f..0d44099d 100644 --- a/bot/args.py +++ b/bot/args.py @@ -1,3 +1,10 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + import discord import re from discord.ext import commands @@ -8,26 +15,32 @@ class tempdelta(commands.Converter): async def convert(self, ctx: MyContext, argument: str) -> int: d = 0 found = False - for x in [('y', 86400*365), ('w', 604800), ('d', 86400), ('h', 3600), ('m', 60), ('min', 60)]: - r = re.search(r'^(\d+)'+x[0]+'$', argument) + for x in [ + ("y", 86400 * 365), + ("w", 604800), + ("d", 86400), + ("h", 3600), + ("m", 60), + ("min", 60), + ]: + r = re.search(r"^(\d+)" + x[0] + "$", argument) if r is not None: - d += int(r.group(1))*x[1] + d += int(r.group(1)) * x[1] found = True - r = re.search(r'^(\d+)h(\d+)m?$', argument) + r = re.search(r"^(\d+)h(\d+)m?$", argument) if r is not None: - d += int(r.group(1))*3600 + int(r.group(2))*60 + d += int(r.group(1)) * 3600 + int(r.group(2)) * 60 found = True if not found: - raise commands.errors.BadArgument('Invalid duration: '+argument) + raise commands.errors.BadArgument("Invalid duration: " + argument) return d class moderatorFlag(commands.Converter): async def convert(self, ctx: MyContext, argument: str) -> str: - LogsFlags = ctx.bot.get_cog('ConfigCog').LogsFlags.FLAGS + LogsFlags = ctx.bot.get_cog("ConfigCog").LogsFlags.FLAGS if argument not in LogsFlags.values(): - raise commands.errors.BadArgument( - 'Invalid moderation flag: '+argument) + raise commands.errors.BadArgument("Invalid moderation flag: " + argument) return argument @@ -37,12 +50,14 @@ class Constant(commands.Converter): async def convert(self, ctx: MyContext, arg: str): if arg != self.w: - raise commands.errors.BadArgument('Unknown argument') + raise commands.errors.BadArgument("Unknown argument") + return Constant + class arguments(commands.Converter): async def convert(self, ctx: MyContext, argument: str) -> dict: answer = dict() - for result in re.finditer(r'(\w+) ?= ?\"((?:[^\"\\]|\\\"|\\)+)\"', argument): + for result in re.finditer(r"(\w+) ?= ?\"((?:[^\"\\]|\\\"|\\)+)\"", argument): answer[result.group(1)] = result.group(2).replace('\\"', '"') - return answer \ No newline at end of file + return answer diff --git a/bot/checks.py b/bot/checks.py index 22c8f533..b1db9606 100644 --- a/bot/checks.py +++ b/bot/checks.py @@ -1,28 +1,52 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + import discord from utils import MyContext, CheckException +from core import config def is_bot_admin(ctx: MyContext): - return ctx.author.id in ctx.bot.config['bot_admins'] + return ctx.author.id in config.get("bot.admins") + async def is_admin(ctx: MyContext): - admin = ctx.guild is None or ctx.author.guild_permissions.administrator or is_bot_admin(ctx) + admin = ( + ctx.guild is None + or ctx.author.guild_permissions.administrator + or is_bot_admin(ctx) + ) if not admin: - raise CheckException('is_admin') + raise CheckException("is_admin") return True + async def is_server_manager(ctx: MyContext): - g_manager = ctx.guild is None or ctx.author.guild_permissions.manage_guild or is_bot_admin(ctx) + g_manager = ( + ctx.guild is None + or ctx.author.guild_permissions.manage_guild + or is_bot_admin(ctx) + ) if not g_manager: - raise CheckException('is_server_manager') + raise CheckException("is_server_manager") return True + async def is_roles_manager(ctx: MyContext): - r_manager = ctx.guild is None or ctx.author.guild_permissions.manage_roles or is_bot_admin(ctx) + r_manager = ( + ctx.guild is None + or ctx.author.guild_permissions.manage_roles + or is_bot_admin(ctx) + ) if not r_manager: - raise CheckException('is_roles_manager') + raise CheckException("is_roles_manager") return True + async def can_group(ctx: MyContext): config = ctx.bot.server_configs[ctx.guild.id] if config["group_allowed_role"] is None: diff --git a/bot/config.py b/bot/config.py deleted file mode 100644 index 3d2af9f1..00000000 --- a/bot/config.py +++ /dev/null @@ -1,16 +0,0 @@ -import json, os -from shutil import copyfile - -def get_config(path: str, isBotConfig: bool): - if not os.path.isfile(path + ".json"): - copyfile(path + '-example.json', path + '.json') - if isBotConfig: - print("TOKEN MISSING: Please, enter your bot token in the config/config.json and restart the bot.") - return None - with open(path + ".json") as f: - conf = json.load(f) - if isBotConfig: - if conf["token"] == "Discord token for main bot": - print("TOKEN MISSING: Please, enter your bot token in the config/config.json and restart the bot.") - return None - return conf \ No newline at end of file diff --git a/bot/docs.py b/bot/docs.py deleted file mode 100644 index 3132f2a6..00000000 --- a/bot/docs.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -from shutil import copyfile - -def generate_docs(): - docs = open("docs/summary.rst","w+") - docs.write(""" -.. toctree:: - :maxdepth: 3 - :caption: Info - - contributing.md - faq.md - -.. toctree:: - :maxdepth: 2 - :caption: Installed plugins - -""") - - if not os.path.isdir("./docs/plugins"): - os.makedirs("./docs/plugins") - for file in os.listdir("./docs/plugins"): - os.remove("./docs/plugins/" + file) - for plugin in sorted(os.listdir('./plugins/')): - if not plugin.startswith('_'): - if os.path.isfile('./plugins/' + plugin + "/docs/user_documentation.rst"): - copyfile('./plugins/' + plugin + "/docs/user_documentation.rst", './docs/plugins/' + plugin + ".rst") - docs.write(" plugins/" + plugin + ".rst\n") - else: - if os.path.isfile('./plugins/' + plugin + "/docs/user_documentation.md"): - copyfile('./plugins/' + plugin + "/docs/user_documentation.md", './docs/plugins/' + plugin + ".md") - docs.write(" plugins/" + plugin + ".md\n") - - - if os.listdir('./docs/create_plugin') != []: - docs.write(""" -.. toctree:: - :maxdepth: 2 - :caption: For developers - -""") - - for file in os.listdir('./docs/create_plugin'): - if file[-3:] == ".md" or file[-4:] == ".rst": - docs.write(" create_plugin/" + file + "\n") - - - docs.close() \ No newline at end of file diff --git a/bot/utils/configManager.py b/bot/utils/configManager.py index 4ca16e27..962738ac 100644 --- a/bot/utils/configManager.py +++ b/bot/utils/configManager.py @@ -1,12 +1,20 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + import os from json import dump, load +from utils import Gunibot from discord.ext import commands from utils import CONFIG_OPTIONS CONFIG_FOLDER = "configs" -CONFIG_TEMPLATE = {k: v['default'] for k, v in CONFIG_OPTIONS.items() if 'default' in v} +CONFIG_TEMPLATE = {k: v["default"] for k, v in CONFIG_OPTIONS.items() if "default" in v} class serverConfig(dict): @@ -36,14 +44,12 @@ def __delitem__(self, key): class ConfigCog(commands.Cog): - def __init__(self, bot): self.bot = bot self.file = "configManager" self.confManager = self.configManager() class configManager(dict): - def __init__(self): super().__init__() self.cache = dict() @@ -76,7 +82,9 @@ def __repr__(self): return "" def __len__(self): - return len([name for name in os.listdir(CONFIG_FOLDER) if os.path.isfile(name)]) + return len( + [name for name in os.listdir(CONFIG_FOLDER) if os.path.isfile(name)] + ) def __delitem__(self, key): pass @@ -106,7 +114,7 @@ def keys(self): # return self.__dict__.pop(*args) def __contains__(self, item): - return self.has_key(item) + return item in self class LogsFlags: FLAGS = { @@ -118,7 +126,7 @@ class LogsFlags: 1 << 5: "boosts", 1 << 6: "roles", 1 << 7: "members", - 1 << 8: "emojis" + 1 << 8: "emojis", } def flagsToInt(self, flags: list) -> int: @@ -132,5 +140,5 @@ def intToFlags(self, i: int) -> list: return [v for k, v in self.FLAGS.items() if i & k == k] -async def setup(bot): +async def setup(bot: Gunibot = None, plugin_config: dict = None): await bot.add_cog(ConfigCog(bot)) diff --git a/bot/utils/errors.py b/bot/utils/errors.py index f18f78a4..ffcdb834 100644 --- a/bot/utils/errors.py +++ b/bot/utils/errors.py @@ -1,12 +1,21 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + +from utils import CheckException, Gunibot, MyContext +from discord.ext import commands +import discord +from bot import checks import re import traceback +from core import config import sys + sys.path.append("./bot") -from bot import checks -import discord -from discord.ext import commands -from utils import CheckException, Gunibot, MyContext class Errors(commands.Cog): @@ -19,17 +28,23 @@ def __init__(self, bot: Gunibot): @commands.Cog.listener() async def on_command_error(self, ctx: MyContext, error: Exception): """The event triggered when an error is raised while invoking a command.""" - # This prevents any commands with local handlers being handled here in on_command_error. - if hasattr(ctx.command, 'on_error'): + # This prevents any commands with local handlers being handled here in + # on_command_error. + if hasattr(ctx.command, "on_error"): return - ignored = (commands.errors.CommandNotFound, commands.errors.CheckFailure, - commands.errors.ConversionError, discord.errors.Forbidden) - actually_not_ignored = (commands.errors.NoPrivateMessage) + ignored = ( + commands.errors.CommandNotFound, + commands.errors.CheckFailure, + commands.errors.ConversionError, + discord.errors.Forbidden, + ) + actually_not_ignored = commands.errors.NoPrivateMessage # Allows us to check for original exceptions raised and sent to CommandInvokeError. - # If nothing is found. We keep the exception passed to on_command_error. - error = getattr(error, 'original', error) + # If nothing is found. We keep the exception passed to + # on_command_error. + error = getattr(error, "original", error) # Anything in ignored will return and prevent anything happening. if isinstance(error, ignored) and not isinstance(error, actually_not_ignored): @@ -38,103 +53,179 @@ async def on_command_error(self, ctx: MyContext, error: Exception): if checks.is_bot_admin(ctx): await ctx.reinvoke() return - await ctx.send(await self.bot._(ctx.channel, "errors.cooldown", c=round(error.retry_after, 2))) + await ctx.send( + await self.bot._( + ctx.channel, "errors.cooldown", c=round(error.retry_after, 2) + ) + ) return elif isinstance(error, CheckException): - return await ctx.send(await self.bot._(ctx.channel, "errors.custom_checks."+error.id)) + return await ctx.send( + await self.bot._(ctx.channel, "errors.custom_checks." + error.id) + ) elif isinstance(error, (commands.BadArgument, commands.BadUnionArgument)): raw_error = str(error) if raw_error == "Unknown argument": - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-arg")) + return await ctx.send( + await self.bot._(ctx.channel, "errors.unknown-arg") + ) elif raw_error == "Unknown dependency action type": - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-dependency")) + return await ctx.send( + await self.bot._(ctx.channel, "errors.invalid-dependency") + ) elif raw_error == "Unknown dependency trigger type": - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-trigger")) + return await ctx.send( + await self.bot._(ctx.channel, "errors.invalid-trigger") + ) elif raw_error == "Unknown permission type": - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-permission")) - # Could not convert "limit" into int. OR Converting to "int" failed for parameter "number". + return await ctx.send( + await self.bot._(ctx.channel, "errors.invalid-permission") + ) + # Could not convert "limit" into int. OR Converting to "int" failed + # for parameter "number". r = re.search( - r'Could not convert \"(?P[^\"]+)\" into (?P[^.\n]+)', raw_error) + r"Could not convert \"(?P[^\"]+)\" into (?P[^.\n]+)", + raw_error, + ) if r is None: r = re.search( - r'Converting to \"(?P[^\"]+)\" failed for parameter \"(?P[^.\n]+)\"', raw_error) + r"Converting to \"(?P[^\"]+)\" failed for parameter \"(?P[^.\n]+)\"", + raw_error, + ) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-arg", p=r.group('arg'), t=r.group('type'))) + return await ctx.send( + await self.bot._( + ctx.channel, + "errors.unknown-arg", + p=r.group("arg"), + t=r.group("type"), + ) + ) # zzz is not a recognised boolean option r = re.search( - r'(?P[^\"]+) is not a recognised (?P[^.\n]+) option', raw_error) + r"(?P[^\"]+) is not a recognised (?P[^.\n]+) option", + raw_error, + ) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-type", p=r.group('arg'), t=r.group('type'))) + return await ctx.send( + await self.bot._( + ctx.channel, + "errors.invalid-type", + p=r.group("arg"), + t=r.group("type"), + ) + ) # Member "Z_runner" not found - r = re.search(r'Member \"([^\"]+)\" not found', raw_error) + r = re.search(r"Member \"([^\"]+)\" not found", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-member", m=r.group(1))) + return await ctx.send( + await self.bot._(ctx.channel, "errors.unknown-member", m=r.group(1)) + ) # User "Z_runner" not found - r = re.search(r'User \"([^\"]+)\" not found', raw_error) + r = re.search(r"User \"([^\"]+)\" not found", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-user", u=r.group(1))) + return await ctx.send( + await self.bot._(ctx.channel, "errors.unknown-user", u=r.group(1)) + ) # Role "Admin" not found - r = re.search(r'Role \"([^\"]+)\" not found', raw_error) + r = re.search(r"Role \"([^\"]+)\" not found", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-role", r=r.group(1))) + return await ctx.send( + await self.bot._(ctx.channel, "errors.unknown-role", r=r.group(1)) + ) # Emoji ":shock:" not found - r = re.search(r'Emoji \"([^\"]+)\" not found', raw_error) + r = re.search(r"Emoji \"([^\"]+)\" not found", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-emoji", e=r.group(1))) - # Colour "blue" is invalid - r = re.search(r'Colour \"([^\"]+)\" is invalid', raw_error) + return await ctx.send( + await self.bot._(ctx.channel, "errors.unknown-emoji", e=r.group(1)) + ) + # Colour "blue" is invalid + r = re.search(r"Colour \"([^\"]+)\" is invalid", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-color", c=r.group(1))) + return await ctx.send( + await self.bot._(ctx.channel, "errors.invalid-color", c=r.group(1)) + ) # Channel "twitter" not found. - r = re.search(r'Channel \"([^\"]+)\" not found', raw_error) + r = re.search(r"Channel \"([^\"]+)\" not found", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-channel", c=r.group(1))) + return await ctx.send( + await self.bot._( + ctx.channel, "errors.unknown-channel", c=r.group(1) + ) + ) # Message "1243" not found. - r = re.search(r'Message \"([^\"]+)\" not found', raw_error) + r = re.search(r"Message \"([^\"]+)\" not found", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-message", m=r.group(1))) + return await ctx.send( + await self.bot._( + ctx.channel, "errors.unknown-message", m=r.group(1) + ) + ) # Group "twitter" not found. - r = re.search(r'Group \"([^\"]+)\" not found', raw_error) + r = re.search(r"Group \"([^\"]+)\" not found", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-group", g=r.group(1))) + return await ctx.send( + await self.bot._(ctx.channel, "errors.unknown-group", g=r.group(1)) + ) # Too many text channels - if raw_error == 'Too many text channels': - return await ctx.send(await self.bot._(ctx.channel, "errors.too-many-text-channels")) + if raw_error == "Too many text channels": + return await ctx.send( + await self.bot._(ctx.channel, "errors.too-many-text-channels") + ) # Invalid duration: 2d - r = re.search(r'Invalid duration: ([^\" ]+)', raw_error) + r = re.search(r"Invalid duration: ([^\" ]+)", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-duration", d=r.group(1))) + return await ctx.send( + await self.bot._( + ctx.channel, "errors.invalid-duration", d=r.group(1) + ) + ) # Invalid invite: nope - r = re.search(r'Invalid invite: (\S+)', raw_error) + r = re.search(r"Invalid invite: (\S+)", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-invite")) + return await ctx.send( + await self.bot._(ctx.channel, "errors.invalid-invite") + ) # Invalid guild: test - r = re.search(r'Invalid guild: (\S+)', raw_error) + r = re.search(r"Invalid guild: (\S+)", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.unknown-server")) + return await ctx.send( + await self.bot._(ctx.channel, "errors.unknown-server") + ) # Invalid url: nou - r = re.search(r'Invalid url: (\S+)', raw_error) + r = re.search(r"Invalid url: (\S+)", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-url")) + return await ctx.send( + await self.bot._(ctx.channel, "errors.invalid-url") + ) # Invalid emoji: lmao - r = re.search(r'Invalid emoji: (\S+)', raw_error) + r = re.search(r"Invalid emoji: (\S+)", raw_error) if r is not None: - return await ctx.send(await self.bot._(ctx.channel, "errors.invalid-emoji")) - print('errors -', error) + return await ctx.send( + await self.bot._(ctx.channel, "errors.invalid-emoji") + ) + print("errors -", error) elif isinstance(error, commands.MissingRequiredArgument): - await ctx.send(await self.bot._(ctx.channel, "errors.missing-arg", a=error.param.name)) + await ctx.send( + await self.bot._(ctx.channel, "errors.missing-arg", a=error.param.name) + ) return elif isinstance(error, commands.DisabledCommand): - await ctx.send(await self.bot._(ctx.channel, "errors.disabled-cmd", c=ctx.invoked_with)) + await ctx.send( + await self.bot._(ctx.channel, "errors.disabled-cmd", c=ctx.invoked_with) + ) return elif isinstance(error, commands.errors.NoPrivateMessage): await ctx.send(await self.bot._(ctx.channel, "errors.disabled-dm")) return else: await ctx.send(await self.bot._(ctx.channel, "errors.error-unknown")) - # All other Errors not returned come here... And we can just print the default TraceBack. - self.bot.log.warning('Ignoring exception in command {}:'.format( - ctx.message.content), exc_info=(type(error), error, error.__traceback__)) + # All other Errors not returned come here... And we can just print the + # default TraceBack. + self.bot.log.warning( + "Ignoring exception in command {}:".format(ctx.message.content), + exc_info=(type(error), error, error.__traceback__), + ) await self.on_error(error, ctx) @commands.Cog.listener() @@ -142,8 +233,7 @@ async def on_error(self, error, ctx=None): try: if isinstance(ctx, discord.Message): ctx = await self.bot.get_context(ctx) - tr = traceback.format_exception( - type(error), error, error.__traceback__) + tr = traceback.format_exception(type(error), error, error.__traceback__) msg = "```python\n{}\n```".format(" ".join(tr)[:1900]) if ctx is None: await self.senf_err_msg(f"Internal Error\n{msg}") @@ -152,18 +242,20 @@ async def on_error(self, error, ctx=None): elif ctx.channel.id == 698547216155017236: return await ctx.send(msg) else: - await self.senf_err_msg(ctx.guild.name+" | "+ctx.channel.name+"\n"+msg) + await self.senf_err_msg( + ctx.guild.name + " | " + ctx.channel.name + "\n" + msg + ) except Exception as e: self.bot.log.warn(f"[on_error] {e}", exc_info=True) async def senf_err_msg(self, msg): """Sends a message to the error channel""" - salon = self.bot.get_channel(self.bot.config["errors_channel"]) - if salon is None: + channel = self.bot.get_channel(config.get("bot.error_channels")) + if channel is None: return False - await salon.send(msg) + await channel.send(msg) return True -async def setup(bot): +async def setup(bot: Gunibot = None, plugin_config: dict = None): await bot.add_cog(Errors(bot)) diff --git a/bot/utils/gunivers.py b/bot/utils/gunivers.py index 76540afb..f22378ef 100644 --- a/bot/utils/gunivers.py +++ b/bot/utils/gunivers.py @@ -1,10 +1,16 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + import discord from discord.ext import tasks, commands from utils import Gunibot, MyContext class Gunivers(commands.Cog): - def __init__(self, bot: Gunibot): self.bot = bot self.file = "gunivers" @@ -15,9 +21,10 @@ def cog_unload(self): @tasks.loop(minutes=60.0 * 24.0) async def update_loop(self): - channel = self.bot.get_channel(757879277776535664) # Round Table + channel = self.bot.get_channel(757879277776535664) # Round Table if channel is not None: await channel.send("Bon, qu'est-ce qu'on peut poster aujourd'hui ?") -async def setup(bot): + +async def setup(bot: Gunibot = None, plugin_config: dict = None): await bot.add_cog(Gunivers(bot)) diff --git a/bot/utils/languages.py b/bot/utils/languages.py index 45c84a64..e74ff258 100644 --- a/bot/utils/languages.py +++ b/bot/utils/languages.py @@ -1,3 +1,10 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + import discord import i18n from discord.ext import commands @@ -5,24 +12,22 @@ import os i18n.translations.container.clear() # invalidate old cache -i18n.set('filename_format', '{locale}.{format}') -i18n.set('fallback', 'fr') -i18n.load_path.append('./langs') +i18n.set("filename_format", "{locale}.{format}") +i18n.set("fallback", "fr") +i18n.load_path.append("./langs") # Check all plugin lang directory -for plugin in os.listdir('./plugins/'): - if os.path.isdir('./plugins/' + plugin + '/langs/') and plugin[0] != '_': - i18n.load_path.append('./plugins/' + plugin + '/langs/') - +for plugin in os.listdir("./plugins/"): + if os.path.isdir("./plugins/" + plugin + "/langs/") and plugin[0] != "_": + i18n.load_path.append("./plugins/" + plugin + "/langs/") class Languages(commands.Cog): - def __init__(self, bot: Gunibot): self.bot = bot self.file = "languages" - self.languages = ['fr', 'en'] - self.config_options = ['language'] + self.languages = ["fr", "en"] + self.config_options = ["language"] async def tr(self, ctx, key: str, **kwargs): """Translate something @@ -30,29 +35,35 @@ async def tr(self, ctx, key: str, **kwargs): lang = self.languages[0] if isinstance(ctx, commands.Context): if ctx.guild: - lang = self.languages[await self.get_lang(ctx.guild.id)] + lang = await self.get_lang(ctx.guild.id, use_str=True) elif isinstance(ctx, discord.Guild): - lang = self.languages[await self.get_lang(ctx.id)] + lang = await self.get_lang(ctx.id, use_str=True) elif isinstance(ctx, discord.abc.GuildChannel): - lang = self.languages[await self.get_lang(ctx.guild.id)] + lang = await self.get_lang(ctx.guild.id, use_str=True) elif isinstance(ctx, str) and ctx in self.languages: lang = ctx elif isinstance(ctx, int): # guild ID if self.bot.get_guild(ctx): # if valid guild - lang = self.languages[await self.get_lang(ctx)] + lang = await self.get_lang(ctx, use_str=True) else: lang = self.languages[0] return i18n.t(key, locale=lang, **kwargs) - async def get_lang(self, guildID: int, use_str: bool=False) -> int: + async def get_lang(self, guildID: int, use_str: bool = False) -> int: if guildID is None: as_int = 0 else: - as_int = self.bot.server_configs[guildID]['language'] + # migration for old format + if isinstance(self.bot.server_configs[guildID]["language"], int): + as_int = self.bot.server_configs[guildID]["language"] + else: + as_int = self.languages.index( + self.bot.server_configs[guildID]["language"] + ) if use_str: return self.languages[as_int] return as_int -async def setup(bot): +async def setup(bot: Gunibot = None, plugin_config: dict = None): await bot.add_cog(Languages(bot)) diff --git a/bot/utils/perms.py b/bot/utils/perms.py deleted file mode 100644 index 26835a50..00000000 --- a/bot/utils/perms.py +++ /dev/null @@ -1,97 +0,0 @@ -import typing - -import discord -from discord.ext import commands -from utils import Gunibot, MyContext - - -class Perms(commands.Cog): - """Cog with a single command, allowing you to see the permissions of a member or a role in a channel.""" - - def __init__(self, bot: Gunibot): - self.bot = bot - self.file = "perms" - chan_perms = [key for key, value in discord.Permissions().all_channel() if value] - self.perms_name = {'general': [key for key, value in discord.Permissions().general() if value], - 'text': [key for key, value in discord.Permissions().text() if value], - 'voice': [key for key, value in discord.Permissions().voice() if value]} - self.perms_name['common_channel'] = [ - x for x in chan_perms if x in self.perms_name['general']] - - @commands.command(name='perms', aliases=['permissions']) - @commands.guild_only() - async def check_permissions(self, ctx: MyContext, channel: typing.Optional[typing.Union[discord.TextChannel, discord.VoiceChannel, discord.CategoryChannel]] = None, *, target: typing.Union[discord.Member, discord.Role] = None): - """Permissions assigned to a member/role (the user by default) - The channel used to view permissions is the channel in which the command is entered.""" - if target == None: - target = ctx.author - perms = None - if isinstance(target, discord.Member): - if channel == None: - perms = target.guild_permissions - else: - perms = channel.permissions_for(target) - col = target.color - avatar = await self.bot.user_avatar_as(target, size=256) - name = str(target) - elif isinstance(target, discord.Role): - perms = target.permissions - if channel != None: - perms.update( - **{x[0]: x[1] for x in channel.overwrites_for(ctx.guild.default_role) if x[1] != None}) - perms.update(**{x[0]: x[1] for x in channel.overwrites_for(target) if x[1] != None}) - col = target.color - avatar = ctx.guild.icon_url_as(format='png', size=256) - name = str(target) - permsl = list() - - if perms is None: - return - - async def perms_tr(x) -> str: - """Get the translation of a permission""" - return await self.bot._(ctx.guild.id, "perms.list."+x) - - # Get the perms translations - if perms.administrator: - # If the user is admin, we just say it - permsl.append(":white_check_mark: " + await perms_tr('administrator')) - else: - # Here we check if the value of each permission is True. - for perm, value in perms: - if (perm not in self.perms_name['text']+self.perms_name['common_channel'] and isinstance(channel, discord.TextChannel)) or (perm not in self.perms_name['voice']+self.perms_name['common_channel'] and isinstance(channel, discord.VoiceChannel)): - continue - perm = await perms_tr(perm) - if 'perms.list.' in perm: - # missing translation - perm = perm.replace('_', ' ').title() - self.bot.log.warn(f"[perms] missing permission translation: {perm}") - if value: - permsl.append(":white_check_mark: " + perm) - else: - permsl.append(":x: " + perm) - if ctx.can_send_embed: - # \uFEFF is a Zero-Width Space, which basically allows us to have an empty field name. - # And to make it look nice, we wrap it in an Embed. - desc = "Permissions générales" if channel is None else channel.mention - embed = discord.Embed(color=col, description=desc) - embed.set_author(name=name, icon_url=avatar) - if len(permsl) > 10: - sep = int(len(permsl)/2) - if len(permsl) % 2 == 1: - sep += 1 - embed.add_field(name='\uFEFF', value="\n".join(permsl[:sep])) - embed.add_field(name='\uFEFF', value="\n".join(permsl[sep:])) - else: - embed.add_field(name='\uFEFF', value="\n".join(permsl)) - await ctx.send(embed=embed) - # Thanks to Gio for the Command. - else: - try: - await ctx.send("**Permission de '{}' :**\n\n".format(name.replace('@', '')) + "\n".join(permsl)) - except: - pass - - -async def setup(bot): - await bot.add_cog(Perms(bot)) diff --git a/bot/utils/sconfig.py b/bot/utils/sconfig.py index b5986748..41d2e084 100644 --- a/bot/utils/sconfig.py +++ b/bot/utils/sconfig.py @@ -1,59 +1,77 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + +from utils import CONFIG_OPTIONS, Gunibot, MyContext +from discord.ext import commands +import emoji +import discord +from bot import checks +from bot import args import asyncio import io import json import re from typing import Any, List, Union +import numpy as np import sys + sys.path.append("./bot") -import args -import sys sys.path.append("./bot") -from bot import checks -import discord -import emoji -from discord.ext import commands -from utils import CONFIG_OPTIONS, Gunibot, MyContext +SERVER_CONFIG = None -class Sconfig(commands.Cog): +class Sconfig(commands.Cog): def __init__(self, bot: Gunibot): + global SERVER_CONFIG + SERVER_CONFIG = self self.bot = bot self.file = "sconfig" - self.sorted_options = dict() # config options sorted by cog - self.config_options = ['prefix'] + self.sorted_options = dict() # config options sorted by cog + self.config_options = ["prefix"] for cog in bot.cogs.values(): - if not hasattr(cog, 'config_options'): - # if the cog doesn't have any specific config + if not hasattr(cog, "config_options"): + # if the cog doesn't have any specific config continue - self.sorted_options[cog.__cog_name__] = {k: v for k, v in CONFIG_OPTIONS.items() if k in cog.config_options} - # for whatever reason, the for loop above doesn't include its own cog, so we just force it - self.sorted_options[self.__cog_name__] = {k: v for k, v in CONFIG_OPTIONS.items() if k in self.config_options} - + self.sorted_options[cog.__cog_name__] = { + k: v for k, v in CONFIG_OPTIONS.items() if k in cog.config_options + } + # for whatever reason, the for loop above doesn't include its own cog, + # so we just force it + self.sorted_options[self.__cog_name__] = { + k: v for k, v in CONFIG_OPTIONS.items() if k in self.config_options + } + def on_anycog_load(self, cog: commands.Cog): """Used to enable config commands when a cog is enabled - + Parameters ----------- cog: :class:`commands.Cog` The cog which got enabled""" - if not hasattr(cog, 'config_options'): + if not hasattr(cog, "config_options"): # if the cog doesn't have any specific config return - self.sorted_options[cog.__cog_name__] = {k: v for k, v in CONFIG_OPTIONS.items() if k in cog.config_options} + self.sorted_options[cog.__cog_name__] = { + k: v for k, v in CONFIG_OPTIONS.items() if k in cog.config_options + } for opt in self.sorted_options[cog.__cog_name__].values(): # we enable the commands if needed - if 'command' in opt: + if "command" in opt: try: - self.bot.get_command("config "+opt['command']).enabled = True + self.bot.get_command("config " + opt["command"]).enabled = True except AttributeError: # if the command doesn't exist pass - + def on_anycog_unload(self, cog: str): """Used to disable config commands when a cog is disabled - + Parameters ----------- cog: :class:`str` @@ -61,17 +79,17 @@ def on_anycog_unload(self, cog: str): if cog in self.sorted_options: for opt in self.sorted_options[cog].values(): # we disable the commands if needed - if 'command' in opt: + if "command" in opt: try: - self.bot.get_command("config "+opt['command']).enabled = False - except AttributeError: + self.bot.get_command("config " + opt["command"]).enabled = False + except (AttributeError, TypeError): # if opt["command"] is None # if the command doesn't exist pass del self.sorted_options[cog] async def edit_config(self, guildID: int, key: str, value: Any): """Edit or reset a config option for a guild - + Parameters ----------- guildID: :class:`int` @@ -79,7 +97,7 @@ async def edit_config(self, guildID: int, key: str, value: Any): key: :class:`str` The name of the option to edit - + value: :class:`Any` The new value of the config, or None to reset""" if value is None: @@ -92,36 +110,44 @@ async def edit_config(self, guildID: int, key: str, value: Any): else: return await self.bot._(guildID, "sconfig.option-edited", opt=key) - async def format_config(self, guild: discord.Guild, key: str, value: str, mention: bool = True) -> str: + async def format_config( + self, guild: discord.Guild, key: str, value: str, mention: bool = True + ) -> str: if value is None: return None config = CONFIG_OPTIONS[key] - def getname(x): return (x.mention if mention else x.name) + def getname(x): + return x.mention if mention else x.name - sep = ' ' if mention else ' | ' + sep = " " if mention else " | " if key == "levelup_channel": - if value in (None, 'none', 'any'): + if value in (None, "none", "any"): return str(value).capitalize() - if config['type'] == 'roles': + if config["type"] == "roles": value = [value] if isinstance(value, int) else value roles = [guild.get_role(x) for x in value] roles = [getname(x) for x in roles if x is not None] return sep.join(roles) - if config['type'] == 'channels': + if config["type"] == "channels": value = [value] if isinstance(value, int) else value channels = [guild.get_channel(x) for x in value] channels = [getname(x) for x in channels if x is not None] return sep.join(channels) - if config['type'] == 'categories': + if config["type"] == "categories": value = [value] if isinstance(value, int) else value categories = [guild.get_channel(x) for x in value] categories = [x.name for x in categories if x is not None] return " | ".join(categories) - if config['type'] == 'duration': - return await self.bot.get_cog("TimeCog").time_delta(value, lang='fr', year=True, precision=0) - if config['type'] == 'emojis': - def emojis_convert(s_emoji:str, bot_emojis:List[discord.Emoji]) -> Union[str, discord.Emoji]: + if config["type"] == "duration": + return await self.bot.get_cog("TimeCog").time_delta( + value, lang="fr", year=True, precision=0 + ) + if config["type"] == "emojis": + + def emojis_convert( + s_emoji: str, bot_emojis: List[discord.Emoji] + ) -> Union[str, discord.Emoji]: if s_emoji.isnumeric(): d_em = discord.utils.get(bot_emojis, id=int(s_emoji)) if d_em is None: @@ -129,17 +155,18 @@ def emojis_convert(s_emoji:str, bot_emojis:List[discord.Emoji]) -> Union[str, di else: return f":{d_em.name}:" return emoji.emojize(s_emoji, language="alias") + value = [value] if isinstance(value, str) else value return " ".join([emojis_convert(x, self.bot.emojis) for x in value]) - if config['type'] == 'modlogsFlags': + if config["type"] == "modlogsFlags": flags = self.bot.get_cog("ConfigCog").LogsFlags().intToFlags(value) return " - ".join(flags) if len(flags) > 0 else None - if config['type'] == 'language': + if config["type"] == "language": cog = self.bot.get_cog("Languages") if cog: return cog.languages[value] return value - if config['type'] == 'int': + if config["type"] == "int": return value return value @@ -149,76 +176,125 @@ def emojis_convert(s_emoji:str, bot_emojis:List[discord.Emoji]) -> Union[str, di async def main_config(self, ctx: MyContext): """Edit your server configuration""" if ctx.subcommand_passed is None: - res = list() # get the server config config = ctx.bot.server_configs[ctx.guild.id] - # get the length of the longest key - max_length = 0 + + # get the length of the longest key to align the values in columns + max_key_length = 0 + max_value_length = 0 for options in self.sorted_options.values(): configs_len = [len(k) for k in config.keys() if k in options] - max_length = max(max_length, *configs_len) if len(configs_len) > 0 else max_length - max_length += 2 - # iterate over groups + max_key_length = ( + max(max_key_length, *configs_len) + if len(configs_len) > 0 + else max_key_length + ) + values_len = [ + len(str(await self.format_config(ctx.guild, k, v, mention=False))) + for k, v in config.items() + if k in options + ] + max_value_length = ( + max(max_value_length, *values_len) + if len(values_len) > 0 + else max_value_length + ) + max_key_length += 3 + max_value_length += 1 + + # iterate over modules + cpt = 0 + embeds = [] for module, options in sorted(self.sorted_options.items()): - subconf = {k:v for k,v in config.items() if k in options} + + subconf = {k: v for k, v in config.items() if k in options} if len(subconf) == 0: continue - temp = " # {}\n".format(await self.bot._(ctx.guild.id, "sconfig.cog-name."+module)) + + module_config = "" + # iterate over configs for that group for k, v in subconf.items(): value = await self.format_config(ctx.guild, k, v, False) - temp += (f"[{k}]").ljust(max_length+1) + f" {value}\n" - if hasattr(self.bot.get_cog(module), '_create_config'): - for extra in await self.bot.get_cog(module)._create_config(ctx): - temp += (f"[{extra[0]}]").ljust(max_length+1) + f" {extra[1]}\n" - res.append(temp) + module_config += ( + (f"{k}:").ljust(max_key_length) + + f" {value}".ljust(max_value_length) + + "\n" + ) - for category in res: - await ctx.send("```ini\n" + "\n" + category + "```") + if hasattr(self.bot.get_cog(module), "_create_config"): + for extra in await self.bot.get_cog(module)._create_config(ctx): + module_config += ( + (f"[{extra[0]}]").ljust(max_key_length) + + f" {extra[1]}".ljust(max_value_length) + + "\n" + ) + + # Put the config in embeds and stack them to be send in group + embeds.append( + discord.Embed( + title=module, + description=f"```yml\n{module_config}```", + colour=0x2F3136, + ) + ) + + cpt += 1 + + # Send the config by group of 10 (limit of embed number per message) + if cpt % 10 == 0: + await ctx.send(embeds=embeds) + embeds = [] + + # Send the remaining embeds + if cpt % 10 != 0: + await ctx.send(embeds=embeds) elif ctx.invoked_subcommand is None: - await ctx.send(await self.bot._(ctx.guild.id, 'sconfig.option-notfound')) + await ctx.send(await self.bot._(ctx.guild.id, "sconfig.option-notfound")) @main_config.command(name="prefix") async def config_prefix(self, ctx: MyContext, new_prefix=None): limit = 7 if new_prefix is not None and len(new_prefix) > limit: - await ctx.send(await self.bot._(ctx.guild.id, "sconfig.prefix-too-long", c=limit)) + await ctx.send( + await self.bot._(ctx.guild.id, "sconfig.prefix-too-long", c=limit) + ) return await ctx.send(await self.edit_config(ctx.guild.id, "prefix", new_prefix)) @main_config.command(name="logs_channel") - async def config_logs_channel(self, ctx: MyContext, *, channel: discord.TextChannel): + async def config_logs_channel( + self, ctx: MyContext, *, channel: discord.TextChannel + ): await ctx.send(await self.edit_config(ctx.guild.id, "logs_channel", channel.id)) if logs_cog := self.bot.get_cog("Logs"): - emb = discord.Embed(title=await self.bot._(ctx.guild, "sconfig.config-enabled"), - description=await self.bot._(ctx.guild, "sconfig.modlogs-channel-enabled"), - color=16098851) + emb = discord.Embed( + title=await self.bot._(ctx.guild, "sconfig.config-enabled"), + description=await self.bot._( + ctx.guild, "sconfig.modlogs-channel-enabled" + ), + color=16098851, + ) await logs_cog.send_embed(ctx.guild, emb) - - - - #-------------------------------------------------- + # -------------------------------------------------- # Voice Channel - #-------------------------------------------------- - - + # -------------------------------------------------- - #-------------------------------------------------- + # -------------------------------------------------- # ModLogs - #-------------------------------------------------- + # -------------------------------------------------- - #-------------------------------------------------- + # -------------------------------------------------- # Thanks - #-------------------------------------------------- + # -------------------------------------------------- - - #-------------------------------------------------- + # -------------------------------------------------- # Language - #-------------------------------------------------- + # -------------------------------------------------- - @main_config.command(name="language", aliases=['lang']) + @main_config.command(name="language", aliases=["lang"]) async def language(self, ctx: MyContext, lang: str): """Change the bot language in your server Use the 'list' option to get the available languages""" @@ -227,40 +303,37 @@ async def language(self, ctx: MyContext, lang: str): await ctx.send("Unable to load languages, please try again later") elif lang == "list": # send a list of available languages availabe = " - ".join(cog.languages) - await ctx.send(await self.bot._(ctx.guild.id, "sconfig.languages-list", list=availabe)) + await ctx.send( + await self.bot._(ctx.guild.id, "sconfig.languages-list", list=availabe) + ) elif lang not in cog.languages: # invalid language - await ctx.send(await self.bot._(ctx.guild.id, 'sconfig.invalid-language', p=ctx.prefix)) + await ctx.send( + await self.bot._(ctx.guild.id, "sconfig.invalid-language", p=ctx.prefix) + ) else: # correct case - selected = cog.languages.index(lang) - await ctx.send(await self.edit_config(ctx.guild.id, "language", selected)) + await ctx.send( + await self.edit_config(ctx.guild.id, "language", lang) + ) # lang should be a string - #-------------------------------------------------- + # -------------------------------------------------- # Hypesquad - #-------------------------------------------------- - + # -------------------------------------------------- - - #-------------------------------------------------- + # -------------------------------------------------- # Giveaway - #-------------------------------------------------- - - + # -------------------------------------------------- - #-------------------------------------------------- + # -------------------------------------------------- # XP - #-------------------------------------------------- + # -------------------------------------------------- - - - #-------------------------------------------------- + # -------------------------------------------------- # Groups - #-------------------------------------------------- - - + # -------------------------------------------------- - #-------------------------------------------------- + # -------------------------------------------------- # Backup - #-------------------------------------------------- + # -------------------------------------------------- """ @config_backup.command(name="get", aliases=["create"]) async def backup_create(self, ctx: MyContext): @@ -302,10 +375,10 @@ def check(reaction, user): await ctx.send('👍') """ - #-------------------------------------------------- + # -------------------------------------------------- # Archives - #-------------------------------------------------- + # -------------------------------------------------- -async def setup(bot): +async def setup(bot: Gunibot = None, plugin_config: dict = None): await bot.add_cog(Sconfig(bot)) diff --git a/bot/utils/timeclass.py b/bot/utils/timeclass.py index 03a0d925..1b74099b 100644 --- a/bot/utils/timeclass.py +++ b/bot/utils/timeclass.py @@ -1,3 +1,10 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + import datetime import time from utils import Gunibot @@ -5,12 +12,48 @@ import discord from discord.ext import tasks -fr_months = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", - "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Décembre"] -en_months = ["January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December"] -fi_months = ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', - 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'] +fr_months = [ + "Janvier", + "Février", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Aout", + "Septembre", + "Octobre", + "Novembre", + "Décembre", +] +en_months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +] +fi_months = [ + "tammikuu", + "helmikuu", + "maaliskuu", + "huhtikuu", + "toukokuu", + "kesäkuu", + "heinäkuu", + "elokuu", + "syyskuu", + "lokakuu", + "marraskuu", + "joulukuu", +] class TimeCog(discord.ext.commands.Cog): @@ -27,21 +70,36 @@ def add_task(self, delay: int, coro, *args, **kwargs): async def launch(task, coro, *args, **kwargs): if task.current_loop != 0: await self.bot.wait_until_ready() - self.bot.log.info("[TaskManager] Tâche {} arrivée à terme".format(coro.__func__)) + self.bot.log.info( + "[TaskManager] Tâche {} arrivée à terme".format(coro.__func__) + ) try: await coro(*args, **kwargs) except Exception as e: self.bot.get_cog("Errors").on_error(e) + a = tasks.loop(seconds=delay, count=2)(launch) a.error(self.bot.get_cog("Errors").on_error) a.start(a, coro, *args, **kwargs) self.bot.log.info( - "[TaskManager] Nouvelle tâche {} programmée pour dans {}s".format(coro.__func__, delay)) + "[TaskManager] Nouvelle tâche {} programmée pour dans {}s".format( + coro.__func__, delay + ) + ) return a class timedelta: - - def __init__(self, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, total_seconds=0, precision=2): + def __init__( + self, + years=0, + months=0, + days=0, + hours=0, + minutes=0, + seconds=0, + total_seconds=0, + precision=2, + ): self.years = years self.months = months self.days = days @@ -54,8 +112,8 @@ def __init__(self, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, tot def set_from_seconds(self): t = self.total_seconds rest = 0 - years, rest = divmod(t, 86400*365) - months, rest = divmod(rest, 86400*365/12) + years, rest = divmod(t, 86400 * 365) + months, rest = divmod(rest, 86400 * 365 / 12) days, rest = divmod(rest, 86400) hours, rest = divmod(rest, 3600) minutes, rest = divmod(rest, 60) @@ -70,10 +128,19 @@ def set_from_seconds(self): else: self.seconds = round(seconds, self.precision) - async def time_delta(self, date1, date2=None, lang='en', year=False, hour=True, form='developed', precision=2): + async def time_delta( + self, + date1, + date2=None, + lang="en", + year=False, + hour=True, + form="developed", + precision=2, + ): """Traduit un intervale de deux temps datetime.datetime en chaine de caractère lisible""" - if date2 != None: - if type(date2) == datetime.datetime: + if date2 is not None: + if isinstance(date2, datetime.datetime): delta = abs(date2 - date1) t = await self.time_interval(delta, precision) else: @@ -81,79 +148,128 @@ async def time_delta(self, date1, date2=None, lang='en', year=False, hour=True, else: t = self.timedelta(total_seconds=date1, precision=precision) t.set_from_seconds() - if form == 'digital': + if form == "digital": if hour: h = "{}:{}:{}".format(t.hours, t.minutes, t.seconds) else: - h = '' - if lang == 'fr': - text = '{}/{}{} {}'.format(t.days, t.months, - "/"+str(t.years) if year else '', h) + h = "" + if lang == "fr": + text = "{}/{}{} {}".format( + t.days, t.months, "/" + str(t.years) if year else "", h + ) else: - text = '{}/{}{} {}'.format(t.months, t.days, - "/"+str(t.years) if year else '', h) - elif form == 'temp': + text = "{}/{}{} {}".format( + t.months, t.days, "/" + str(t.years) if year else "", h + ) + elif form == "temp": text = str() - if t.days + t.months*365/12 + t.years*365 > 0: - d = round(t.days+t.months*365/12) + if t.days + t.months * 365 / 12 + t.years * 365 > 0: + d = round(t.days + t.months * 365 / 12) if not year: - d += round(t.years*365) + d += round(t.years * 365) elif year and t.years > 0: - text += str(t.years) + \ - 'a ' if lang == 'fr' else str(t.years)+'y ' - text += str(d)+'j ' if lang == 'fr' else str(d)+'d ' + text += str(t.years) + "a " if lang == "fr" else str(t.years) + "y " + text += str(d) + "j " if lang == "fr" else str(d) + "d " if hour: if t.hours > 0: - text += str(t.hours)+'h ' + text += str(t.hours) + "h " if t.minutes > 0: - text += str(t.minutes)+'m ' + text += str(t.minutes) + "m " if t.seconds > 0: - text += str(t.seconds)+'s ' + text += str(t.seconds) + "s " text = text.strip() else: text = str() - if lang == 'fr': - lib = ['ans', 'an', 'mois', 'mois', 'jours', 'jour', 'heures', - 'heure', 'minutes', 'minute', 'secondes', 'seconde'] - elif lang == 'lolcat': - lib = ['yearz', 'year', 'mons', 'month', 'dayz', 'day', - 'hourz', 'hour', 'minutz', 'minut', 'secondz', 'secnd'] - elif lang == 'fi': - lib = ['Vuotta', 'vuosi', 'kuukautta', 'kuukausi', 'päivää', 'päivä', - 'tuntia', 'h', 'minuuttia', 'minute', 'sekuntia', 'toinen'] + if lang == "fr": + lib = [ + "ans", + "an", + "mois", + "mois", + "jours", + "jour", + "heures", + "heure", + "minutes", + "minute", + "secondes", + "seconde", + ] + elif lang == "lolcat": + lib = [ + "yearz", + "year", + "mons", + "month", + "dayz", + "day", + "hourz", + "hour", + "minutz", + "minut", + "secondz", + "secnd", + ] + elif lang == "fi": + lib = [ + "Vuotta", + "vuosi", + "kuukautta", + "kuukausi", + "päivää", + "päivä", + "tuntia", + "h", + "minuuttia", + "minute", + "sekuntia", + "toinen", + ] else: - lib = ['years', 'year', 'months', 'month', 'days', 'day', - 'hours', 'hour', 'minutes', 'minute', 'seconds', 'second'] + lib = [ + "years", + "year", + "months", + "month", + "days", + "day", + "hours", + "hour", + "minutes", + "minute", + "seconds", + "second", + ] if year and t.years != 0: if t.years > 1: - text += str(t.years)+" "+lib[0] + text += str(t.years) + " " + lib[0] else: - text += str(t.years)+" "+lib[1] + text += str(t.years) + " " + lib[1] text += " " if t.months > 1: - text += str(t.months)+" "+lib[2] + text += str(t.months) + " " + lib[2] elif t.months == 1: - text += str(t.months)+" "+lib[3] + text += str(t.months) + " " + lib[3] text += " " if t.days > 1: - text += str(t.days)+" "+lib[4] + text += str(t.days) + " " + lib[4] elif t.days == 1: - text += str(t.days)+" "+lib[5] + text += str(t.days) + " " + lib[5] if hour: if t.hours > 1: - text += " "+str(t.hours)+" "+lib[6] + text += " " + str(t.hours) + " " + lib[6] elif t.hours == 1: - text += " "+str(t.hours)+" "+lib[7] + text += " " + str(t.hours) + " " + lib[7] text += " " if t.minutes > 1: - text += str(t.minutes)+" "+lib[8] + text += str(t.minutes) + " " + lib[8] elif t.minutes == 1: - text += str(t.minutes)+" "+lib[9] + text += str(t.minutes) + " " + lib[9] text += " " if t.seconds > 1: - text += str(t.seconds)+" "+lib[10] + text += str(t.seconds) + " " + lib[10] elif t.seconds == 1: - text += str(t.seconds)+" "+lib[11] + text += str(t.seconds) + " " + lib[11] return text.strip() async def time_interval(self, tmd, precision=2): @@ -163,52 +279,60 @@ async def time_interval(self, tmd, precision=2): obj.set_from_seconds() return obj - async def date(self, date, lang='fr', year=False, hour=True, digital=False): + async def date(self, date, lang="fr", year=False, hour=True, digital=False): """Traduit un objet de type datetime.datetime en chaine de caractère lisible. Renvoie un str""" - if type(date) == time.struct_time: + if isinstance(date, time.struct_time): date = datetime.datetime(*date[:6]) - if type(date) == datetime.datetime: + if isinstance(date, datetime.datetime): if len(str(date.day)) == 1: - jour = "0"+str(date.day) + jour = "0" + str(date.day) else: jour = str(date.day) h = [] - if lang == 'fr': + if lang == "fr": month = fr_months - elif lang == 'fi': + elif lang == "fi": month = fi_months else: month = en_months - for i in ['hour', 'minute', 'second']: - a = eval(str("date."+i)) + for i in ["hour", "minute", "second"]: + a = eval(str("date." + i)) if len(str(a)) == 1: - h.append("0"+str(a)) + h.append("0" + str(a)) else: h.append(str(a)) if digital: if date.month < 10: - month = "0"+str(date.month) + month = "0" + str(date.month) else: month = str(date.month) separator = "/" - if lang == 'fr': + if lang == "fr": df = "{d}/{m}{y} {h}" - elif lang == 'fi': + elif lang == "fi": df = "{d}.{m}{y} {h}" separator = "." else: df = "{m}/{d}{y} {h}" - df = df.format(d=jour, m=month, y=separator+str(date.year) - if year else "", h=":".join(h) if hour else "") + df = df.format( + d=jour, + m=month, + y=separator + str(date.year) if year else "", + h=":".join(h) if hour else "", + ) else: - if lang == 'fr' or lang == 'fi': + if lang == "fr" or lang == "fi": df = "{d} {m} {y} {h}" else: df = "{m} {d}, {y} {h}" - df = df.format(d=jour, m=month[date.month-1], y=str( - date.year) if year else "", h=":".join(h) if hour else "") + df = df.format( + d=jour, + m=month[date.month - 1], + y=str(date.year) if year else "", + h=":".join(h) if hour else "", + ) return df.strip() -async def setup(bot): +async def setup(bot: Gunibot = None, plugin_config: dict = None): await bot.add_cog(TimeCog(bot)) diff --git a/config/config-example.json b/config/config-example.json deleted file mode 100644 index 564a481a..00000000 --- a/config/config-example.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "token": "Discord token for main bot", - "token_beta": "Discord token for a beta bot (use with --beta arg)", - "main_server_id": 695253389411483739, - "bot_admins": [279568324260528128,125722240896598016], - "errors_channel": 698557078964273279 -} \ No newline at end of file diff --git a/config/global_options.json b/config/global_options.json deleted file mode 100644 index f038401b..00000000 --- a/config/global_options.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "prefix": { - "default": "&", - "type": "prefix", - "command": "prefix" - }, - "language": { - "default": 0, - "type": "language", - "command": "language" - } -} \ No newline at end of file diff --git a/configs/TO_REMOVE.md b/configs/TO_REMOVE.md new file mode 100644 index 00000000..9795a99f --- /dev/null +++ b/configs/TO_REMOVE.md @@ -0,0 +1 @@ +# This folder is deprecated and have to be removed in future versions. Most of files here will be moved (and adapted) in `data/` folder. \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 00000000..cc86cce9 --- /dev/null +++ b/core/__init__.py @@ -0,0 +1,9 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + +version = (1, 4) +version_string = '1.4' \ No newline at end of file diff --git a/core/config.py b/core/config.py new file mode 100644 index 00000000..8a3d92ff --- /dev/null +++ b/core/config.py @@ -0,0 +1,196 @@ +""" +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". +""" + +import yaml +import os +import importlib +from LRFutils import color +from LRFutils import logs + +accept = ["y", "yes", "yeah", "ye"] +decline = ["n", "no", "nope", "nah"] + +_global_config = {} + +# Check basic requirements and start this script if something is missing +def check(): + if not os.path.isfile("config.yaml"): + print(" ") + logs.warn("⛔ The bot is not correctly setup. Running setup script...") + os.system("python3 setup.py") + exit() + + +def get(config: str): + path = config.split(".") + conf = _global_config + for i in path: + conf = conf[i] + return conf + + +################# +# Reload config # +################# + + +def reload_config(): + """This function read the core/default_config.yaml file and store it in a dictionnary. + Then, it update the dict' using all the plugins//config.yaml files. + Finally, it update the dict' using the config.yaml file wich is defined by the user. + Each step overwrite the previus one.""" + + with open("core/default_config.yaml", "r") as f: + _global_config.update(yaml.safe_load(f)) + + for plugin in os.listdir(f"plugins"): + if os.path.isfile(file := f"plugins/" + plugin + "/config.yaml"): + with open(file) as f: + _global_config.update({plugin: yaml.safe_load(f)}) + + if os.path.isfile("config.yaml"): + with open("config.yaml", "r") as f: + _global_config.update(yaml.safe_load(f)) + + # Save config + with open("config.yaml", "w") as f: + yaml.dump(_global_config, f) + + +# Automatically load config when the file is imported +if _global_config == {}: + reload_config() + +################ +# Plugin Setup # +################ + + +def setup_plugins(): + """Run the "run" function of each plugin's "setup.py" file in order to allow user to configure the plugins. + Called once in the main setup script.""" + + for plugin in os.listdir(f"plugins"): + if os.path.isfile(f"plugins/" + plugin + "/setup.py"): + + plugin_setup = importlib.import_module(f"plugins." + plugin + ".setup") + + choice = input( + f"\n{color.fg.blue}🔌 Do you want to configure {plugin} plugin? [Y/n]:{color.stop} " + ) + + if choice.lower() not in decline: + plugin_config = plugin_setup.run() + if plugin_config is not None: + _global_config.update({plugin: plugin_config}) + + # Save config + with open("config.yaml", "w") as f: + yaml.dump(_global_config, f) + + +############### +# TOKEN CHECK # +############### + + +def token_set(force_set=False): + """Check if the token is set, if not, ask for it. Return True if the token is set, False if not.""" + + if _global_config["bot"]["token"] is not None and not force_set: + choice = input( + f"\n🔑 {color.fg.blue}A token is already set. Do you want to edit it? [y/N]:{color.stop} " + ) + if choice.lower() not in accept: + return + + print( + f"\n🔑 You need to set your Discord bot token in the config file.\n To do so, go on {color.fg.blue}https://discord.com/developers/applications{color.stop}, select your application, go in bot section and copy your token.\n To create a bot application, please refere to this page: {color.fg.blue}https://discord.com/developers/docs/intro{color.stop}.\n Also, be sure to anable all intents." + ) + + token = "" + while token == "": + token = input(f"\n🔑 {color.fg.blue}Your bot token:{color.stop} ") + if token == "": + print(f"\n{color.fg.red}🔑 You need to set a token.{color.stop}") + else: + _global_config["bot"]["token"] = token + + with open("config.yaml", "w") as f: + yaml.dump(_global_config, f) + return True + + +######################### +# Advanced config setup # +######################### + + +def advanced_setup(): + + # Language + + lang = "Baguette de fromage" + language = _global_config["bot"]["default_language"] + while lang.lower() not in ["en", "fr", ""]: + lang = input( + f"\n{color.fg.blue}🌐 Choose your language [en/fr] (current: {language}):{color.stop} " + ) + if lang.lower() not in ["en", "fr", ""]: + print(f"{color.fg.red}🌐 Invalid language.{color.stop}") + if lang != "": + _global_config["bot"]["default_language"] = lang.lower() + + # Prefix + + prefix = _global_config["bot"]["default_prefix"] + choice = input( + f"\n{color.fg.blue}⚜️ Choose the bot command prefix? (current: {prefix}):{color.stop} " + ) + if choice != "": + _global_config["bot"]["default_prefix"] = choice + + # Admins + + error = True + while error: + error = False + choice = input( + f"\n{color.fg.blue}👑 Bot admins (User ID separated with comma. Let empty to ignore):{color.stop} " + ) + if choice != "": + admins = choice.replace(" ", "").split(",") + try: + for admin in admins: + admin = int(admin) + _global_config["bot"]["admins"] = admins + except: + print( + f"{color.fg.red}👑 Invalid entry. Only user ID (integers), comma and space are expected.{color.stop}" + ) + error = True + + # Error channel + + error = True + while error: + error = False + choice = input( + f"\n{color.fg.blue}🤕 Error channel (Channel ID. Let empty to ignore):{color.stop} " + ) + if choice != "": + try: + channel = int(choice) + _global_config["bot"]["error_channels"] = channel + except: + print( + f"{color.fg.red}🤕 Invalid entry. Only channel ID (integers) are expected.{color.stop}" + ) + error = True + + with open("config.yaml", "w") as f: + yaml.dump(_global_config, f) diff --git a/core/default_config.yaml b/core/default_config.yaml new file mode 100644 index 00000000..18c52f86 --- /dev/null +++ b/core/default_config.yaml @@ -0,0 +1,7 @@ +bot: + token: null + default_prefix: "!" + default_language: "en" + admins: [279568324260528128,125722240896598016] # (Default: Leirof & Z_runner, the project leaders of Gipsy) + error_channels: 823813751018487848 # (Default: Hidden channel on Gunivers) + diff --git a/credits.md b/credits.md new file mode 100644 index 00000000..a4a4258c --- /dev/null +++ b/credits.md @@ -0,0 +1,11 @@ +Copyright © ZRunner 2020 - 2023 +Copyright © Aeris One 2020 - 2023 +Copyright © Leirof 2020 - 2023 +Copyright © theogiraudet 2020 - 2023 +Copyright © ascpial 2021 - 2023 +Copyright © Theaustudio 2022 - 2023 + +Ce programme est régi par la licence CeCILL soumise au droit français et +respectant les principes de diffusion des logiciels libres. Vous pouvez +utiliser, modifier et/ou redistribuer ce programme sous les conditions +de la licence CeCILL diffusée sur le site "http://www.cecill.info". diff --git a/data/model.sql b/data/model.sql index e69de29b..949b3523 100644 --- a/data/model.sql +++ b/data/model.sql @@ -0,0 +1,4 @@ +-- Ce programme est régi par la licence CeCILL soumise au droit français et +-- respectant les principes de diffusion des logiciels libres. Vous pouvez +-- utiliser, modifier et/ou redistribuer ce programme sous les conditions +-- de la licence CeCILL diffusée sur le site "http://www.cecill.info". diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..ed880990 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/banner.png b/docs/_static/banner.png new file mode 100644 index 00000000..c6bbc056 Binary files /dev/null and b/docs/_static/banner.png differ diff --git a/docs/_static/css/stylesheet.css b/docs/_static/css/stylesheet.css new file mode 100644 index 00000000..263ae837 --- /dev/null +++ b/docs/_static/css/stylesheet.css @@ -0,0 +1,17 @@ +html[data-theme="light"] { + --pst-color-primary: #fa9632; + --pst-color-secondary: #3296fa; + --sd-color-card-border-hover: var(--pst-color-primary); + --pst-color-primary-highlight: var(--pst-color-secondary); +} + +html[data-theme="dark"] { + --pst-color-primary: #fa9632; + --pst-color-secondary: #3296fa; + --sd-color-card-border-hover: var(--pst-color-primary); + --pst-color-primary-highlight: var(--pst-color-secondary); +} + +a.headerlink { + color: var(--pst-color-secondary); +} \ No newline at end of file diff --git a/docs/_static/logo-discord.png b/docs/_static/logo-discord.png new file mode 100644 index 00000000..30d61361 Binary files /dev/null and b/docs/_static/logo-discord.png differ diff --git a/docs/_static/logo-gunivers.png b/docs/_static/logo-gunivers.png new file mode 100644 index 00000000..a1622c1a Binary files /dev/null and b/docs/_static/logo-gunivers.png differ diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 00000000..dd03f874 Binary files /dev/null and b/docs/_static/logo.png differ diff --git a/docs/_static/meet_gipsy.png b/docs/_static/meet_gipsy.png new file mode 100644 index 00000000..120c5898 Binary files /dev/null and b/docs/_static/meet_gipsy.png differ diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 00000000..77f892ef --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,9 @@ +# 📜 Changelog + +> Coming soon + +```{admonition} 🤝 Help us building this project! +:class: note + +This part of the documentation is still under construction. If you want to help us, you can contribute to the project on [GitHub](https://github.com/Gunivers/Gipsy) or come on our [Discord server](https://discord.gg/E8qq6tN). +``` \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 1dcfb6e9..78ada2a7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,60 +1,180 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html -# -- Path setup -------------------------------------------------------------- -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +# -- Project information ----------------------------------------------------- +project = "Gipsy" +copyright = "2023, Gunivers" +author = "Z_runner, Leirof, Aeris One, ascpial, theogiraudet, fantomitechno, Just_a_Player and Aragorn" -# -- Project information ----------------------------------------------------- +import os + +# Project information --------------------------------------------------------- project = 'Gipsy' -copyright = '2021, Gunivers' +copyright = '2023, Gunivers' author = 'Gunivers' +html_favicon = "_static/logo.png" -# The full version, including alpha/beta/rc tags -release = '1.3' +# -- General configuration ---------------------------------------------------- - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ - 'myst_parser' + 'myst_parser', + 'sphinx_design', + 'sphinx_togglebutton', + 'sphinx_copybutton', ] - - -# Add any paths that contain templates here, relative to this directory. +myst_heading_anchors = 6 templates_path = ['_templates'] - -# The root document. -root_doc = 'index' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +# Options for HTML output ----------------------------------------------------- + +html_theme = 'pydata_sphinx_theme' + +# html_css_files = [ +# 'credits.css', +# ] + +html_theme_options = { + "github_url": "https://github.com/Gunivers/Gipsy", + "announcement": "⚠️ You are reading a doc of an undergoing development version. Information can be out of date and/or change at any time. ⚠️", + "logo": { + "image_dark": "_static/logo.png", + "text": "Gipsy", # Uncomment to try text with logo + }, + "icon_links": [ + { + "name": "Support us", + "url": "https://utip.io/gunivers", + "icon": "fa fa-heart", + }, + { + "name": "Gunivers", + "url": "https://gunivers.net", + "icon": "_static/logo-gunivers.png", + "type": "local", + }, + { + "name": "Discord server", + "url": "https://discord.gg/E8qq6tN", + "icon": "_static/logo-discord.png", + "type": "local", + }, + ] +} + +html_logo = "_static/logo.png" + +html_static_path = ['_static'] + +html_css_files = [ + 'css/stylesheet.css', +] -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' +myst_enable_extensions = [ + "amsmath", + "colon_fence", + "deflist", + "dollarmath", + "fieldlist", + "html_admonition", + "html_image", + #"linkify", + "replacements", + "smartquotes", + "strikethrough", + "substitution", + "tasklist", +] -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +# Plugin doc generation ------------------------------------------------------- + +import shutil + +CONTRIBUTE = """ +```{admonition} 🤝 Help us to improve this documentation! +:class: tip +If you want to help us to improve this documentation, you can edit it on the [GitHub repo](https://github.com/Gunivers/Gipsy/) or come and discuss with us on our [Discord server](https://discord.gg/E8qq6tN)! +``` +""" + +GITHUB_DISCUSSION_FOOTER = """ +--- +## 💬 Did it help you? +Feel free to leave your questions and feedbacks below! + +""" + +def generate_plugin_doc(): + + # Saving index.md file content except plugin toctree + with open("user_guide.md", "r", encoding="utf-8") as f: + before = [] + for line in f: + before.append(line) + if line.startswith(" + +# ❓ FAQ + +Nothing here... for the moment. + + +```{admonition} 🤝 Help us to improve this documentation! +:class: tip + +If you want to help us to improve this documentation, you can edit it on the [GitHub repo](https://github.com/Gunivers/Gipsy/) or come and discuss with us on our [Discord server](https://discord.gg/E8qq6tN)! +``` diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 00000000..ba26a809 --- /dev/null +++ b/docs/getting_started.md @@ -0,0 +1,136 @@ +--- +html_theme.sidebar_secondary.remove: true +html_theme.sidebar_primary.remove: true +--- + +# 👋 Getting Started + +## 📥 Get it on your Discord server + +::::{tab-set} +:::{tab-item} 💌 Invite Gunivers' instance + +
+ +You can simply invite the bot instance hosted by the Gunivers community itself by clicking on the button below! + +```{button-link} http://utip.io/s/1yhs7W +:color: primary +:align: center +:shadow: + +Invite the bot on your server +``` + +Alternatively, you can invite the beta version by [clicking here](https://discordapp.com/oauth2/authorize?client_id=813836349147840513&scope=bot) + +
+ +```{admonition} If you use the beta version +:class: important + +If you invite the beta version, you will be able to test the latest features added to the bot. However, the bot in beta version may contain security holes and many bugs. It may also stop working suddenly and for long periods. +``` + +::: +:::{tab-item} ⚙️ Self-host the bot + +1. Create a Discord application and add a bot to it by following [this tutorial](https://discord.com/developers/docs/getting-started). + +2. Install [Python 3.9](https://www.python.org/downloads/release/python-390/) or higher. + +3. Install [Git CLI](https://git-scm.com/book/en/v2/Getting-Started-The-Command-Line) + +4. Open a terminal and go where you want to install it + + ```bash + cd /path/to/installation + ``` + +5. Clone the repository + + ```bash + git clone https://github.com/Gunivers/Gipsy + ``` + +6. (Optional) Create a virtual environment with + + ``` + python3.9 -m venv venv + ``` + and activate it with + ``` + source venv/bin/activate # Linux. + venv\Scripts\activate # Windows + ``` + +7. Install dependencies + + ``` + pip install -r requirements.txt + ``` + +8. Run the setup script and answer the questions. + + ```bash + python setup.py + ``` + +9. Create a `plugins` folder and add all the plugins you wan to use on your bot. You can find all the official plugins [here](https://github.com/Gunivers/Gipsy-plugins). To install a plugin, simply copy the folder of the plugin in the `plugins` folder. + +10. Start the bot + ```bash + python start.py + ``` + +11. In the logs, find a line like this: + + ``` + 09/02/2023 at 19:59:32 | [INFO] ID : 786546781919641600 + ``` + +12. Copy the ID an place it in the following URL (replace the underscores with the ID): + + ``` + https://discord.com/oauth2/authorize?scope=bot&client_id=__________________ + ``` + +13. Open the URL in your browser and invite the bot to your server. + +::: +:::{tab-item} 🌐 Other instances + +Here is the list of other Gipsy instances hosted by trusted peoples: + +```` {grid} 2 +```{grid-item-card} Axobot +:link: https://top.gg/bot/1048011651145797673 +:link-type: url +:margin: 0 3 0 0 + +
+ +
+ +Axobot is not really a Gipsy instance, but it is maintained by the same original creator and both project share the same origin. It is designed to a ready-to-use high quality bot capable of handling a huge amount of servers, such as most popular bots like Mee6 or Dyno. + +``` +```` + +::: +:::: + +## 👶 First steps + +In a channel where the bot can read and write messages, follow the instructions below. + +1. Define your language with the command `@gipsy config language ` +2. Define the prefix with the command `@gipsy config prefix `. + Exemple: type `@gipsy config prefix !` and then `!ping` + +```{note} +In this documentation, we will use `@gipsy` as the prefix because it always work, even if another prefix is set. +``` +3. Type `@gipsy config` to see the rest of the option you can configure. To edit a config option, enter `@gipsy config