diff --git a/.env-default b/.env-default index a22695bd..93114b81 100644 --- a/.env-default +++ b/.env-default @@ -3,4 +3,7 @@ AI_API_TOKEN="Your token here for the ai part" AI_API_BASE_URL="The url for the request open ai or any openai proxy one do not forget to end with a / the url" IMAGE_GENERATION_MODELS_ON=True IMAGE_GENERATION_MODELS="" -LOG="info" # trace, debug, info, warn or error \ No newline at end of file +RUST_LOG="info" # trace, debug, info, warn or error +NSFW=True# if the bot should honor the nsfw for image if set to true it will if false it will not. (turn it off if you are 100% the model you use for image can't generate nsfw) +REMOVE_OLD_COMMAND_ON_STARTUP=True# if the bot should remove the old command when it start. recommended to set it for the 1st launch or when updating to a breaking change version +DB_TYPE="sqlite" \ No newline at end of file diff --git a/.github/workflows/docker-image-dev.yml b/.github/workflows/docker-image-dev.yml index 6fa4d09d..05ae8a84 100644 --- a/.github/workflows/docker-image-dev.yml +++ b/.github/workflows/docker-image-dev.yml @@ -32,3 +32,5 @@ jobs: tags: ${{ secrets.DOCKER_HUB_USERNAME }}/kasuki:dev cache-from: type=gha cache-to: type=gha,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 diff --git a/.github/workflows/docker-image-release.yml b/.github/workflows/docker-image-release.yml index 9e17fd65..b8e4ca05 100644 --- a/.github/workflows/docker-image-release.yml +++ b/.github/workflows/docker-image-release.yml @@ -32,3 +32,5 @@ jobs: tags: ${{ secrets.DOCKER_HUB_USERNAME }}/kasuki:latest cache-from: type=gha cache-to: type=gha,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 diff --git a/.github/workflows/docker-image-v2.yml b/.github/workflows/docker-image-v2.yml new file mode 100644 index 00000000..4f1d1e3d --- /dev/null +++ b/.github/workflows/docker-image-v2.yml @@ -0,0 +1,36 @@ +name: Build & Publish Release Docker Image + +on: + push: + branches: + - v2 + workflow_dispatch: + +concurrency: + group: "v2" + cancel-in-progress: false + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Build and push + id: docker_build + uses: docker/build-push-action@v5 + with: + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ secrets.DOCKER_HUB_USERNAME }}/kasuki:v2 + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 07ca527a..2713c60b 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: permissions: - checks: write + checks: write jobs: linter: @@ -16,15 +16,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Setup Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: clippy + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy - - name: Run Clippy - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + - name: Run Clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index dc614b42..7c4a4cf5 100644 --- a/.gitignore +++ b/.gitignore @@ -96,4 +96,255 @@ fabric.properties .env -.idea/ \ No newline at end of file +.idea/rust-project.json + +./logs +/.idea/.gitignore +/.idea/codeStyles/codeStyleConfig.xml +/.idea/dataSources.xml +/.idea/kasuki.iml +/logs/log_8d6fe47c-8c46-49c2-82c6-93bd4e3437a8.log +/logs/log_8e4b249a-9b49-45b2-8ab8-60bf2e8b64e1.log +/logs/log_016323ef-ea3f-42dd-9736-32e9dc45d846.log +/logs/log_220647e1-7227-4f52-99be-501a90ac140f.log +/logs/log_c577b710-0e43-4f1e-97ae-0ed3662e751c.log +/logs/log_fbbd1ffb-1193-419f-aae4-47d7be1446fa.log +/.idea/modules.xml +/.idea/inspectionProfiles/Project_Default.xml +/.idea/rust.xml +/.idea/vcs.xml +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +.history/ +*.vsix +rust-project.json +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +*.stackdump +[Dd]esktop.ini +$RECYCLE.BIN/ +*.cab +*.msi +*.msix +*.msm +*.msp +*.lnk +*~ +.fuse_hidden* +.directory +.Trash-* +.nfs* +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates +*.userprefs +mono_crash.* +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ +.vs/ +Generated\ Files/ +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +*.VisualState.xml +TestResult.xml +nunit-*.xml +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c +BenchmarkDotNet.Artifacts/ +project.lock.json +project.fragment.lock.json +artifacts/ +ScaffoldingReadMe.txt +StyleCopReport.xml +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc +_Chutzpah* +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb +*.psess +*.vsp +*.vspx +*.sap +*.e2e +$tf/ +*.gpState +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user +_TeamCity* +*.dotCover +.axoCover/* +!.axoCover/settings.json +coverage*.json +coverage*.xml +coverage*.info +*.coverage +*.coveragexml +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* +*.mm.* +AutoTest.Net/ +.sass-cache/ +[Ee]xpress/ +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html +publish/ +*.[Pp]ublish.xml +*.azurePubxml +*.pubxml +*.publishproj +PublishScripts/ +*.nupkg +*.snupkg +**/[Pp]ackages/* +!**/[Pp]ackages/build/ +*.nuget.props +*.nuget.targets +csx/ +*.build.csdef +ecf/ +rcf/ +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload +*.[Cc]ache +!?*.[Cc]ache/ +ClientBin/ +~$* +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs +Generated_Code/ +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak +*.mdf +*.ldf +*.ndf +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl +FakesAssemblies/ +*.GhostDoc.xml +.ntvs_analysis.dat +node_modules/ +*.plg +*.opt +*.vbw +*.vbp +*.dsw +*.dsp +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions +.paket/paket.exe +paket-files/ +.fake/ +.cr/personal +__pycache__/ +*.pyc +*.tss +*.jmconfig +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs +OpenCover/ +ASALocalRun/ +*.binlog +*.nvuser +.mfractor/ +.localhistory/ +.vshistory/ +healthchecksdb +MigrationBackup/ +.ionide/ +FodyWeavers.xsd +*.code-workspace +*.sln.iml +*.tar +*.tar.* +*.jar +*.exe +*.zip +*.tgz +*.log.* +*.sig +pkg/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..ee65c3ea --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,149 @@ +# Contributor Covenant Code of Conduct + + +## Our Pledge + + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + + +## Our Standards + + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + + +## Scope + + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + + +## Enforcement + + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + + +## Enforcement Guidelines + + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + + +### 1. Correction + + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + + +### 2. Warning + + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + + +### 3. Temporary Ban + + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + + +### 4. Permanent Ban + + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + + +## Attribution + + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Cargo.toml b/Cargo.toml index cceb04e6..67a94816 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,26 +7,35 @@ description = "A discord bot written in rust that get info from anilist API" readme = "readme.md" repository = "https://github.com/ValgulNecron/kasuki" license-file = "LICENSE" +include = ["src", "LICENSE"] +exclude = [".*", "json", "logs"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -regex = "1.9.3" -reqwest = "0.11.18" -serde = "1.0.183" -serde_json = "1.0.105" -serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "utils"] } -tokio = { version = "1.32.0", features = ["full"] } -sqlx = { version = "0.7.1", features = ["sqlite", "runtime-tokio-native-tls"] } +regex = "1.10.2" +reqwest = { version = "0.11.22", features = ["json"] } +serde = "1.0.193" +serde_json = "1.0.108" +serenity = { version = "0.12.0", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "utils", "full"] } +tokio = { version = "1.34.0", features = ["full"] } +sqlx = { version = "0.7.3", features = ["sqlite", "runtime-tokio-native-tls"] } rand = "0.8.5" -chrono = "0.4.26" -uuid = { version = "1.4.1", features = ["v4"] } +chrono = "0.4.31" +uuid = { version = "1.6.1", features = ["v4"] } dotenv = "0.15.0" -tide = "0.16.0" image = "0.24.7" -base64 = "0.21.2" -log = { version = "0.4.20" } +base64 = "0.21.5" +tracing = { version = "0.1.40" } +once_cell = "1.18.0" +tracing-subscriber = { version = "0.3.18", features = ["default", "env-filter"] } +tracing-core = "0.1.32" + +[profile.dev] +codegen-units = 512 [profile.release] lto = true +incremental = true +codegen-units = 8 diff --git a/Dockerfile b/Dockerfile index 7baf891b..cc287786 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.72-buster AS builder +FROM rust:1.74-buster AS builder RUN USER=root cargo new --bin kasuki @@ -21,15 +21,17 @@ LABEL author="valgul" LABEL "com.docker.compose.hide"="true" LABEL hidden="true" +HEALTHCHECK CMD ps aux | grep kasuki || exit 1 + +WORKDIR /kasuki/ + +COPY json /kasuki/json + RUN apt-get update && apt-get install -y --no-install-recommends \ libssl-dev libsqlite3-dev \ libpng-dev libjpeg-dev \ ca-certificates && rm -rf /var/lib/apt/lists/* -WORKDIR /kasuki/ - -COPY lang_file /kasuki/lang_file - COPY --from=builder /kasuki/target/release/kasuki /kasuki/. CMD ["./kasuki"] \ No newline at end of file diff --git a/_not_used/readme.md b/_not_used/readme.md new file mode 100644 index 00000000..5969b2fc --- /dev/null +++ b/_not_used/readme.md @@ -0,0 +1,2 @@ +This folder contain source code and json file for stuff that my bot should do an was already done but i can't manage to +do it or serenity block by changing return type. \ No newline at end of file diff --git a/anilist_post.md b/anilist_post.md new file mode 100644 index 00000000..d6c6bb68 --- /dev/null +++ b/anilist_post.md @@ -0,0 +1,33 @@ +# Discord bot that uses the anilist api to get anime/manga info + + +## **What does it do?** + +- Get anime/manga/ln info +- Get anime airing notifications +- Get user info +- Get character info +- Get studio info +- Get staff info +- Generate an image with a seyuu and 4 characters that they voiced +- Get a random anime/manga +- and more... (WIP) + +It also convert anilist flavored markdown into discord markdown (and also include "all" the html stuff). + +you can find the bot on [github](https://github.com/ValgulNecron/kasuki) +or [add it with](https://discord.com/api/oauth2/authorize?client_id=923286536445894697&permissions=533113194560&scope=bot) + +It also support localisation. If you want to add one you simply need to edit the json file on the github repos. (I will accept new translation on the repos (if it seems good on deepl), but will not add more myself since I know no one who speaks other language to help me translate it.) +You can also request feature and report issue. + + +## **Screenshot** + + +![anime command](https://files.catbox.moe/pmp6be.png) +![seiyuu command](https://files.catbox.moe/nwn173.png) +![user command](https://files.catbox.moe/jcxunp.png) +![register command](https://files.catbox.moe/pybzns.png) +![autocomplete](https://files.catbox.moe/3j3xwf.png) +![character command](https://files.catbox.moe/62tmhy.png) \ No newline at end of file diff --git a/cache.db b/cache.db index 105269b9..b4cc82e9 100644 Binary files a/cache.db and b/cache.db differ diff --git a/compose-default.yml b/compose-default.yml index 82e20450..305966fb 100644 --- a/compose-default.yml +++ b/compose-default.yml @@ -9,13 +9,19 @@ services: # image: valgul/kasuki:dev # the latest dev build that compile there could be error. container_name: kasuki restart: unless-stopped + tty: true environment: - DISCORD_TOKEN=your_discord_token_here - AI_API_TOKEN=your_token_here_for_the_ai_part - AI_API_BASE_URL=the_url_for_the_request_open_ai_or_any_openai_proxy_one_do_not_forget_to_end_with_a_/_the_url - IMAGE_GENERATION_MODELS_ON=True # true if you use have a proxy server on which you added another model - IMAGE_GENERATION_MODELS=model_name # the name of the model you want only used if IMAGE_GENERATION_MODELS_ON is True - - LOG="info" # trace, debug, info, warn or error + - RUST_LOG="info" # trace, debug, info, warn or error + - NSFW=True # if the bot should honor the nsfw for image if set to true it will if false it will not. (turn it off if you are 100% the model you use for image can't generate nsfw) + - TERM=xterm-256color + - REMOVE_OLD_COMMAND_ON_STARTUP=True # if the bot should remove the old command when it start. recommended to set it for the 1st launch or when updating to a breaking change version + - DB_TYPE="sqlite" volumes: - ./db/data.db:/kasuki/data.db - - ./db/cache.db:/kasuki/cache.db \ No newline at end of file + - ./db/cache.db:/kasuki/cache.db + - ./logs/:/kasuki/logs/ \ No newline at end of file diff --git a/data.db b/data.db index e14ffdf5..09e95612 100644 Binary files a/data.db and b/data.db differ diff --git a/json/command/add_activity.json b/json/command/add_activity.json new file mode 100644 index 00000000..03350a3d --- /dev/null +++ b/json/command/add_activity.json @@ -0,0 +1,95 @@ +{ + "name": "add_activity", + "desc": "Add an anime activity.", + "dm_command": false, + "nsfw": false, + "perm": true, + "default_permissions": [ + { + "permission": "Administrator" + } + ], + "arg_num": 2, + "args": [ + { + "name": "anime_name", + "desc": "Name of the anime you want to add as an activity.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "anime_name", + "desc": "Name of the anime you want to add as an activity." + }, + { + "code": "fr", + "name": "nom_de_l_anime", + "desc": "Nom de l'anime que vous voulez ajouter comme activité." + }, + { + "code": "de", + "name": "anime_name", + "desc": "Name des Animes, das Sie als Aktivität hinzufügen möchten." + }, + { + "code": "ja", + "name": "anime_no_namae", + "desc": "アクティビティとして追加したいアニメの名前。" + } + ] + }, + { + "name": "delays", + "desc": "A delay in seconds.", + "required": false, + "autocomplete": false, + "command_type": "Integer", + "localised_args": [ + { + "code": "en-US", + "name": "delays", + "desc": "A delay in seconds." + }, + { + "code": "fr", + "name": "delais", + "desc": "Un délai en secondes." + }, + { + "code": "de", + "name": "verzogerungen", + "desc": "Eine Verzögerung in Sekunden." + }, + { + "code": "ja", + "name": "keishi", + "desc": "秒単位の遅延。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "add_activity", + "desc": "Add an anime activity." + }, + { + "code": "fr", + "name": "ajouter_activite", + "desc": "Ajouter une activité anime." + }, + { + "code": "de", + "name": "aktivitat_hinzufugen", + "desc": "Fügen Sie eine Anime-Aktivität hinzu." + }, + { + "code": "ja", + "name": "katsudo_o_tsuika", + "desc": "アニメ活動を追加します。" + } + ] +} diff --git a/json/command/anime.json b/json/command/anime.json new file mode 100644 index 00000000..c3564d8a --- /dev/null +++ b/json/command/anime.json @@ -0,0 +1,61 @@ +{ + "name": "anime", + "desc": "Info of an anime.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "anime_name", + "desc": "Name of the anime you want to check.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "anime_name", + "desc": "Name of the anime you want to check." + }, + { + "code": "fr", + "name": "nom_anime", + "desc": "Nom de l'anime que vous voulez vérifier." + }, + { + "code": "de", + "name": "anime_name", + "desc": "Name des Animes, den Sie überprüfen möchten." + }, + { + "code": "ja", + "name": "anime_no_namae", + "desc": "チェックしたいアニメの名前。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "anime", + "desc": "Info of an anime." + }, + { + "code": "fr", + "name": "anime", + "desc": "Informations sur un anime." + }, + { + "code": "de", + "name": "anime", + "desc": "Informationen zu einem Anime." + }, + { + "code": "ja", + "name": "anime", + "desc": "アニメ情報。" + } + ] +} \ No newline at end of file diff --git a/json/command/avatar.json b/json/command/avatar.json new file mode 100644 index 00000000..eb9a856c --- /dev/null +++ b/json/command/avatar.json @@ -0,0 +1,61 @@ +{ + "name": "avatar", + "desc": "Get the avatar.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "username", + "desc": "Username of the user you want the avatar of.", + "required": false, + "autocomplete": false, + "command_type": "User", + "localised_args": [ + { + "code": "en-US", + "name": "username", + "desc": "Username of the user you want the avatar of." + }, + { + "code": "fr", + "name": "nom_dutilisateur", + "desc": "Nom d'utilisateur de l'utilisateur dont vous souhaitez l'avatar." + }, + { + "code": "de", + "name": "benutzername", + "desc": "Benutzername des Benutzers, von dem Sie den Avatar möchten." + }, + { + "code": "ja", + "name": "ユーザー名", + "desc": "アバターを取得したいユーザーのユーザー名。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "avatar", + "desc": "Get the avatar." + }, + { + "code": "fr", + "name": "avatar", + "desc": "Obtenir l'avatar." + }, + { + "code": "de", + "name": "avatar", + "desc": "Holen Sie sich den Avatar." + }, + { + "code": "ja", + "name": "アバター", + "desc": "アバターを取得します。" + } + ] +} \ No newline at end of file diff --git a/json/command/banner.json b/json/command/banner.json new file mode 100644 index 00000000..b91e7a2e --- /dev/null +++ b/json/command/banner.json @@ -0,0 +1,61 @@ +{ + "name": "banner", + "desc": "Get the banner.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "username", + "desc": "Username of the user you want the profile of.", + "required": false, + "autocomplete": false, + "command_type": "User", + "localised_args": [ + { + "code": "en-US", + "name": "username", + "desc": "Username of the user you want the avatar of." + }, + { + "code": "fr", + "name": "nom_dutilisateur", + "desc": "Nom d'utilisateur de l'utilisateur dont vous souhaitez l'avatar." + }, + { + "code": "de", + "name": "benutzername", + "desc": "Benutzername des Benutzers, von dem Sie den Avatar möchten." + }, + { + "code": "ja", + "name": "ユーザー名", + "desc": "アバターを取得したいユーザーのユーザー名。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "banner", + "desc": "Get the banner." + }, + { + "code": "fr", + "name": "bannière", + "desc": "Obtenez la bannière." + }, + { + "code": "de", + "name": "banner", + "desc": "Holen Sie das Banner." + }, + { + "code": "ja", + "name": "バナー", + "desc": "バナーを取得します。" + } + ] +} \ No newline at end of file diff --git a/json/command/character.json b/json/command/character.json new file mode 100644 index 00000000..255ad7dc --- /dev/null +++ b/json/command/character.json @@ -0,0 +1,61 @@ +{ + "name": "character", + "desc": "Get information about a character.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "name", + "desc": "The name of the character.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "name", + "desc": "The name of the character." + }, + { + "code": "fr", + "name": "nom", + "desc": "Le nom du personnage." + }, + { + "code": "de", + "name": "name", + "desc": "Der Name des Charakters." + }, + { + "code": "ja", + "name": "名前", + "desc": "キャラクターの名前です。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "character", + "desc": "Get information about a character." + }, + { + "code": "fr", + "name": "personnage", + "desc": "Obtenir des informations sur un personnage." + }, + { + "code": "de", + "name": "zeichen", + "desc": "Erhalte Informationen über einen Charakter." + }, + { + "code": "ja", + "name": "キャラクター", + "desc": "キャラクターに関する情報を取得します。" + } + ] +} diff --git a/json/command/compare.json b/json/command/compare.json new file mode 100644 index 00000000..1beff46b --- /dev/null +++ b/json/command/compare.json @@ -0,0 +1,90 @@ +{ + "name": "compare", + "desc": "Compare 2 anilist user.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 2, + "args": [ + { + "name": "username", + "desc": "Username of the anilist user you want to check.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "username", + "desc": "Username of the anilist user you want to check." + }, + { + "code": "fr", + "name": "nom_d_utilisateur", + "desc": "Nom d'utilisateur du utilisateur d'Anilist que vous souhaitez vérifier." + }, + { + "code": "de", + "name": "benutzername", + "desc": "Benutzername des Anilist-Benutzers, den Sie prüfen möchten." + }, + { + "code": "ja", + "name": "ユーザーネーム", + "desc": "チェックしたいアニリストユーザーのユーザーネーム。" + } + ] + }, + { + "name": "username2", + "desc": "Username of the anilist user you want to check.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "username2", + "desc": "Username of the anilist user you want to check." + }, + { + "code": "fr", + "name": "nom_d_utilisateur2", + "desc": "Nom d'utilisateur du utilisateur d'Anilist que vous souhaitez vérifier." + }, + { + "code": "de", + "name": "benutzername2", + "desc": "Benutzername des Anilist-Benutzers, den Sie prüfen möchten." + }, + { + "code": "ja", + "name": "ユーザーネーム2", + "desc": "チェックしたいアニリストユーザーのユーザーネーム。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "compare", + "desc": "Compare 2 anilist user." + }, + { + "code": "fr", + "name": "comparer", + "desc": "Comparer 2 utilisateurs Anilist." + }, + { + "code": "de", + "name": "vergleichen", + "desc": "Vergleichen Sie 2 Anilist-Benutzer." + }, + { + "code": "ja", + "name": "比較", + "desc": "2つのAnilistユーザーを比較します。" + } + ] +} \ No newline at end of file diff --git a/json/command/credit.json b/json/command/credit.json new file mode 100644 index 00000000..27d2e037 --- /dev/null +++ b/json/command/credit.json @@ -0,0 +1,30 @@ +{ + "name": "credit", + "desc": "Get the credit of the app.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 0, + "localised": [ + { + "code": "en-US", + "name": "credit", + "desc": "Get the credit of the app." + }, + { + "code": "fr", + "name": "credit", + "desc": "Obtenir le crédit de l'application." + }, + { + "code": "de", + "name": "kredit", + "desc": "Holen Sie sich das Kredit der App." + }, + { + "code": "ja", + "name": "クレジット", + "desc": "アプリのクレジットを取得します。" + } + ] +} \ No newline at end of file diff --git a/json/command/image.json b/json/command/image.json new file mode 100644 index 00000000..803400dd --- /dev/null +++ b/json/command/image.json @@ -0,0 +1,61 @@ +{ + "name": "image", + "desc": "Generate an image.", + "dm_command": false, + "nsfw": true, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "username", + "desc": "Username of the user you want the avatar of.", + "required": true, + "autocomplete": false, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "description", + "desc": "Enter a description of the image you want to generate." + }, + { + "code": "fr", + "name": "description", + "desc": "Entrez une description de l'image que vous voulez générer." + }, + { + "code": "de", + "name": "beschreibung", + "desc": "Geben Sie eine Beschreibung des Bildes ein, das Sie generieren möchten." + }, + { + "code": "ja", + "name": "説明", + "desc": "生成したい画像の説明を入力してください。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "image", + "desc": "Generate an image." + }, + { + "code": "fr", + "name": "image", + "desc": "Générer une image." + }, + { + "code": "de", + "name": "bild", + "desc": "Ein Bild generieren." + }, + { + "code": "ja", + "name": "画像", + "desc": "画像を生成する。" + } + ] +} \ No newline at end of file diff --git a/json/command/info.json b/json/command/info.json new file mode 100644 index 00000000..c6a401f2 --- /dev/null +++ b/json/command/info.json @@ -0,0 +1,30 @@ +{ + "name": "info", + "desc": "Get information on the bot.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 0, + "localised": [ + { + "code": "en-US", + "name": "info", + "desc": "Get information on the bot." + }, + { + "code": "fr", + "name": "info", + "desc": "Obtenez des informations sur le bot." + }, + { + "code": "de", + "name": "info", + "desc": "Holen Sie sich Informationen über den Bot." + }, + { + "code": "ja", + "name": "info", + "desc": "ボットの情報を取得します。" + } + ] +} \ No newline at end of file diff --git a/json/command/lang.json b/json/command/lang.json new file mode 100644 index 00000000..1e5ff2fe --- /dev/null +++ b/json/command/lang.json @@ -0,0 +1,75 @@ +{ + "name": "langage", + "desc": "Change the language of the bot's response.", + "dm_command": false, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "lang_choice", + "desc": "The language you want to set the response to.", + "required": true, + "autocomplete": false, + "command_type": "String", + "choices": [ + { + "option_choice": "en" + }, + { + "option_choice": "jp" + }, + { + "option_choice": "de" + }, + { + "option_choice": "fr" + } + ], + "localised_args": [ + { + "code": "en-US", + "name": "lang", + "desc": "The language you want to set the response to." + }, + { + "code": "fr", + "name": "lang", + "desc": "The language you want to set the response to." + }, + { + "code": "de", + "name": "lang", + "desc": "The language you want to set the response to." + }, + { + "code": "ja", + "name": "lang", + "desc": "The language you want to set the response to." + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "lang_choice", + "desc": "Change the language of the bot's response." + }, + { + "code": "fr", + "name": "lang_choice", + "desc": "Change the language of the bot's response." + }, + { + "code": "de", + "name": "lang_choice", + "desc": "Change the language of the bot's response." + }, + { + "code": "ja", + "name": "lang_choice", + "desc": "Change the language of the bot's response." + } + ] +} \ No newline at end of file diff --git a/json/command/level.json b/json/command/level.json new file mode 100644 index 00000000..72388076 --- /dev/null +++ b/json/command/level.json @@ -0,0 +1,61 @@ +{ + "name": "level", + "desc": "Get the level of a user.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "username", + "desc": "Username of the Anilist user you want to check.", + "required": false, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "username", + "desc": "Username of the Anilist user you want to check." + }, + { + "code": "fr", + "name": "nom_d_utilisateur", + "desc": "Nom d'utilisateur de l'utilisateur Anilist que vous souhaitez vérifier." + }, + { + "code": "de", + "name": "benutzername", + "desc": "Benutzername des Anilist-Benutzers, den Sie überprüfen möchten." + }, + { + "code": "ja", + "name": "ユーザーネーム", + "desc": "チェックしたいアニリストユーザーのユーザーネーム。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "level", + "desc": "Get the level of a user." + }, + { + "code": "fr", + "name": "level", + "desc": "Obtenez le niveau d'un utilisateur." + }, + { + "code": "de", + "name": "level", + "desc": "Erhalten Sie das Level eines Benutzers." + }, + { + "code": "ja", + "name": "level", + "desc": "ユーザーのレベルを取得します。" + } + ] +} diff --git a/json/command/ln.json b/json/command/ln.json new file mode 100644 index 00000000..1c64f4fc --- /dev/null +++ b/json/command/ln.json @@ -0,0 +1,61 @@ +{ + "name": "ln", + "desc": "Info of a light novel.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "ln_name", + "desc": "Name of the light novel you want to check.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "ln_name", + "desc": "Name of the light novel you want to check." + }, + { + "code": "fr", + "name": "ln_nom", + "desc": "Nom du roman léger que vous souhaitez vérifier." + }, + { + "code": "de", + "name": "ln_name", + "desc": "Name des leichten Romans, den Sie überprüfen möchten." + }, + { + "code": "ja", + "name": "ln_namae", + "desc": "チェックするライトノベルの名前。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "ln", + "desc": "Info of a light novel." + }, + { + "code": "fr", + "name": "ln", + "desc": "Informations sur un roman léger." + }, + { + "code": "de", + "name": "ln", + "desc": "Informationen zu einem leichten Roman." + }, + { + "code": "ja", + "name": "ln", + "desc": "ライトノベルの情報。" + } + ] +} \ No newline at end of file diff --git a/json/command/manga.json b/json/command/manga.json new file mode 100644 index 00000000..8ff7be57 --- /dev/null +++ b/json/command/manga.json @@ -0,0 +1,61 @@ +{ + "name": "manga", + "desc": "Info of a manga.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "manga_name", + "desc": "Name of the manga you want to check.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "manga_name", + "desc": "Name of the manga you want to check." + }, + { + "code": "fr", + "name": "nom_du_manga", + "desc": "Nom du manga que vous voulez vérifier." + }, + { + "code": "de", + "name": "manga_name", + "desc": "Name des Mangas, den Sie überprüfen möchten." + }, + { + "code": "ja", + "name": "manga_no_namae", + "desc": "チェックしたいマンガの名前。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "manga", + "desc": "Info of a manga." + }, + { + "code": "fr", + "name": "manga", + "desc": "Infos d'un manga." + }, + { + "code": "de", + "name": "manga", + "desc": "Info eines Mangas." + }, + { + "code": "ja", + "name": "manga", + "desc": "マンガの情報。" + } + ] +} \ No newline at end of file diff --git a/json/command/module.json b/json/command/module.json new file mode 100644 index 00000000..88e3e56b --- /dev/null +++ b/json/command/module.json @@ -0,0 +1,111 @@ +{ + "name": "module", + "desc": "Turn on or off a module.", + "dm_command": false, + "nsfw": false, + "perm": true, + "default_permissions": [ + { + "permission": "Administrator" + } + ], + "arg_num": 2, + "args": [ + { + "name": "name", + "desc": "The module you want to change the state of.", + "required": true, + "autocomplete": false, + "command_type": "String", + "choices": [ + { + "option_choice": "AI" + }, + { + "option_choice": "ANILIST" + } + ], + "localised_args": [ + { + "code": "en-US", + "name": "module_name", + "desc": "The module you want to change the state of." + }, + { + "code": "fr", + "name": "nom_du_module", + "desc": "Le module dont vous voulez changer l'état." + }, + { + "code": "de", + "name": "modulname", + "desc": "Das Modul, dessen Zustand Sie ändern möchten." + }, + { + "code": "ja", + "name": "モジュール名", + "desc": "状態を変更したいモジュール。" + } + ] + }, + { + "name": "state", + "desc": "The state you want to to.", + "required": true, + "autocomplete": false, + "command_type": "Boolean", + "choices": [ + { + "option_choice": "True" + }, + { + "option_choice": "False" + } + ], + "localised_args": [ + { + "code": "en-US", + "name": "module_state", + "desc": "The state you want to to." + }, + { + "code": "fr", + "name": "statut", + "desc": "L'état que vous voulez appliquer." + }, + { + "code": "de", + "name": "status", + "desc": "Der Zustand den Sie anwenden möchten." + }, + { + "code": "ja", + "name": "状態", + "desc": "適用したい状態。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "module", + "desc": "Turn on or off a module." + }, + { + "code": "fr", + "name": "module", + "desc": "Activer ou désactiver un module." + }, + { + "code": "de", + "name": "modul", + "desc": "Ein Modul ein- oder ausschalten." + }, + { + "code": "ja", + "name": "モジュール", + "desc": "モジュールをオンまたはオフにします。" + } + ] +} diff --git a/json/command/ping.json b/json/command/ping.json new file mode 100644 index 00000000..46a6580b --- /dev/null +++ b/json/command/ping.json @@ -0,0 +1,30 @@ +{ + "name": "ping", + "desc": "Get the ping of the bot (and the shard id).", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 0, + "localised": [ + { + "code": "en-US", + "name": "ping", + "desc": "Get the ping of the bot (and the shard id)." + }, + { + "code": "fr", + "name": "ping", + "desc": "Obtenez le ping du bot (et l'id du shard)." + }, + { + "code": "de", + "name": "ping", + "desc": "Holen Sie sich den Ping des Bots (und die Shard-ID)." + }, + { + "code": "ja", + "name": "ping", + "desc": "ボットのピング(およびシャードID)を取得します。" + } + ] +} \ No newline at end of file diff --git a/json/command/profile.json b/json/command/profile.json new file mode 100644 index 00000000..37681e2e --- /dev/null +++ b/json/command/profile.json @@ -0,0 +1,61 @@ +{ + "name": "profile", + "desc": "Show the profile of a user.", + "dm_command": false, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "username", + "desc": "Username of the user you want the avatar of.", + "required": false, + "autocomplete": false, + "command_type": "User", + "localised_args": [ + { + "code": "en-US", + "name": "username", + "desc": "Username of the user you want the avatar of." + }, + { + "code": "fr", + "name": "nom_dutilisateur", + "desc": "Nom d'utilisateur de l'utilisateur dont vous voulez l'avatar." + }, + { + "code": "de", + "name": "benutzername", + "desc": "Benutzername des Benutzers, dessen Avatar Sie wollen." + }, + { + "code": "ja", + "name": "ユーザー名", + "desc": "アバターを表示したいユーザーのユーザー名。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "profile", + "desc": "Show the profile of a user." + }, + { + "code": "fr", + "name": "profil", + "desc": "Afficher le profil d'un utilisateur." + }, + { + "code": "de", + "name": "profil", + "desc": "Das Profil eines Benutzers anzeigen." + }, + { + "code": "ja", + "name": "プロフィール", + "desc": "ユーザーのプロフィールを表示します。" + } + ] +} \ No newline at end of file diff --git a/json/command/random.json b/json/command/random.json new file mode 100644 index 00000000..8f3d9695 --- /dev/null +++ b/json/command/random.json @@ -0,0 +1,69 @@ +{ + "name": "random", + "desc": "Get a random manga or anime.", + "dm_command": false, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "type", + "desc": "Type of the media you want manga or anime. manga include ln atm.", + "required": true, + "autocomplete": false, + "command_type": "String", + "choices": [ + { + "option_choice": "anime" + }, + { + "option_choice": "manga" + } + ], + "localised_args": [ + { + "code": "en-US", + "name": "type", + "desc": "Type of the media you want manga or anime. manga include ln atm." + }, + { + "code": "fr", + "name": "type", + "desc": "Type de média souhaité : manga ou anime. Les mangas incluent les light novels pour le moment." + }, + { + "code": "de", + "name": "type", + "desc": "Art des Mediums, das du möchtest: Manga oder Anime. Mangas schließen derzeit Light Novels ein." + }, + { + "code": "ja", + "name": "type", + "desc": "希望するメディアの種類:マンガまたはアニメ。現在、マンガにはライトノベルが含まれています。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "random", + "desc": "Get a random manga or anime." + }, + { + "code": "fr", + "name": "random", + "desc": "Obtenez un manga ou un anime au hasard." + }, + { + "code": "de", + "name": "random", + "desc": "Erhalte einen zufälligen Manga oder Anime." + }, + { + "code": "ja", + "name": "random", + "desc": "ランダムなマンガまたはアニメを取得します。" + } + ] +} diff --git a/json/command/register.json b/json/command/register.json new file mode 100644 index 00000000..836681ee --- /dev/null +++ b/json/command/register.json @@ -0,0 +1,61 @@ +{ + "name": "register", + "desc": "Register your Anilist username for ease of use.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "username", + "desc": "Your Anilist username.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "username", + "desc": "Your Anilist username." + }, + { + "code": "fr", + "name": "username", + "desc": "Votre nom d'utilisateur Anilist." + }, + { + "code": "de", + "name": "username", + "desc": "Dein Anilist-Benutzername." + }, + { + "code": "ja", + "name": "username", + "desc": "あなたのAnilistユーザー名。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "register", + "desc": "Register your Anilist username for ease of use." + }, + { + "code": "fr", + "name": "register", + "desc": "Enregistrez votre nom d'utilisateur Anilist pour plus de facilité d'utilisation." + }, + { + "code": "de", + "name": "register", + "desc": "Registriere deinen Anilist-Benutzernamen für mehr Benutzerfreundlichkeit." + }, + { + "code": "ja", + "name": "register", + "desc": "利便性のためにAnilistユーザー名を登録してください。" + } + ] +} diff --git a/json/command/search.json b/json/command/search.json new file mode 100644 index 00000000..a3509ded --- /dev/null +++ b/json/command/search.json @@ -0,0 +1,113 @@ +{ + "name": "search", + "desc": "One command that regroups all search commands.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 2, + "args": [ + { + "name": "type", + "desc": "The type of the search you want.", + "required": true, + "autocomplete": false, + "command_type": "String", + "choices": [ + { + "option_choice": "anime" + }, + { + "option_choice": "manga" + }, + { + "option_choice": "ln" + }, + { + "option_choice": "studio" + }, + { + "option_choice": "staff" + }, + { + "option_choice": "character" + }, + { + "option_choice": "user" + } + ], + "localised_args": [ + { + "code": "en-US", + "name": "type", + "desc": "The type of the search you want." + }, + { + "code": "fr", + "name": "type", + "desc": "Le type de recherche que vous souhaitez." + }, + { + "code": "de", + "name": "type", + "desc": "Die Art der Suche, die Sie möchten." + }, + { + "code": "ja", + "name": "type", + "desc": "検索の種類。" + } + ] + }, + { + "name": "name", + "desc": "The name you want to search for.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "name", + "desc": "The name you want to search for." + }, + { + "code": "fr", + "name": "name", + "desc": "Le nom que vous souhaitez rechercher." + }, + { + "code": "de", + "name": "name", + "desc": "Der Name, den Sie suchen möchten." + }, + { + "code": "ja", + "name": "name", + "desc": "検索したい名前。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "search", + "desc": "One command that regroups all search commands." + }, + { + "code": "fr", + "name": "search", + "desc": "Une commande qui regroupe toutes les commandes de recherche." + }, + { + "code": "de", + "name": "search", + "desc": "Ein Befehl, der alle Suchbefehle zusammenfasst." + }, + { + "code": "ja", + "name": "search", + "desc": "すべての検索コマンドをまとめる1つのコマンド。" + } + ] +} diff --git a/json/command/seiyuu.json b/json/command/seiyuu.json new file mode 100644 index 00000000..2e96f8ee --- /dev/null +++ b/json/command/seiyuu.json @@ -0,0 +1,61 @@ +{ + "name": "seiyuu", + "desc": "Get an image of a seiyuu with 4 of their roles.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "seiyuu_name", + "desc": "Name of the seiyuu.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "seiyuu_name", + "desc": "Name of the seiyuu." + }, + { + "code": "fr", + "name": "seiyuu_name", + "desc": "Nom du seiyuu." + }, + { + "code": "de", + "name": "seiyuu_name", + "desc": "Name des Seiyuu." + }, + { + "code": "ja", + "name": "seiyuu_name", + "desc": "声優の名前。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "seiyuu", + "desc": "Get an image of a seiyuu with 4 of their roles." + }, + { + "code": "fr", + "name": "seiyuu", + "desc": "Obtenez une image d'un seiyuu avec 4 de ses rôles." + }, + { + "code": "de", + "name": "seiyuu", + "desc": "Erhalte ein Bild eines Seiyuu mit 4 seiner Rollen." + }, + { + "code": "ja", + "name": "seiyuu", + "desc": "4つの役割を持つ声優の画像を取得します。" + } + ] +} diff --git a/json/command/staff.json b/json/command/staff.json new file mode 100644 index 00000000..6c1220c2 --- /dev/null +++ b/json/command/staff.json @@ -0,0 +1,61 @@ +{ + "name": "staff", + "desc": "Get info of a staff.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "staff_name", + "desc": "Name of the staff you want info about.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "staff_name", + "desc": "Name of the staff you want info about." + }, + { + "code": "fr", + "name": "nom_du_personnel", + "desc": "Nom du personnel dont vous souhaitez obtenir des informations." + }, + { + "code": "de", + "name": "mitarbeiter_name", + "desc": "Name des Personals, über das Sie Informationen erhalten möchten." + }, + { + "code": "ja", + "name": "スタッフ名", + "desc": "情報を取得したいスタッフの名前。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "staff", + "desc": "Get info of a staff." + }, + { + "code": "fr", + "name": "personnel", + "desc": "Obtenir des informations sur un membre du personnel." + }, + { + "code": "de", + "name": "mitarbeiter", + "desc": "Informationen über ein Mitarbeiter erhalten." + }, + { + "code": "ja", + "name": "スタッフ", + "desc": "スタッフの情報を取得します。" + } + ] +} diff --git a/json/command/studio.json b/json/command/studio.json new file mode 100644 index 00000000..1cda5e90 --- /dev/null +++ b/json/command/studio.json @@ -0,0 +1,61 @@ +{ + "name": "studio", + "desc": "Get info on a studio.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "studio", + "desc": "The name of the studio.", + "required": true, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "studio", + "desc": "The name of the studio." + }, + { + "code": "fr", + "name": "studio", + "desc": "Le nom du studio." + }, + { + "code": "de", + "name": "studio", + "desc": "Der Name des Studios." + }, + { + "code": "ja", + "name": "studio", + "desc": "スタジオの名前。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "studio", + "desc": "Get info on a studio." + }, + { + "code": "fr", + "name": "studio", + "desc": "Obtenez des informations sur un studio." + }, + { + "code": "de", + "name": "studio", + "desc": "Erhalte Informationen über ein Studio." + }, + { + "code": "ja", + "name": "studio", + "desc": "スタジオの情報を取得します。" + } + ] +} diff --git a/json/command/tranlation.json b/json/command/tranlation.json new file mode 100644 index 00000000..0783a9c5 --- /dev/null +++ b/json/command/tranlation.json @@ -0,0 +1,90 @@ +{ + "name": "translation", + "desc": "Generate a translation.", + "dm_command": false, + "nsfw": false, + "perm": false, + "arg_num": 2, + "args": [ + { + "name": "video", + "desc": "Upload video file (max. 25MB).", + "required": true, + "autocomplete": false, + "command_type": "Attachment", + "localised_args": [ + { + "code": "en-US", + "name": "video", + "desc": "Upload video file (max. 25MB)." + }, + { + "code": "fr", + "name": "video", + "desc": "Télécharger le fichier vidéo (max. 25MB)." + }, + { + "code": "de", + "name": "video", + "desc": "Video-Datei hochladen (max. 25MB)." + }, + { + "code": "ja", + "name": "video", + "desc": "ビデオファイルをアップロード(最大25MB)。" + } + ] + }, + { + "name": "lang", + "desc": "Select input language (ISO-639-1)", + "required": false, + "autocomplete": false, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "lang", + "desc": "Select input language (ISO-639-1)" + }, + { + "code": "fr", + "name": "lang", + "desc": "Sélectionnez la langue d'entrée (ISO-639-1)" + }, + { + "code": "de", + "name": "lang", + "desc": "Eingabesprache auswählen (ISO-639-1)" + }, + { + "code": "ja", + "name": "lang", + "desc": "入力言語を選択(ISO-639-1)" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "translation", + "desc": "Generate a translation." + }, + { + "code": "fr", + "name": "traduction", + "desc": "Générer une traduction." + }, + { + "code": "de", + "name": "ubersetzung", + "desc": "Erzeugen Sie eine Übersetzung." + }, + { + "code": "ja", + "name": "honyaku", + "desc": "翻訳を生成する。" + } + ] +} \ No newline at end of file diff --git a/json/command/transcript.json b/json/command/transcript.json new file mode 100644 index 00000000..5427e60a --- /dev/null +++ b/json/command/transcript.json @@ -0,0 +1,119 @@ +{ + "name": "transcript", + "desc": "Generate a transcript from a video.", + "dm_command": false, + "nsfw": false, + "perm": false, + "arg_num": 3, + "args": [ + { + "name": "video", + "desc": "Upload video file (max. 25MB).", + "required": true, + "autocomplete": false, + "command_type": "Attachment", + "localised_args": [ + { + "code": "en-US", + "name": "video", + "desc": "Upload video file (max. 25MB)." + }, + { + "code": "fr", + "name": "video", + "desc": "Télécharger le fichier vidéo (max. 25MB)." + }, + { + "code": "de", + "name": "video", + "desc": "Video-Datei hochladen (max. 25MB)." + }, + { + "code": "ja", + "name": "video", + "desc": "ビデオファイルをアップロード(最大25MB)。" + } + ] + }, + { + "name": "prompt", + "desc": "A guide text for audio style. Must match the audio language.", + "required": false, + "autocomplete": false, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "prompt", + "desc": "A guide text for audio style. Must match the audio language." + }, + { + "code": "fr", + "name": "invite", + "desc": "Un texte de guide pour le style audio. Doit correspondre à la langue de l'audio." + }, + { + "code": "de", + "name": "auffordern", + "desc": "Ein Leittext für den Audio-Stil. Muss der Audiosprache entsprechen." + }, + { + "code": "ja", + "name": "プロンプト", + "desc": "オーディオスタイルのガイドテキスト。オーディオの言語と一致する必要があります。" + } + ] + }, + { + "name": "lang", + "desc": "Select input language (ISO-639-1)", + "required": false, + "autocomplete": false, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "lang", + "desc": "Select input language (ISO-639-1)" + }, + { + "code": "fr", + "name": "lang", + "desc": "Sélectionnez la langue d'entrée (ISO-639-1)" + }, + { + "code": "de", + "name": "lang", + "desc": "Eingabesprache auswählen (ISO-639-1)" + }, + { + "code": "ja", + "name": "lang", + "desc": "入力言語を選択(ISO-639-1)" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "transcript", + "desc": "Generate a transcript from a video." + }, + { + "code": "fr", + "name": "transcription", + "desc": "Générer une transcription à partir d'une vidéo." + }, + { + "code": "de", + "name": "transkript", + "desc": "Erstellt ein Transkript aus einem Video." + }, + { + "code": "ja", + "name": "トランスクリプト", + "desc": "動画から脚本を生成する。" + } + ] +} \ No newline at end of file diff --git a/json/command/user.json b/json/command/user.json new file mode 100644 index 00000000..d0d62280 --- /dev/null +++ b/json/command/user.json @@ -0,0 +1,61 @@ +{ + "name": "user", + "desc": "Info of an anilist user.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 1, + "args": [ + { + "name": "username", + "desc": "Username of the anilist user you want to check.", + "required": false, + "autocomplete": true, + "command_type": "String", + "localised_args": [ + { + "code": "en-US", + "name": "username", + "desc": "Username of the anilist user you want to check." + }, + { + "code": "fr", + "name": "nom_d_utilisateur", + "desc": "Nom d'utilisateur du utilisateur d'Anilist que vous souhaitez vérifier." + }, + { + "code": "de", + "name": "benutzername", + "desc": "Benutzername des Anilist-Benutzers, den Sie prüfen möchten." + }, + { + "code": "ja", + "name": "ユーザーネーム", + "desc": "チェックしたいアニリストユーザーのユーザーネーム。" + } + ] + } + ], + "localised": [ + { + "code": "en-US", + "name": "user", + "desc": "Info of an anilist user." + }, + { + "code": "fr", + "name": "utilisateur", + "desc": "Informations sur un utilisateur d'Anilist." + }, + { + "code": "de", + "name": "benutzer", + "desc": "Informationen zu einem Anilist-Benutzer." + }, + { + "code": "ja", + "name": "ユーザー", + "desc": "アニリストユーザーの情報。" + } + ] +} \ No newline at end of file diff --git a/json/command/waifu.json b/json/command/waifu.json new file mode 100644 index 00000000..78bf89ee --- /dev/null +++ b/json/command/waifu.json @@ -0,0 +1,30 @@ +{ + "name": "waifu", + "desc": "Give you the best waifu.", + "dm_command": true, + "nsfw": false, + "perm": false, + "arg_num": 0, + "localised": [ + { + "code": "en-US", + "name": "waifu", + "desc": "Give you the best waifu." + }, + { + "code": "fr", + "name": "waifu", + "desc": "Donner la meilleure waifu." + }, + { + "code": "de", + "name": "waifu", + "desc": "Die beste Waifu für dich." + }, + { + "code": "ja", + "name": "ワイフ", + "desc": "最高のワイフを提供します。" + } + ] +} \ No newline at end of file diff --git a/json/message/ai/image.json b/json/message/ai/image.json new file mode 100644 index 00000000..31dc02d3 --- /dev/null +++ b/json/message/ai/image.json @@ -0,0 +1,14 @@ +{ + "en": { + "title": "Here's your generated image." + }, + "fr": { + "title": "Voici votre image générée." + }, + "jp": { + "title": "これが生成された画像です。" + }, + "de": { + "title": "Hier ist Ihr generiertes Bild." + } +} \ No newline at end of file diff --git a/json/message/ai/transcript.json b/json/message/ai/transcript.json new file mode 100644 index 00000000..133a9f5e --- /dev/null +++ b/json/message/ai/transcript.json @@ -0,0 +1,14 @@ +{ + "en": { + "title": "Here's your transcript." + }, + "fr": { + "title": "Voici votre transcription." + }, + "jp": { + "title": "ここにあなたの成績証明書があります。" + }, + "de": { + "title": "Hier ist Ihre Abschrift." + } +} \ No newline at end of file diff --git a/json/message/ai/translation.json b/json/message/ai/translation.json new file mode 100644 index 00000000..077c483e --- /dev/null +++ b/json/message/ai/translation.json @@ -0,0 +1,14 @@ +{ + "en": { + "title": "Here's your translation." + }, + "fr": { + "title": "Voici votre traduction." + }, + "jp": { + "title": "これがあなたの翻訳です。" + }, + "de": { + "title": "Hier ist Ihre Übersetzung." + } +} \ No newline at end of file diff --git a/json/message/anilist/add_activity.json b/json/message/anilist/add_activity.json new file mode 100644 index 00000000..bd169218 --- /dev/null +++ b/json/message/anilist/add_activity.json @@ -0,0 +1,26 @@ +{ + "en": { + "success": "Activity added Successfully", + "fail": "Activity already Exist", + "fail_desc": "This activity already exists. You should already have an activity for: $anime$.", + "success_desc": "The activity was added successfully for: $anime$." + }, + "fr": { + "success": "Activité ajoutée avec succès", + "fail": "L'activité existe déjà", + "fail_desc": "Cette activité existe déjà. Vous devriez déjà avoir une activité pour : $anime$.", + "success_desc": "L'activité a été ajoutée avec succès pour : $anime$." + }, + "jp": { + "success": "アクティビティーが成功裏に追加されました", + "fail": "アクティビティーはすでに存在します", + "fail_desc": "このアクティビティーはすでに存在します。$anime$のアクティビティーはすでにあるはずです。", + "success_desc": "$anime$のアクティビティーが成功裏に追加されました。" + }, + "de": { + "success": "Aktivität erfolgreich hinzugefügt", + "fail": "Aktivität existiert bereits", + "fail_desc": "Diese Aktivität existiert bereits. Sie sollten bereits eine Aktivität für haben: $anime$.", + "success_desc": "Die Aktivität wurde erfolgreich hinzugefügt für: $anime$." + } +} \ No newline at end of file diff --git a/json/message/anilist/character.json b/json/message/anilist/character.json new file mode 100644 index 00000000..9e56006d --- /dev/null +++ b/json/message/anilist/character.json @@ -0,0 +1,18 @@ +{ + "en": { + "desc": "Age: $age$. \n Gender: $gender$. $date_of_birth$ \n Number of Favorites: $fav$. \n\n Description: \n $desc$", + "date_of_birth": "\n Date of Birth: $date$." + }, + "fr": { + "desc": "Age: $age$. \n Genre: $gender$. \n $date_of_birth$ \n Nombre de favoris: $fav$. \n\n Description: \n $desc$", + "date_of_birth": "Date de naissance: $date$." + }, + "de": { + "desc": "Alter: $age$. \n Geschlecht: $gender$. \n $date_of_birth$ \n Anzahl der Favoriten: $fav$. \n\n Beschreibung: \n $desc$", + "date_of_birth": "Geburtsdatum: $date$." + }, + "jp": { + "desc": "せいねん: $age$. \n せいべつ: $gender$. \n $date_of_birth$ \n おきにいりすう: $fav$. \n\n せつめい: \n $desc$", + "date_of_birth": "しょうねんつきび: $date$." + } +} \ No newline at end of file diff --git a/json/message/anilist/compare.json b/json/message/anilist/compare.json new file mode 100644 index 00000000..228b11e3 --- /dev/null +++ b/json/message/anilist/compare.json @@ -0,0 +1,74 @@ +{ + "en": { + "more_anime": "$greater$ as watched more anime than $lesser$\n", + "same_anime": "both $1$ and $2$ as the same amount of anime watched\n", + "more_watch_time": "$greater$ as more watch time than $lesser$\n", + "same_watch_time": "both $1$ and $2$ watched anime for the same amount of time\n", + "genre_anime": "$1$ prefer the $1a$ genre and $2$ prefer the $2a$ genre for anime\n", + "same_genre_anime": "both $1$ and $2$ prefer the $1a$ genre for anime\n", + "tag_anime": "$1$ prefer the $1a$ tag and $2$ prefer the $2a$ tag for anime\n", + "same_tag_anime": "both $1$ and $2$ prefer the $1a$ tag for anime\n", + "more_manga": "$greater$ as read more manga than $lesser$\n", + "same_manga": "both $1$ and $2$ as the same amount of manga read\n", + "genre_manga": "$1$ prefer the $1a$ genre and $2$ prefer the $2a$ genre for manga\n", + "same_genre_manga": "both $1$ and $2$ prefer the $1a$ genre for manga\n", + "tag_manga": "$1$ prefer the $1a$ tag and $2$ prefer the $2a$ tag for manga\n", + "same_tag_manga": "both $1$ and $2$ prefer the $1a$ tag for manga\n", + "more_manga_chapter": "$greater$ as read more chapters than $lesser$\n", + "same_manga_chapter": "both $1$ and $2$ as the same amount of chapters read\n" + }, + "fr": { + "more_anime": "$greater$ a regardé plus d'animes que $lesser$\n", + "same_anime": "à la fois $1$ et $2$ ont regardé le même nombre d'animes\n", + "more_watch_time": "$greater$ a plus de temps de visionnage que $lesser$\n", + "same_watch_time": "à la fois $1$ et $2$ ont regardé des animes pendant le même laps de temps\n", + "genre_anime": "$1$ préfère le genre $1a$ et $2$ préfère le genre $2a$ pour les animes\n", + "same_genre_anime": "à la fois $1$ et $2$ préfèrent le genre $1a$ pour les animes\n", + "tag_anime": "$1$ préfère la balise $1a$ et $2$ préfère la balise $2a$ pour les animes\n", + "same_tag_anime": "à la fois $1$ et $2$ préfèrent la balise $1a$ pour les animes\n", + "more_manga": "$greater$ a lu plus de mangas que $lesser$\n", + "same_manga": "à la fois $1$ et $2$ ont lu le même nombre de mangas\n", + "genre_manga": "$1$ préfère le genre $1a$ et $2$ préfère le genre $2a$ pour les mangas\n", + "same_genre_manga": "à la fois $1$ et $2$ préfèrent le genre $1a$ pour les mangas\n", + "tag_manga": "$1$ préfère la balise $1a$ et $2$ préfère la balise $2a$ pour les mangas\n", + "same_tag_manga": "à la fois $1$ et $2$ préfèrent la balise $1a$ pour les mangas\n", + "more_manga_chapter": "$greater$ a lu plus de chapitres que $lesser$\n", + "same_manga_chapter": "à la fois $1$ et $2$ ont lu le même nombre de chapitres\n" + }, + "jp": { + "more_anime": "$greater$は$lesser$よりもアニメをより多く見ました\n", + "same_anime": "$1$と$2$は同じ量のアニメを見ました\n", + "more_watch_time": "$greater$は$lesser$よりも視聴時間が長いです\n", + "same_watch_time": "$1$と$2$は同じ時間だけアニメを見ました\n", + "genre_anime": "$1$はアニメのジャンル$1a$を好み、$2$はアニメのジャンル$2a$を好みます\n", + "same_genre_anime": "$1$と$2$はアニメのジャンル$1a$を好みます\n", + "tag_anime": "$1$はアニメのタグ$1a$を好み、$2$はアニメのタグ$2a$を好みます\n", + "same_tag_anime": "$1$と$2$はアニメのタグ$1a$を好みます\n", + "more_manga": "$greater$は$lesser$よりもマンガをより多く読みました\n", + "same_manga": "$1$と$2$は同じ量のマンガを読みました\n", + "genre_manga": "$1$はマンガのジャンル$1a$を好み、$2$はマンガのジャンル$2a$を好みます\n", + "same_genre_manga": "$1$と$2$はマンガのジャンル$1a$を好みます\n", + "tag_manga": "$1$はマンガのタグ$1a$を好み、$2$はマンガのタグ$2a$を好みます\n", + "same_tag_manga": "$1$と$2$はマンガのタグ$1a$を好みます\n", + "more_manga_chapter": "$greater$は$lesser$よりも章をより多く読みました\n", + "same_manga_chapter": "$1$と$2$は同じ量の章を読みました\n" + }, + "de": { + "more_anime": "$greater$ hat mehr Anime geschaut als $lesser$\n", + "same_anime": "sowohl $1$ als auch $2$ haben die gleiche Menge Anime geschaut\n", + "more_watch_time": "$greater$ hat mehr Anime-Zeit als $lesser$\n", + "same_watch_time": "sowohl $1$ als auch $2$ haben Anime für die gleiche Zeit geschaut\n", + "genre_anime": "$1$ bevorzugt das Genre $1a$ und $2$ bevorzugt das Genre $2a$ für Anime\n", + "same_genre_anime": "sowohl $1$ als auch $2$ bevorzugen das Genre $1a$ für Anime\n", + "tag_anime": "$1$ bevorzugt das Tag $1a$ und $2$ bevorzugt das Tag $2a$ für Anime\n", + "same_tag_anime": "sowohl $1$ als auch $2$ bevorzugen das Tag $1a$ für Anime\n", + "more_manga": "$greater$ hat mehr Manga gelesen als $lesser$\n", + "same_manga": "sowohl $1$ als auch $2$ haben die gleiche Menge Manga gelesen\n", + "genre_manga": "$1$ bevorzugt das Genre $1a$ und $2$ bevorzugt das Genre $2a$ für Manga\n", + "same_genre_manga": "sowohl $1$ als auch $2$ bevorzugen das Genre $1a$ für Manga\n", + "tag_manga": "$1$ bevorzugt das Tag $1a$ und $2$ bevorzugt das Tag $2a$ für Manga\n", + "same_tag_manga": "sowohl $1$ als auch $2$ bevorzugen das Tag $1a$ für Manga\n", + "more_manga_chapter": "$greater$ hat mehr Kapitel gelesen als $lesser$\n", + "same_manga_chapter": "sowohl $1$ als auch $2$ haben die gleiche Menge Kapitel gelesen\n" + } +} diff --git a/json/message/anilist/level.json b/json/message/anilist/level.json new file mode 100644 index 00000000..f746b879 --- /dev/null +++ b/json/message/anilist/level.json @@ -0,0 +1,14 @@ +{ + "en": { + "desc": "User $username$ level is $level$ \n with a total of $xp$ \n and is $actual$ / $next$ to the next level" + }, + "fr": { + "desc": "Le niveau de l'utilisateur $username$ est $level$ \n avec un total de $xp$ \n et il est à $actual$ / $next$ du niveau suivant" + }, + "jp": { + "desc": "ユーザー$username$のレベルは$level$で、合計$xp$あり、次のレベルまで$actual$ / $next$です" + }, + "de": { + "desc": "Der Benutzer $username$ hat Level $level$ \n mit insgesamt $xp$ \n und ist $actual$ / $next$ vom nächsten Level entfernt" + } +} diff --git a/json/message/anilist/media.json b/json/message/anilist/media.json new file mode 100644 index 00000000..6f55bdd4 --- /dev/null +++ b/json/message/anilist/media.json @@ -0,0 +1,26 @@ +{ + "en": { + "field1_title": "Genre", + "field2_title": "Tag", + "desc": "**Info** \n Format: $format$ / Source: $source$ \n Start date: $start_date$ \n End date: $end_date$ \n \n **Staff** \n $staff_list$", + "staff_text": "Name: $name$ with the role $role$\n" + }, + "fr": { + "field1_title": "Genre", + "field2_title": "Tag", + "desc": "**Info** \n Format: $format$ / Source: $source$ \n Date de début: $start_date$ \n Date de fin: $end_date$ \n\n **Équipe** \n $staff_list$", + "staff_text": "Nom: $name$ avec le rôle $role$" + }, + "jp": { + "field1_title": "ジャンル", + "field2_title": "タグ", + "desc": "**情報** \n 形式: $format$ / 情報源: $source$ \n 開始日: $start_date$ \n 終了日: $end_date$ \n\n **スタッフ** \n $staff_list$", + "staff_text": "名前: $name$ の役割 $role$" + }, + "de": { + "field1_title": "Genre", + "field2_title": "Tag", + "desc": "**Info** \n Format: $format$ / Quelle: $source$ \n Startdatum: $start_date$ \n Enddatum: $end_date$ \n\n **Personal** \n $staff_list$", + "staff_text": "Name: $name$ mit der Rolle $role$" + } +} diff --git a/json/message/anilist/random.json b/json/message/anilist/random.json new file mode 100644 index 00000000..fe37da65 --- /dev/null +++ b/json/message/anilist/random.json @@ -0,0 +1,14 @@ +{ + "en": { + "desc": "**Info** \n Format: $format$ \n Tags: $tags$\n Genres: $genres$\n \n Description: $desc$" + }, + "fr": { + "desc": "**Info** \n Format: $format$ \n Tags: $tags$\n Genres: $genres$\n \n Description: $desc$" + }, + "jp": { + "desc": "**情報** \n フォーマット: $format$ \n タグ: $tags$\n ジャンル: $genres$\n \n 説明: $desc$" + }, + "de": { + "desc": "**Info** \n Format: $format$ \n Tags: $tags$\n Genres: $genres$\n \n Beschreibung: $desc$" + } +} diff --git a/json/message/anilist/register.json b/json/message/anilist/register.json new file mode 100644 index 00000000..6b1a7cab --- /dev/null +++ b/json/message/anilist/register.json @@ -0,0 +1,14 @@ +{ + "en": { + "desc": "The user $user$ (with the id $id$) was linked with the Anilist account $anilist$" + }, + "fr": { + "desc": "L'utilisateur $user$ (avec l'identifiant $id$) a été lié au compte Anilist $anilist$" + }, + "jp": { + "desc": "ユーザー $user$(ID:$id$)はAnilistアカウント $anilist$ とリンクされました" + }, + "de": { + "desc": "Der Benutzer $user$ (mit der ID $id$) wurde mit dem Anilist-Konto $anilist$ verknüpft" + } +} diff --git a/json/message/anilist/seiyuu.json b/json/message/anilist/seiyuu.json new file mode 100644 index 00000000..4d20687c --- /dev/null +++ b/json/message/anilist/seiyuu.json @@ -0,0 +1,14 @@ +{ + "en": { + "title": "seiyuu" + }, + "fr": { + "title": "seiyuu" + }, + "jp": { + "title": "seiyuu" + }, + "de": { + "title": "seiyuu" + } +} \ No newline at end of file diff --git a/json/message/anilist/send_activity.json b/json/message/anilist/send_activity.json new file mode 100644 index 00000000..50cd21f8 --- /dev/null +++ b/json/message/anilist/send_activity.json @@ -0,0 +1,18 @@ +{ + "en": { + "title": "New Episode", + "desc": "Episode $ep$ of $anime$ just released." + }, + "fr": { + "title": "Nouvel Épisode", + "desc": "Épisode $ep$ de $anime$ vient de sortir." + }, + "jp": { + "title": "新エピソード", + "desc": "$anime$のエピソード$ep$がリリースされました。" + }, + "de": { + "title": "Neue Folge", + "desc": "Folge $ep$ von $anime$ wurde gerade veröffentlicht." + } +} \ No newline at end of file diff --git a/json/message/anilist/staff.json b/json/message/anilist/staff.json new file mode 100644 index 00000000..7c0225bd --- /dev/null +++ b/json/message/anilist/staff.json @@ -0,0 +1,30 @@ +{ + "en": { + "field1_title": "Media", + "field2_title": "VA", + "desc": "**Info** $dob$ $dod$ \n Occupation: $job$ \n Gender: $gender$ \n Age: $age$", + "date_of_birth": "\n Date of Birth: $date$.", + "date_of_death": "\n Date of death: $date$." + }, + "fr": { + "field1_title": "Média", + "field2_title": "Doubleur", + "desc": "**Info** $dob$ $dod$ \n Occupation : $job$ \n Genre : $gender$ \n Âge : $age$", + "date_of_birth": "\n Date de naissance : $date$.", + "date_of_death": "\n Date de décès : $date$." + }, + "jp": { + "field1_title": "メディア", + "field2_title": "声優", + "desc": "**情報** $dob$ $dod$ \n 職業: $job$ \n 性別: $gender$ \n 年齢: $age$", + "date_of_birth": "\n 生年月日: $date$.", + "date_of_death": "\n 死亡日: $date$." + }, + "de": { + "field1_title": "Medien", + "field2_title": "Synchronsprecher", + "desc": "**Info** $dob$ $dod$ \n Beruf: $job$ \n Geschlecht: $gender$ \n Alter: $age$", + "date_of_birth": "\n Geburtsdatum: $date$.", + "date_of_death": "\n Todesdatum: $date$." + } +} diff --git a/json/message/anilist/studio.json b/json/message/anilist/studio.json new file mode 100644 index 00000000..21a9857b --- /dev/null +++ b/json/message/anilist/studio.json @@ -0,0 +1,14 @@ +{ + "en": { + "desc": "Id: $id$ \n Favorite: $fav$ \n Is animation studio: $animation$ \n $list$" + }, + "fr": { + "desc": "Identifiant : $id$ \n Favori : $fav$ \n Est un studio d'animation : $animation$ \n $list$" + }, + "jp": { + "desc": "ID: $id$ \n お気に入り: $fav$ \n アニメーションスタジオかどうか: $animation$ \n $list$" + }, + "de": { + "desc": "ID: $id$ \n Favorit: $fav$ \n Ist ein Animationsstudio: $animation$ \n $list$" + } +} diff --git a/json/message/anilist/user.json b/json/message/anilist/user.json new file mode 100644 index 00000000..12f8ef81 --- /dev/null +++ b/json/message/anilist/user.json @@ -0,0 +1,50 @@ +{ + "en": { + "manga": "**[Manga]($url$)** \n $count$ read and $complete$ completed \n $chap$ chapter read \n with a mean score of $score$ and a standard deviation of $sd$ \n here a list of his preferred tag (by mean score): \n $tag_list$ \n and here for the genre: \n $genre_list$", + "anime": "**[Anime]($url$)** \n $count$ watch and $complete$ completed \n Time watched: $duration$ \n with a mean score of $score$ and a standard deviation of $sd$ \n here a list of his preferred tag (by mean score): \n $tag_list$ \n and here for the genre: \n $genre_list$", + "week": "week : ", + "day": "day : ", + "hour": "hour : ", + "minute": "minute", + "weeks": "weeks : ", + "days": "days : ", + "hours": "hours : ", + "minutes": "minutes" + }, + "fr": { + "manga": "**[Manga]($url$)** \n $count$ lu et $complete$ terminé \n $chap$ chapitre lu \n avec une note moyenne de $score$ et un écart type de $sd$ \n voici une liste de ses tags préférés (par note moyenne) : \n $tag_list$ \n et ici pour le genre : \n $genre_list$", + "anime": "**[Anime]($url$)** \n $count$ regardé et $complete$ terminé \n Durée regardée: $duration$ \n avec une note moyenne de $score$ et un écart type de $sd$ \n voici une liste de ses tags préférés (par note moyenne) : \n $tag_list$ \n et ici pour le genre : \n $genre_list$", + "week": "semaine : ", + "day": "jour : ", + "hour": "heure : ", + "minute": "minute", + "weeks": "semaines : ", + "days": "jours : ", + "hours": "heures : ", + "minutes": "minutes" + }, + "jp": { + "manga": "**[マンガ]($url$)** \n $count$ 読んで、$complete$ 完了しました \n $chap$ 章を読んだ \n 平均スコアは $score$ 、標準偏差は $sd$ です \n 以下は彼の好きなタグのリストです(平均スコアによる): \n $tag_list$ \n そして、ここにジャンルがあります: \n $genre_list$", + "anime": "**[アニメ]($url$)** \n $count$ を観て、$complete$ 完了しました \n 視聴時間: $duration$ \n 平均スコアは $score$ 、標準偏差は $sd$ です \n 以下は彼の好きなタグのリストです(平均スコアによる): \n $tag_list$ \n そして、ここにジャンルがあります: \n $genre_list$", + "week": "週 : ", + "day": "日 : ", + "hour": "時間 : ", + "minute": "分", + "weeks": "週間 : ", + "days": "日々 : ", + "hours": "時間 : ", + "minutes": "分" + }, + "de": { + "manga": "**[Manga]($url$)** \n $count$ gelesen und $complete$ abgeschlossen \n $chap$ Kapitel gelesen \n Mit einem Durchschnittswert von $score$ und einer Standardabweichung von $sd$ \n Hier eine Liste seiner bevorzugten Tags (nach Durchschnittswert): \n $tag_list$ \n und hier für das Genre: \n $genre_list$", + "anime": "**[Anime]($url$)** \n $count$ gesehen und $complete$ abgeschlossen \n Gesehene Dauer: $duration$ \n Mit einem Durchschnittswert von $score$ und einer Standardabweichung von $sd$ \n Hier eine Liste seiner bevorzugten Tags (nach Durchschnittswert): \n $tag_list$ \n und hier für das Genre: \n $genre_list$", + "week": "Woche : ", + "day": "Tag : ", + "hour": "Stunde : ", + "minute": "Minute", + "weeks": "Wochen : ", + "days": "Tage : ", + "hours": "Stunden : ", + "minutes": "Minuten" + } +} \ No newline at end of file diff --git a/json/message/general/avatar.json b/json/message/general/avatar.json new file mode 100644 index 00000000..38486dc4 --- /dev/null +++ b/json/message/general/avatar.json @@ -0,0 +1,14 @@ +{ + "en": { + "title": "Here's the avatar of $user$" + }, + "fr": { + "title": "Voici l'avatar de $user$" + }, + "jp": { + "title": "$user$のアバターはこちらです" + }, + "de": { + "title": "Hier ist der Avatar von $user$" + } +} \ No newline at end of file diff --git a/json/message/general/banner.json b/json/message/general/banner.json new file mode 100644 index 00000000..00baed7f --- /dev/null +++ b/json/message/general/banner.json @@ -0,0 +1,22 @@ +{ + "en": { + "title": "Here's the banner of $user$", + "no_banner": "$user$ does not have a banner", + "no_banner_title": "No banner" + }, + "fr": { + "title": "Voici la bannière de $user$", + "no_banner": "$user$ n'a pas de bannière", + "no_banner_title": "Pas de bannière" + }, + "jp": { + "title": "$user$のバナーです", + "no_banner": "$user$にはバナーがありません", + "no_banner_title": "バナーなし" + }, + "de": { + "title": "Hier ist das Banner von $user$", + "no_banner": "$user$ hat kein Banner", + "no_banner_title": "Kein Banner" + } +} \ No newline at end of file diff --git a/json/message/general/credit.json b/json/message/general/credit.json new file mode 100644 index 00000000..ae4e3276 --- /dev/null +++ b/json/message/general/credit.json @@ -0,0 +1,46 @@ +{ + "en": { + "title": "Credit", + "credits": [ + { + "desc": "valgul: Main dev / original creator / French and English translation \n" + }, + { + "desc": "Sionnakh: Dev of the website \n" + } + ] + }, + "fr": { + "title": "Crédit", + "credits": [ + { + "desc": "valgul: Développeur principal / créateur original / traduction française et anglaise \n" + }, + { + "desc": "Sionnakh: Développeur du site web \n" + } + ] + }, + "jp": { + "title": "クレジット", + "credits": [ + { + "desc": "valgul: メイン開発者 / 元のクリエーター / フランス語と英語の翻訳 \n" + }, + { + "desc": "Sionnakh: ウェブサイトの開発者 \n" + } + ] + }, + "de": { + "title": "Kredit", + "credits": [ + { + "desc": "valgul: Hauptentwickler / ursprünglicher Schöpfer / französische und englische Übersetzung \n" + }, + { + "desc": "Sionnakh: Webseiten-Entwickler \n" + } + ] + } +} \ No newline at end of file diff --git a/json/message/general/info.json b/json/message/general/info.json new file mode 100644 index 00000000..e09522b5 --- /dev/null +++ b/json/message/general/info.json @@ -0,0 +1,38 @@ +{ + "en": { + "title": "Info", + "desc": "This bot uses the AniList API to provide information on a show or a user.", + "footer": "Creator: valgul.", + "button_see_on_github": "See on GitHub", + "button_official_website": "Official website", + "button_official_discord": "Official Discord", + "button_add_the_bot": "Add the bot" + }, + "fr": { + "title": "Info", + "desc": "Ce bot utilise l'API AniList pour fournir des informations sur une émission ou un utilisateur.", + "footer": "Créateur: valgul.", + "button_see_on_github": "Voir sur GitHub", + "button_official_website": "Site officiel", + "button_official_discord": "Discord officiel", + "button_add_the_bot": "Ajouter le bot" + }, + "jp": { + "title": "情報", + "desc": "このボットはAniList APIを使用して、ショーまたはユーザーの情報を提供します。", + "footer": "作成者: valgul.", + "button_see_on_github": "GitHubで見る", + "button_official_website": "公式ウェブサイト", + "button_official_discord": "公式Discord", + "button_add_the_bot": "ボットを追加する" + }, + "de": { + "title": "Info", + "desc": "Dieser Bot verwendet die AniList API, um Informationen über eine Show oder einen Benutzer bereitzustellen.", + "footer": "Ersteller: valgul.", + "button_see_on_github": "Auf GitHub anzeigen", + "button_official_website": "Offizielle Webseite", + "button_official_discord": "Offizielle Discord", + "button_add_the_bot": "Den Bot hinzufügen" + } +} \ No newline at end of file diff --git a/json/message/general/lang.json b/json/message/general/lang.json new file mode 100644 index 00000000..5d438842 --- /dev/null +++ b/json/message/general/lang.json @@ -0,0 +1,18 @@ +{ + "en": { + "title": "Lang", + "desc": "The bot language was set to: $lang$" + }, + "fr": { + "title": "Langue", + "desc": "La langue du bot a été définie à: $lang$" + }, + "jp": { + "title": "言語", + "desc": "ボットの言語が設定されました: $lang$" + }, + "de": { + "title": "Sprache", + "desc": "Die Bot-Sprache wurde auf eingestellt: $lang$" + } +} \ No newline at end of file diff --git a/json/message/general/module.json b/json/message/general/module.json new file mode 100644 index 00000000..2240ebf3 --- /dev/null +++ b/json/message/general/module.json @@ -0,0 +1,18 @@ +{ + "en": { + "on": "The module has been activated on the server.", + "off": "The module has been deactivated on the server." + }, + "fr": { + "on": "Le module a été activé sur le serveur.", + "off": "Le module a été désactivé sur le serveur." + }, + "jp": { + "on": "モジュールがサーバーで有効化されました。", + "off": "サーバーでモジュールが無効化されました。" + }, + "de": { + "on": "Das Modul wurde auf dem Server aktiviert.", + "off": "Das Modul wurde auf dem Server deaktiviert." + } +} \ No newline at end of file diff --git a/json/message/general/ping.json b/json/message/general/ping.json new file mode 100644 index 00000000..166448f1 --- /dev/null +++ b/json/message/general/ping.json @@ -0,0 +1,18 @@ +{ + "en": { + "title": "Ping", + "desc": "Hey, I'm alive! \n You are running on shard: $shard$. \n Latency is: $latency$. \n The shard status is $status$." + }, + "fr": { + "title": "Ping", + "desc": "Salut, je suis en vie! \n Vous fonctionnez sur le shard: $shard$. \n La latence est de: $latency$. \n Le statut du shard est $status$." + }, + "jp": { + "title": "Ping", + "desc": "ねえ、私は生きています! \n シャードに実行中です: $shard$. \n レイテンシは: $latency$. \n シャードのステータスは $status$ 。" + }, + "de": { + "title": "Ping", + "desc": "Hallo, ich lebe! \n Sie laufen auf Shard: $shard$. \n Die Latenz beträgt: $latency$. \n Der Shard-Status ist $status$." + } +} \ No newline at end of file diff --git a/json/message/general/profile.json b/json/message/general/profile.json new file mode 100644 index 00000000..4527882c --- /dev/null +++ b/json/message/general/profile.json @@ -0,0 +1,18 @@ +{ + "en": { + "title": "Here's the profile of $user$", + "desc": "The user id of $user$ is $id$. \n Here the account creation date $creation_date$. \n Here when the user joined $joined_date$. \n Is this user a bot? $bot$. \n Here all the public flag of the user $public_flag$. \n Here the nitro type of the user $nitro$. \n Is the user system? $system$" + }, + "fr": { + "title": "Voici le profil de $user$", + "desc": "L'identifiant utilisateur de $user$ est $id$. \n Voici la date de création du compte $creation_date$. \n Voici la date d'entrée de l'utilisateur $joined_date$. \n Cet utilisateur est-il un bot? $bot$. \n Voici tous les drapeaux publics de l'utilisateur $public_flag$." + }, + "jp": { + "title": "$user$のプロフィールはこちら", + "desc": "$user$のユーザーIDは$id$です。 \n アカウント作成日$creation_date$です。 \n ユーザーが入会したのは$joined_date$です。 \n このユーザーはボットですか? $bot$。 \n ユーザーの全ての公開フラグは$public_flag$です。" + }, + "de": { + "title": "Hier ist das Profil von $user$", + "desc": "Die Benutzer-ID von $user$ ist $id$. \n Hier ist das Erstellungsdatum des Kontos $creation_date$. \n Hier ist das Beitrittsdatum des Benutzers $joined_date$. \n Ist dieser Benutzer ein Bot? $bot$. \n Hier sind alle öffentlichen Flags des Benutzers $public_flag$." + } +} \ No newline at end of file diff --git a/DiscordAnilistBot.iml b/kasuki.iml similarity index 100% rename from DiscordAnilistBot.iml rename to kasuki.iml diff --git a/lang_file/available_lang.json b/lang_file/available_lang.json deleted file mode 100644 index e6fd9b20..00000000 --- a/lang_file/available_lang.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "En": { - "lang": "En" - }, - "Fr": { - "lang": "Fr" - }, - "Jp": { - "lang": "Jp" - }, - "De": { - "lang": "De" - } -} \ No newline at end of file diff --git a/lang_file/command_register/ai/image.json b/lang_file/command_register/ai/image.json deleted file mode 100644 index ba5ffb0e..00000000 --- a/lang_file/command_register/ai/image.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "image", - "desc": "Generate an image", - "option1": "description", - "option1_desc": "Enter a description of the image you want to generate." - }, - "Fr": { - "code": "fr", - "name": "image", - "desc": "Générer une image", - "option1": "description", - "option1_desc": "Entrez une description de l'image que vous souhaitez générer." - }, - "Jp": { - "code": "ja", - "name": "画像", - "desc": "画像を生成する", - "option1": "説明", - "option1_desc": "画像の説明を入力してください。" - }, - "De": { - "code": "de", - "name": "bild", - "desc": "Ein Bild generieren", - "option1": "beschreibung", - "option1_desc": "Beschreiben Sie das Bild, das Sie generieren möchten." - } -} \ No newline at end of file diff --git a/lang_file/command_register/ai/transcript.json b/lang_file/command_register/ai/transcript.json deleted file mode 100644 index dab93629..00000000 --- a/lang_file/command_register/ai/transcript.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "transcript", - "desc": "Generate a transcript", - "option1": "video", - "option1_desc": "Upload video file (max. 25MB)", - "option2": "prompt", - "option2_desc": "Enter text to guide style or continue audio", - "option3": "lang", - "option3_desc": "Select input language (ISO-639-1)" - }, - "Fr": { - "code": "fr", - "name": "transcription", - "desc": "Générer une transcription", - "option1": "vidéo", - "option1_desc": "Télécharger fichier vidéo (max. 25Mo)", - "option2": "prompt", - "option2_desc": "Entrez du texte pour guider le style ou continuer l'audio", - "option3": "langue", - "option3_desc": "Sélectionner la langue d'entrée (ISO-639-1)" - }, - "Jp": { - "code": "ja", - "name": "トランスクリプト", - "desc": "トランスクリプトを生成する", - "option1": "ビデオ", - "option1_desc": "最大25MBのビデオファイルを選択してください。", - "option2": "プロンプト", - "option2_desc": "任意のテキストを使用してスタイルをガイドまたは音声を続ける。", - "option3": "言語", - "option3_desc": "入力言語をISO-639-1形式にして、正確性とレートを改善します。" - }, - "De": { - "code": "de", - "name": "transkript", - "desc": "Ein Transkript erstellen", - "option1": "video", - "option1_desc": "Wählen Sie eine Video-Datei bis maximal 25 MB.", - "option2": "prompt", - "option2_desc": "Optionaler Text zum Stil oder Fortsetzen des Audios.", - "option3": "sprache", - "option3_desc": "Spracheingabe im Format ISO-639-1verbessert Genauigkeit und Latenz." - } -} \ No newline at end of file diff --git a/lang_file/command_register/ai/translation.json b/lang_file/command_register/ai/translation.json deleted file mode 100644 index 48be2e77..00000000 --- a/lang_file/command_register/ai/translation.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "translation", - "desc": "Generate a translation", - "option1": "video", - "option1_desc": "Upload video file (max. 25MB)", - "option2": "lang", - "option2_desc": "Select input language (ISO-639-1)" - }, - "Fr": { - "code": "fr", - "name": "traduction", - "desc": "Générer une traduction", - "option1": "vidéo", - "option1_desc": "Télécharger fichier vidéo (max. 25Mo)", - "option2": "langue", - "option2_desc": "Sélectionner la langue d'entrée (ISO-639-1)" - }, - "Jp": { - "code": "ja", - "name": "翻訳", - "desc": "翻訳を生成する", - "option1": "ビデオ", - "option1_desc": "最大25MBのビデオファイルを選択してください。", - "option2": "言語", - "option2_desc": "入力言語をISO-639-1形式にして、正確性とレートを改善します。" - }, - "De": { - "code": "de", - "name": "übersetzung", - "desc": "Eine Übersetzung generieren", - "option1": "video", - "option1_desc": "Wählen Sie eine Video-Datei bis maximal 25 MB.", - "option2": "sprache", - "option2_desc": "Spracheingabe im Format ISO-639-1verbessert Genauigkeit und Latenz." - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/anime.json b/lang_file/command_register/anilist/anime.json deleted file mode 100644 index e4656616..00000000 --- a/lang_file/command_register/anilist/anime.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "anime", - "desc": "Info of an anime", - "option1": "anime_name", - "option1_desc": "Name of the anime you want to check" - }, - "Fr": { - "code": "fr", - "name": "anime", - "desc": "Informations sur un anime", - "option1": "nom", - "option1_desc": "Nom de l'animation que vous souhaitez consulter" - }, - "De": { - "code": "de", - "name": "anime", - "desc": "Informationen zu einem Anime", - "option1": "anime_name", - "option1_desc": "Name des Animes, den Sie überprüfen möchten" - }, - "Jp": { - "code": "ja", - "name": "アニメ", - "desc": "アニメの情報", - "option1": "アニメ名", - "option1_desc": "確認したいアニメの名前" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/anime_activity/add_activity.json b/lang_file/command_register/anilist/anime_activity/add_activity.json deleted file mode 100644 index 6ed56c35..00000000 --- a/lang_file/command_register/anilist/anime_activity/add_activity.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "add_activity", - "desc": "Add an anime activity", - "option1": "anime_name", - "option1_desc": "Name of the anime you want to add as an activity", - "option2": "delays", - "option2_desc": "A delays in second" - }, - "Fr": { - "code": "fr", - "name": "ajouter_activité", - "desc": "Ajouter une activité d'anime", - "option1": "nom_anime", - "option1_desc": "Nom de l'anime que vous voulez ajouter comme activité", - "option2": "retards", - "option2_desc": "Retard en seconde" - }, - "Jp": { - "code": "ja", - "name": "アニメ活動を追加する", - "desc": "アニメ活動を追加する", - "option1": "アニメ名", - "option1_desc": "アニメの名前を入力してください", - "option2": "遅延時間", - "option2_desc": "遅延時間(秒)を入力してください" - }, - "De": { - "code": "de", - "name": "aktivitat_hinzufugen", - "desc": "Eine Aktivität hinzufügen", - "option1": "anime_name", - "option1_desc": "Name des Animes, den Sie als Aktivität hinzufügen möchten", - "option2": "verzögerungen", - "option2_desc": "Verzögerungen in Sekunden" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/anime_activity/register.json b/lang_file/command_register/anilist/anime_activity/register.json deleted file mode 100644 index 523b2957..00000000 --- a/lang_file/command_register/anilist/anime_activity/register.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "register", - "desc": "Register your Anilist username for ease of use.", - "option1": "username", - "option1_desc": "Your Anilist username." - }, - "Fr": { - "code": "fr", - "name": "enregistrer", - "desc": "Enregistrez votre nom d'utilisateur Anilist pour plus de facilité.", - "option1": "nom_d_utilisateur", - "option1_desc": "Votre nom d'utilisateur Anilist." - }, - "De": { - "code": "de", - "name": "registrieren", - "desc": "Registrieren Sie Ihren Anilist-Benutzernamen für eine einfachere Verwendung.", - "option1": "benutzername", - "option1_desc": "Ihr Anilist-Benutzername." - }, - "Jp": { - "code": "ja", - "name": "登録", - "desc": "Anilistユーザー名を登録して、利便性を向上させます。", - "option1": "ユーザー名", - "option1_desc": "あなたのAnilistユーザー名。" - } -} diff --git a/lang_file/command_register/anilist/character.json b/lang_file/command_register/anilist/character.json deleted file mode 100644 index 0f48eac8..00000000 --- a/lang_file/command_register/anilist/character.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "character", - "desc": "Get information on a character", - "option1": "name", - "option1_desc": "The name of the character" - }, - "Fr": { - "code": "fr", - "name": "personnage", - "desc": "Obtenir des informations sur un personnage", - "option1": "nom", - "option1_desc": "Le nom du personnage" - }, - "De": { - "code": "de", - "name": "charakter", - "desc": "Informationen zu einem Charakter", - "option1": "name", - "option1_desc": "Der Name des Charakters" - }, - "Jp": { - "code": "ja", - "name": "キャラクター", - "desc": "キャラクターの情報を取得する", - "option1": "名前", - "option1_desc": "キャラクターの名前" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/compare.json b/lang_file/command_register/anilist/compare.json deleted file mode 100644 index d5a5b253..00000000 --- a/lang_file/command_register/anilist/compare.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "compare", - "desc": "compare stats of two users", - "option1": "username", - "option1_desc": "Username of the 1st AniList user to compare", - "option2": "username2", - "option2_desc": "Username of the 2nd AniList user to compare" - }, - "Fr": { - "code": "fr", - "name": "comparer", - "desc": "comparer les statistiques de deux utilisateurs", - "option1": "nom_d_utilisateur", - "option1_desc": "Nom d'utilisateur du 1er utilisateur AniList à comparer", - "option2": "nom_d_utilisateur2", - "option2_desc": "Nom d'utilisateur du 2e utilisateur AniList à comparer" - }, - "De": { - "code": "de", - "name": "vergleichen", - "desc": "Statistiken von zwei Benutzern vergleichen", - "option1": "benutzername", - "option1_desc": "Benutzername des ersten AniList-Benutzers, um zu vergleichen", - "option2": "benutzername2", - "option2_desc": "Benutzername des zweiten AniList-Benutzers, um zu vergleichen" - }, - "Jp": { - "code": "ja", - "name": "比較", - "desc": "2つのユーザーの統計を比較する", - "option1": "ユーザー名", - "option1_desc": "比較する1番目のAniListユーザーのユーザー名", - "option2": "ユーザー名2", - "option2_desc": "比較する2番目のAniListユーザーのユーザー名" - } -} diff --git a/lang_file/command_register/anilist/level.json b/lang_file/command_register/anilist/level.json deleted file mode 100644 index b87a0afa..00000000 --- a/lang_file/command_register/anilist/level.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "level", - "desc": "Weeb level of a user", - "option1": "username", - "option1_desc": "Username of the anilist user you want to know the level of" - }, - "Fr": { - "code": "fr", - "name": "niveau", - "desc": "Niveau de weeb d'un utilisateur", - "option1": "nom_d_utilisateur", - "option1_desc": "Nom d'utilisateur de l'utilisateur Anilist dont vous voulez connaître le niveau." - }, - "De": { - "code": "de", - "name": "level", - "desc": "Weeb-Level eines Benutzers", - "option1": "benutzername", - "option1_desc": "Benutzername des Anilist-Benutzers, dessen Level Sie erfahren möchten." - }, - "Jp": { - "code": "ja", - "name": "レベル", - "desc": "アニリストユーザーのウィーブレベルを取得する", - "option1": "ユーザーネーム", - "option1_desc": "アニリストユーザーのユーザーネームを指定して、そのウィーブレベルを知りたいと思います。" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/ln.json b/lang_file/command_register/anilist/ln.json deleted file mode 100644 index 57d1f97d..00000000 --- a/lang_file/command_register/anilist/ln.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "ln", - "desc": "Info of a light novel", - "option1": "ln_name", - "option1_desc": "Name of the light novel you want to check" - }, - "Fr": { - "code": "fr", - "name": "light_novel", - "desc": "Informations sur un roman", - "option1": "titre", - "option1_desc": "Titre du roman que vous souhaitez consulter" - }, - "De": { - "code": "de", - "name": "light_novel", - "desc": "Informationen zu einem Light Novel", - "option1": "titel", - "option1_desc": "Titel des Light Novels, den Sie überprüfen möchten" - }, - "Jp": { - "code": "ja", - "name": "ライトノベル", - "desc": "ライトノベルの情報を取得する", - "option1": "タイトル", - "option1_desc": "確認したいライトノベルのタイトル" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/manga.json b/lang_file/command_register/anilist/manga.json deleted file mode 100644 index b9b71fdd..00000000 --- a/lang_file/command_register/anilist/manga.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "manga", - "desc": "Info of a manga", - "option1": "manga_name", - "option1_desc": "Name of the manga you want to check" - }, - "Fr": { - "code": "fr", - "name": "manga", - "desc": "Informations sur un manga", - "option1": "titre", - "option1_desc": "Titre du manga que vous souhaitez consulter" - }, - "De": { - "code": "de", - "name": "manga", - "desc": "Informationen zu einem Manga", - "option1": "titel", - "option1_desc": "Titel des Mangas, den Sie überprüfen möchten" - }, - "Jp": { - "code": "ja", - "name": "漫画", - "desc": "漫画の情報を取得する", - "option1": "タイトル", - "option1_desc": "確認したい漫画のタイトル" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/register.json b/lang_file/command_register/anilist/register.json deleted file mode 100644 index a4db4ec8..00000000 --- a/lang_file/command_register/anilist/register.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "register", - "desc": "Register your anilist username for ease of use.", - "option1": "username", - "option1_desc": "Your anilist user name." - }, - "Fr": { - "code": "fr", - "name": "enregistrement", - "desc": "Enregistrez votre nom d'utilisateur anilist pour faciliter l'utilisation.", - "option1": "nom_d_utilisateur", - "option1_desc": "Votre nom d'utilisateur anilist." - }, - "De": { - "code": "de", - "name": "registrierung", - "desc": "Registrieren Sie Ihren anilist-Benutzernamen, um die Nutzung zu vereinfachen.", - "option1": "benutzername", - "option1_desc": "Ihr anilist Benutzername." - }, - "Jp": { - "code": "ja", - "name": "登録", - "desc": "利用の便宜のためのあなたのanilistユーザー名を登録してください。", - "option1": "ユーザー名", - "option1_desc": "あなたのanilistユーザー名." - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/staff.json b/lang_file/command_register/anilist/staff.json deleted file mode 100644 index 358d043e..00000000 --- a/lang_file/command_register/anilist/staff.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "staff", - "desc": "Get info of a staff", - "option1": "staff_name", - "option1_desc": "Name of the staff you want info about." - }, - "Fr": { - "code": "fr", - "name": "staff", - "desc": "Obtenir des informations sur un membre du staff", - "option1": "nom_du_staff", - "option1_desc": "Nom du staff que vous souhaitez obtenir des informations sur." - }, - "De": { - "code": "de", - "name": "mitarbeiter", - "desc": "Informationen über einen Mitarbeiter erhalten", - "option1": "mitarbeiter_name", - "option1_desc": "Name des Mitarbeiters, über den Sie Informationen erhalten möchten." - }, - "Jp": { - "code": "ja", - "name": "スタッフ", - "desc": "スタッフの情報を入手する", - "option1": "スタッフ名", - "option1_desc": "情報を求めるスタッフの名前です。" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/studio.json b/lang_file/command_register/anilist/studio.json deleted file mode 100644 index a2a7eb1b..00000000 --- a/lang_file/command_register/anilist/studio.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "studio", - "desc": "Get info on an studio", - "option1": "studio", - "option1_desc": "The name of the studio." - }, - "Fr": { - "code": "fr", - "name": "studio", - "desc": "Obtenir des informations sur un studio", - "option1": "nom", - "option1_desc": "Le nom du studio." - }, - "De": { - "code": "de", - "name": "studio", - "desc": "Informationen zu einem Studio erhalten", - "option1": "name", - "option1_desc": "Der Name des Studios." - }, - "Jp": { - "code": "ja", - "name": "スタジオ", - "desc": "スタジオの情報を取得する", - "option1": "名前", - "option1_desc": "スタジオの名前。" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/user.json b/lang_file/command_register/anilist/user.json deleted file mode 100644 index 416e4847..00000000 --- a/lang_file/command_register/anilist/user.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "user", - "desc": "Info of an anilist user", - "option1": "username", - "option1_desc": "Username of the anilist user you want to check." - }, - "Fr": { - "code": "fr", - "name": "utilisateur", - "desc": "Informations sur un utilisateur d'Anilist", - "option1": "nom_d_utilisateur", - "option1_desc": "Nom d'utilisateur de l'utilisateur Anilist que vous souhaitez vérifier." - }, - "De": { - "code": "de", - "name": "benutzer", - "desc": "Informationen zu einem Anilist-Benutzer", - "option1": "benutzername", - "option1_desc": "Benutzername des Anilist-Benutzers, den Sie überprüfen möchten." - }, - "Jp": { - "code": "ja", - "name": "ユーザー", - "desc": "アニリストユーザーの情報を取得する", - "option1": "ユーザーネーム", - "option1_desc": "アニリストユーザーのユーザーネームを指定してください。" - } -} \ No newline at end of file diff --git a/lang_file/command_register/anilist/waifu.json b/lang_file/command_register/anilist/waifu.json deleted file mode 100644 index 9905c894..00000000 --- a/lang_file/command_register/anilist/waifu.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "waifu", - "desc": "Give you the best waifu.", - "option1": "username", - "option1_desc": "Username of the discord user you want the waifu of." - }, - "Fr": { - "code": "fr", - "name": "waifu", - "desc": "Donner la meilleure waifu.", - "option1": "nom_d_utilisateur", - "option1_desc": "Nom d'utilisateur Discord que vous voulez la waifu de." - }, - "De": { - "code": "de", - "name": "waifu", - "desc": "Die beste Waifu für dich.", - "option1": "benutzername", - "option1_desc": "Benutzername des Discord-Benutzers, von dem du die Waifu haben möchtest." - }, - "Jp": { - "code": "ja", - "name": "ワイフ", - "desc": "最高のワイフを提供します。", - "option1": "ユーザー名", - "option1_desc": "ディスコードユーザーのユーザー名を指定して、そのワイフを知りたいと思います。" - } -} \ No newline at end of file diff --git a/lang_file/command_register/general/avatar.json b/lang_file/command_register/general/avatar.json deleted file mode 100644 index a8833808..00000000 --- a/lang_file/command_register/general/avatar.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "avatar", - "description": "Get the avatar", - "option1": "user", - "option1_desc": "The user you want the avatar of" - }, - "Fr": { - "code": "fr", - "name": "avatar", - "description": "Obtenez l'avatar", - "option1": "utilisateur", - "option1_desc": "L'utilisateur dont vous voulez l'avatar" - }, - "De": { - "code": "de", - "name": "avatar", - "description": "Holen Sie sich den Avatar", - "option1": "benutzer", - "option1_desc": "Der Benutzer, dessen Avatar Sie möchten" - }, - "Jp": { - "code": "ja", - "name": "avatar", - "description": "アバターを取得する", - "option1": "user", - "option1_desc": "アバターが欲しいユーザー" - } -} \ No newline at end of file diff --git a/lang_file/command_register/general/banner.json b/lang_file/command_register/general/banner.json deleted file mode 100644 index c2879112..00000000 --- a/lang_file/command_register/general/banner.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "banner", - "description": "Get the banner", - "option1": "user", - "option1_desc": "The user you want the banner of" - }, - "Fr": { - "code": "fr", - "name": "banner", - "description": "Obtenir la bannière", - "option1": "utilisateur", - "option1_desc": "L'utilisateur dont vous voulez la bannière" - }, - "De": { - "code": "de", - "name": "banner", - "description": "Erhalte das Banner", - "option1": "benutzer", - "option1_desc": "Der Benutzer, dessen Banner du erhalten möchtest" - }, - "Jp": { - "code": "ja", - "name": "バナー", - "description": "バナーを取得する", - "option1": "ユーザー", - "option1_desc": "バナーを取得したいユーザー" - } -} diff --git a/lang_file/command_register/general/credit.json b/lang_file/command_register/general/credit.json deleted file mode 100644 index 182be494..00000000 --- a/lang_file/command_register/general/credit.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "credit", - "desc": "List of credit" - }, - "Fr": { - "code": "fr", - "name": "crédit", - "desc": "Liste de crédits" - }, - "De": { - "code": "de", - "name": "kredit", - "desc": "Liste von Krediten" - }, - "Jp": { - "code": "ja", - "name": "信用", - "desc": "信用一覧" - } -} diff --git a/lang_file/command_register/general/info.json b/lang_file/command_register/general/info.json deleted file mode 100644 index e9b0bcc6..00000000 --- a/lang_file/command_register/general/info.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "info", - "desc": "Get information on the bot" - }, - "Fr": { - "code": "fr", - "name": "info", - "desc": "Obtenir des informations sur le bot" - }, - "De": { - "code": "de", - "name": "info", - "desc": "Erhalte Informationen über den Bot" - }, - "Jp": { - "code": "ja", - "name": "インフォメーション", - "desc": "ボットの情報を取得する" - } -} diff --git a/lang_file/command_register/general/lang.json b/lang_file/command_register/general/lang.json deleted file mode 100644 index 8b7e02a3..00000000 --- a/lang_file/command_register/general/lang.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "lang", - "description": "Change the language of the bot's response", - "option1": "lang", - "option1_desc": "The language you want to set the response to." - }, - "Fr": { - "code": "fr", - "name": "langue", - "description": "Change la langue de la réponse du bot", - "option1": "langue", - "option1_desc": "La langue à laquelle vous souhaitez définir la réponse." - }, - "De": { - "code": "de", - "name": "sprache", - "description": "Ändere die Sprache der Antwort des Bots", - "option1": "sprache", - "option1_desc": "Die Sprache, auf die du die Antwort setzen möchtest." - }, - "Jp": { - "code": "ja", - "name": "ラング", - "description": "ボットの応答の言語を変更します", - "option1": "言語", - "option1_desc": "応答を設定したい言語" - } -} diff --git a/lang_file/command_register/general/module_activation.json b/lang_file/command_register/general/module_activation.json deleted file mode 100644 index 7d88bca9..00000000 --- a/lang_file/command_register/general/module_activation.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "module", - "desc": "Turn on and off module.", - "option1": "module_name", - "option1_desc": "The name of the module you want to turn on or off", - "option2": "state", - "option2_desc": "ON or OFF" - }, - "Fr": { - "code": "fr", - "name": "module", - "desc": "Activer et désactiver le module.", - "option1": "nom_du_module", - "option1_desc": "Le nom du module que vous souhaitez activer ou désactiver.", - "option2": "statut", - "option2_desc": "ON (activé) ou OFF (désactivé)" - }, - "Jp": { - "code": "ja", - "name": "モジュール", - "desc": "モジュールのON/OFFを切り替えます。", - "option1": "モジュール名", - "option1_desc": "ONまたはOFFにしたいモジュールの名前", - "option2": "状態", - "option2_desc": "ON または OFF" - }, - "De": { - "code": "de", - "name": "modul", - "desc": "Schalten Sie das Modul ein oder aus.", - "option1": "modulname", - "option1_desc": "Der Name des Moduls, das Sie aktivieren oder deaktivieren möchten.", - "option2": "status", - "option2_desc": "AN (aktiviert) oder AUS (deaktiviert)" - } -} diff --git a/lang_file/command_register/general/ping.json b/lang_file/command_register/general/ping.json deleted file mode 100644 index a9622c49..00000000 --- a/lang_file/command_register/general/ping.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "ping", - "desc": "A ping command" - }, - "Fr": { - "code": "fr", - "name": "ping", - "desc": "Une commande de ping" - }, - "De": { - "code": "de", - "name": "ping", - "desc": "Ein Ping-Befehl" - }, - "Jp": { - "code": "ja", - "name": "ピング", - "desc": "Pingコマンド" - } -} diff --git a/lang_file/command_register/general/profile.json b/lang_file/command_register/general/profile.json deleted file mode 100644 index e7f7b966..00000000 --- a/lang_file/command_register/general/profile.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "code": "en-US", - "name": "profile", - "desc": "Show the profile of a user", - "option1": "user", - "option1_desc": "The user you want the profile of" - }, - "Fr": { - "code": "fr", - "name": "profil", - "desc": "Afficher le profil d'un utilisateur", - "option1": "utilisateur", - "option1_desc": "L'utilisateur dont vous souhaitez afficher le profil" - }, - "De": { - "code": "de", - "name": "profil", - "desc": "Zeige das Profil eines Benutzers", - "option1": "benutzer", - "option1_desc": "Der Benutzer, dessen Profil du anzeigen möchtest" - }, - "Jp": { - "code": "ja", - "name": "プロフィル", - "desc": "ユーザーのプロフィールを表示します", - "option1": "ユーザー", - "option1_desc": "プロフィールを表示したいユーザー" - } -} diff --git a/lang_file/embed/ai/image.json b/lang_file/embed/ai/image.json deleted file mode 100644 index c482f936..00000000 --- a/lang_file/embed/ai/image.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "En": { - "title": "Here's your generated image" - }, - "Fr": { - "title": "Voici votre image générée" - }, - "De": { - "title": "Hier ist Ihr generiertes Bild" - }, - "Jp": { - "title": "生成された画像はこちらです" - }, - "cmn": { - "title": "这是您生成的图像" - }, - "cmnp": { - "title": "Zhè shì nín shēngchéng de túxiàng" - } -} diff --git a/lang_file/embed/ai/transcript.json b/lang_file/embed/ai/transcript.json deleted file mode 100644 index 101ef4dd..00000000 --- a/lang_file/embed/ai/transcript.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "En": { - "title": "Here's your transcript" - }, - "Fr": { - "title": "Voici votre transcription" - }, - "De": { - "title": "Hier ist Ihr Transkript" - }, - "Jp": { - "title": "トランスクリプトをご覧ください" - }, - "cmn": { - "title": "这是您的转录 " - }, - "cmnp": { - "title": "Zhè shì nín de zhuǎnlù" - } -} diff --git a/lang_file/embed/ai/translation.json b/lang_file/embed/ai/translation.json deleted file mode 100644 index 77928b79..00000000 --- a/lang_file/embed/ai/translation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "En": { - "title": "Here's your translation" - }, - "Fr": { - "title": "Voici votre traduction" - }, - "De": { - "title": "Hier ist Ihre Übersetzung" - }, - "Jp": { - "title": "翻訳結果です" - }, - "cmn": { - "title": "这是您的翻译" - }, - "cmnp": { - "title": "Zhè shì nín de fānyì" - } -} diff --git a/lang_file/embed/anilist/anime.json b/lang_file/embed/anilist/anime.json deleted file mode 100644 index d2e48332..00000000 --- a/lang_file/embed/anilist/anime.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "En": { - "desc_title": "Info", - "desc_part_1": "Format: ", - "desc_part_2": " / Source: ", - "desc_part_3": "\nStart Date: ", - "desc_part_4": "\nEnd Date: ", - "desc_part_5": "Full Name: ", - "desc_part_6": " / User Preferred: ", - "desc_part_7": " / Role: ", - "fields_name_1": "Genre", - "fields_name_2": "Tag", - "error_anime_not_found": "Unable to find this anime" - }, - "Fr": { - "desc_title": "Infos", - "desc_part_1": "Format : ", - "desc_part_2": " / Source : ", - "desc_part_3": "\nDate de début : ", - "desc_part_4": "\nDate de fin : ", - "desc_part_5": "Nom complet : ", - "desc_part_6": " / Préférence utilisateur : ", - "desc_part_7": " / Rôle : ", - "fields_name_1": "Genre", - "fields_name_2": "Tag", - "error_anime_not_found": "Impossible de trouver cet anime" - }, - "Jp": { - "desc_title": "情報", - "desc_part_1": "フォーマット:", - "desc_part_2": " / ソース:", - "desc_part_3": "\n開始日:", - "desc_part_4": "\n終了日:", - "desc_part_5": "フルネーム:", - "desc_part_6": " / ユーザーの優先設定:", - "desc_part_7": " / 役割:", - "fields_name_1": "ジャンル", - "fields_name_2": "タグ", - "error_anime_not_found": "このアニメが見つかりません" - }, - "De": { - "desc_title": "Informationen", - "desc_part_1": "Format: ", - "desc_part_2": " / Quelle: ", - "desc_part_3": "\nStartdatum: ", - "desc_part_4": "\nEnddatum: ", - "desc_part_5": "Voller Name: ", - "desc_part_6": " / Benutzer bevorzugt: ", - "desc_part_7": " / Rolle: ", - "fields_name_1": "Genre", - "fields_name_2": "Tag", - "error_anime_not_found": "Dieses Anime konnte nicht gefunden werden" - } -} diff --git a/lang_file/embed/anilist/anime_activity/add_activity.json b/lang_file/embed/anilist/anime_activity/add_activity.json deleted file mode 100644 index 354b57ab..00000000 --- a/lang_file/embed/anilist/anime_activity/add_activity.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "En": { - "error_no_media": "Unable to find this media", - "title1": "Already Exist", - "title2": "Added Successfully", - "already_added": "This activity already exists. You should already have an activity for: ", - "adding": "The activity was added successfully for: ", - "error_slash_command": "Can't create slash command" - }, - "Fr": { - "error_no_media": "Impossible de trouver ce média", - "title1": "Déjà existant", - "title2": "Ajouté avec succès", - "already_added": "Cette activité existe déjà. Vous devriez déjà avoir une activité pour : ", - "adding": "L'activité a été ajoutée avec succès pour : ", - "error_slash_command": "Impossible de créer la commande slash" - }, - "De": { - "error_no_media": "Dieses Medium konnte nicht gefunden werden", - "title1": "Bereits vorhanden", - "title2": "Erfolgreich hinzugefügt", - "already_added": "Diese Aktivität ist bereits vorhanden. Sie sollten bereits eine Aktivität für haben: ", - "adding": "Die Aktivität wurde erfolgreich hinzugefügt für: ", - "error_slash_command": "Slash-Befehl kann nicht erstellt werden" - }, - "Jp": { - "error_no_media": "このメディアが見つかりません", - "title1": "すでに存在します", - "title2": "正常に追加されました", - "already_added": "このアクティビティはすでに存在します。すでに次のアクティビティがあるはずです:", - "adding": "アクティビティは正常に追加されました:", - "error_slash_command": "スラッシュコマンドを作成できません" - } -} diff --git a/lang_file/embed/anilist/anime_activity/send_activity.json b/lang_file/embed/anilist/anime_activity/send_activity.json deleted file mode 100644 index 9e6afb45..00000000 --- a/lang_file/embed/anilist/anime_activity/send_activity.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "En": { - "title": "New Episode", - "ep": "Episode ", - "of": "of ", - "end": "just released" - }, - "Fr": { - "title": "Nouvel épisode", - "ep": "Épisode ", - "of": "de ", - "end": "vient de sortir" - }, - "De": { - "title": "Neue Folge", - "ep": "Episode ", - "of": "von ", - "end": "wurde gerade veröffentlicht" - }, - "Jp": { - "title": "新しいエピソード", - "ep": "エピソード ", - "of": "(全 ", - "end": ")がリリースされました" - } -} diff --git a/lang_file/embed/anilist/character.json b/lang_file/embed/anilist/character.json deleted file mode 100644 index 6b8b6ac0..00000000 --- a/lang_file/embed/anilist/character.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "En": { - "age": "Age: ", - "gender": ".\nGender: ", - "date_of_birth": ".\nDate of Birth: ", - "favourite": ".\nNumber of Favorites: ", - "desc": ".\n\nDescription: \n\n", - "error_no_character": "Error: Failed to retrieve character data", - "info": "Info" - }, - "Fr": { - "age": "Âge : ", - "gender": ".\nGenre : ", - "date_of_birth": ".\nDate de naissance : ", - "favourite": ".\nNombre de favoris : ", - "desc": ".\nDescription :\n ", - "error_no_character": "Erreur : Impossible de récupérer les données du personnage", - "info": "Informations" - }, - "Jp": { - "age": "年齢:", - "gender": "。\n性別:", - "date_of_birth": "。\n生年月日:", - "favourite": "。\nお気に入りの数:", - "desc": "。\n説明:\n", - "error_no_character": "エラー:キャラクターデータの取得に失敗しました", - "info": "情報" - }, - "De": { - "age": "Alter: ", - "gender": ".\nGeschlecht: ", - "date_of_birth": ".\nGeburtsdatum: ", - "favourite": ".\nAnzahl der Favoriten: ", - "desc": ".\nBeschreibung: \n", - "error_no_character": "Fehler: Charakterdaten konnten nicht abgerufen werden", - "info": "Info" - } -} \ No newline at end of file diff --git a/lang_file/embed/anilist/compare.json b/lang_file/embed/anilist/compare.json deleted file mode 100644 index 5ed3a9c4..00000000 --- a/lang_file/embed/anilist/compare.json +++ /dev/null @@ -1,188 +0,0 @@ -{ - "En": { - "more_anime": " as more anime than ", - "connector_user_same_anime": " and ", - "same_anime": " as the same amount of anime watched.", - "time_anime_watch": " as watched anime for longer than ", - "connector_user_same_time": " and ", - "same_time": " as the same amount of anime watch time than ", - "more_manga": " as read more manga than ", - "connector_user_same_manga": " and ", - "same_manga": " as the same amount of manga read than ", - "more_chapter": " as read more chapters than ", - "connector_user_same_chapter": " and ", - "same_chapter": " as the same amount of chapters read than ", - "genre_same_connector_anime": " and ", - "genre_same_prefer_anime": " prefer ", - "diff_pref_genre_1_anime": " prefer ", - "diff_pref_genre_while_anime": " while ", - "diff_pref_genre_2_anime": " prefer ", - "same_tag_connector_anime": " and ", - "same_tag_prefer_anime": " prefer ", - "diff_pref_tag_1_anime": " prefer ", - "diff_pref_tag_while_anime": " while ", - "diff_pref_tag_2_anime": " prefer ", - "diff_pref_tag_anime": " for anime.", - "genre_same_connector_manga": " and ", - "genre_same_prefer_manga": " prefer ", - "diff_pref_genre_1_manga": " prefer ", - "diff_pref_genre_while_manga": " while ", - "diff_pref_genre_2_manga": " prefer ", - "diff_pref_genre_manga": " for manga.", - "same_tag_connector_manga": " and ", - "same_tag_prefer_manga": " prefer ", - "diff_pref_tag_1_manga": " prefer ", - "diff_pref_tag_while_manga": " while ", - "diff_pref_tag_2_manga": " prefer ", - "title": "Comparison", - "sub_title_anime": "Anime: ", - "watch_time": ". \n \n Watch Time: ", - "pref_genre_anime": ". \n \n Preferred genre for anime: ", - "pref_tag_anime": ". \n \n Preferred tag for anime: ", - "sub_title_manga": ". \n \n Manga: ", - "chapter_read": ". \n \n Chapter read: ", - "pref_genre_manga": "\n \n Preferred genre for manga: ", - "pref_tag_manga": ". \n \n Preferred tag for manga: ", - "error_slash_command": "Can't create slash command" - }, - "Fr": { - "more_anime": " a regardé plus d'animes que ", - "connector_user_same_anime": " et ", - "same_anime": " a regardé le même nombre d'animes.", - "time_anime_watch": " a regardé des animes plus longtemps que ", - "connector_user_same_time": " et ", - "same_time": " a regardé le même temps d'animes que ", - "more_manga": " a lu plus de mangas que ", - "connector_user_same_manga": " et ", - "same_manga": " a lu le même nombre de mangas que ", - "more_chapter": " a lu plus de chapitres que ", - "connector_user_same_chapter": " et ", - "same_chapter": " a lu le même nombre de chapitres que ", - "genre_same_connector_anime": " et ", - "genre_same_prefer_anime": " préfère ", - "diff_pref_genre_1_anime": " préfère ", - "diff_pref_genre_while_anime": " tandis que ", - "diff_pref_genre_2_anime": " préfère ", - "same_tag_connector_anime": " et ", - "same_tag_prefer_anime": " préfère ", - "diff_pref_tag_1_anime": " préfère ", - "diff_pref_tag_while_anime": " tandis que ", - "diff_pref_tag_2_anime": " préfère ", - "diff_pref_tag_anime": " pour les animes.", - "genre_same_connector_manga": " et ", - "genre_same_prefer_manga": " préfère ", - "diff_pref_genre_1_manga": " préfère ", - "diff_pref_genre_while_manga": " tandis que ", - "diff_pref_genre_2_manga": " préfère ", - "diff_pref_genre_manga": " pour les mangas.", - "same_tag_connector_manga": " et ", - "same_tag_prefer_manga": " préfère ", - "diff_pref_tag_1_manga": " préfère ", - "diff_pref_tag_while_manga": " tandis que ", - "diff_pref_tag_2_manga": " préfère ", - "title": "Comparaison", - "sub_title_anime": "Anime : ", - "watch_time": ". \n \n Temps de visionnage : ", - "pref_genre_anime": ". \n \n Genre préféré pour les animes : ", - "pref_tag_anime": ". \n \n Tag préféré pour les animes : ", - "sub_title_manga": ". \n \n Manga : ", - "chapter_read": ". \n \n Chapitres lus : ", - "pref_genre_manga": " \n \n Genre préféré pour les mangas : ", - "pref_tag_manga": ". \n \n Tag préféré pour les mangas : ", - "error_slash_command": "Impossible de créer la commande slash" - }, - "Jp": { - "more_anime": " は ", - "connector_user_same_anime": " と ", - "same_anime": " と同じ数のアニメを視聴しています。", - "time_anime_watch": " は ", - "connector_user_same_time": " と ", - "same_time": " と同じ視聴時間のアニメを視聴しています。", - "more_manga": " は ", - "connector_user_same_manga": " と ", - "same_manga": " と同じ数のマンガを読んでいます。", - "more_chapter": " は ", - "connector_user_same_chapter": " と ", - "same_chapter": " と同じ数のチャプターを読んでいます。", - "genre_same_connector_anime": " と ", - "genre_same_prefer_anime": " が好きなジャンルは ", - "diff_pref_genre_1_anime": " は好きなジャンルが ", - "diff_pref_genre_while_anime": " 一方、", - "diff_pref_genre_2_anime": " は好きなジャンルが ", - "same_tag_connector_anime": " と ", - "same_tag_prefer_anime": " が好きなタグは ", - "diff_pref_tag_1_anime": " は好きなタグが ", - "diff_pref_tag_while_anime": " 一方、", - "diff_pref_tag_2_anime": " は好きなタグが ", - "diff_pref_tag_anime": " です。", - "genre_same_connector_manga": " と ", - "genre_same_prefer_manga": " が好きなジャンルは ", - "diff_pref_genre_1_manga": " は好きなジャンルが ", - "diff_pref_genre_while_manga": " 一方、", - "diff_pref_genre_2_manga": " は好きなジャンルが ", - "diff_pref_genre_manga": " です。", - "same_tag_connector_manga": " と ", - "same_tag_prefer_manga": " が好きなタグは ", - "diff_pref_tag_1_manga": " は好きなタグが ", - "diff_pref_tag_while_manga": " 一方、", - "diff_pref_tag_2_manga": " は好きなタグが ", - "diff_pref_tag_manga": " です。", - "title": "比較", - "sub_title_anime": "アニメ:", - "watch_time": " \n \n 視聴時間:", - "pref_genre_anime": " \n \n アニメの好きなジャンル:", - "pref_tag_anime": " \n \n アニメの好きなタグ:", - "sub_title_manga": " \n \n マンガ:", - "chapter_read": " \n \n 読んだチャプター:", - "pref_genre_manga": " \n \n マンガの好きなジャンル:", - "pref_tag_manga": " \n \n マンガの好きなタグ:", - "error_slash_command": "スラッシュコマンドを作成できません" - }, - "De": { - "more_anime": " hat mehr Anime als ", - "connector_user_same_anime": " und ", - "same_anime": " hat die gleiche Anzahl an Anime gesehen.", - "time_anime_watch": " hat Anime länger geschaut als ", - "connector_user_same_time": " und ", - "same_time": " hat die gleiche Anzahl an Anime-Wiedergabezeit als ", - "more_manga": " hat mehr Manga als ", - "connector_user_same_manga": " und ", - "same_manga": " hat die gleiche Anzahl an Manga gelesen als ", - "more_chapter": " hat mehr Kapitel gelesen als ", - "connector_user_same_chapter": " und ", - "same_chapter": " hat die gleiche Anzahl an Kapiteln gelesen als ", - "genre_same_connector_anime": " und ", - "genre_same_prefer_anime": " bevorzugt ", - "diff_pref_genre_1_anime": " bevorzugt ", - "diff_pref_genre_while_anime": " während ", - "diff_pref_genre_2_anime": " bevorzugt ", - "same_tag_connector_anime": " und ", - "same_tag_prefer_anime": " bevorzugt ", - "diff_pref_tag_1_anime": " bevorzugt ", - "diff_pref_tag_while_anime": " während ", - "diff_pref_tag_2_anime": " bevorzugt ", - "diff_pref_tag_anime": " für Anime.", - "genre_same_connector_manga": " und ", - "genre_same_prefer_manga": " bevorzugt ", - "diff_pref_genre_1_manga": " bevorzugt ", - "diff_pref_genre_while_manga": " während ", - "diff_pref_genre_2_manga": " bevorzugt ", - "diff_pref_genre_manga": " für Manga.", - "same_tag_connector_manga": " und ", - "same_tag_prefer_manga": " bevorzugt ", - "diff_pref_tag_1_manga": " bevorzugt ", - "diff_pref_tag_while_manga": " während ", - "diff_pref_tag_2_manga": " bevorzugt ", - "diff_pref_tag_manga": " für Manga.", - "title": "Vergleich", - "sub_title_anime": "Anime: ", - "watch_time": ". \n \n Wiedergabezeit: ", - "pref_genre_anime": ". \n \n Bevorzugtes Genre für Anime: ", - "pref_tag_anime": ". \n \n Bevorzugter Tag für Anime: ", - "sub_title_manga": ". \n \n Manga: ", - "chapter_read": ". \n \n Gelesene Kapitel: ", - "pref_genre_manga": " \n \n Bevorzugtes Genre für Manga: ", - "pref_tag_manga": ". \n \n Bevorzugter Tag für Manga: ", - "error_slash_command": "Slash-Befehl kann nicht erstellt werden" - } -} \ No newline at end of file diff --git a/lang_file/embed/anilist/level.json b/lang_file/embed/anilist/level.json deleted file mode 100644 index d890375d..00000000 --- a/lang_file/embed/anilist/level.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "En": { - "level": "Your level is: ", - "xp": ".\nYou have a total of: ", - "progression_1": " XP.\nYou are at ", - "progression_2": " for the next level." - }, - "Fr": { - "level": "Votre niveau est : ", - "xp": ".\nVous avez un total de : ", - "progression_1": " XP.\nVous êtes à ", - "progression_2": " pour le prochain niveau." - }, - "Jp": { - "level": "あなたのレベルは:", - "xp": "です。\n合計:", - "progression_1": " XPです。\n次のレベルまで", - "progression_2": " 進んでいます。" - }, - "De": { - "level": "Dein Level ist: ", - "xp": ".\nDu hast insgesamt: ", - "progression_1": " XP.\nDu bist auf Level ", - "progression_2": " für das nächste Level." - } -} \ No newline at end of file diff --git a/lang_file/embed/anilist/media.json b/lang_file/embed/anilist/media.json deleted file mode 100644 index 4c60d50e..00000000 --- a/lang_file/embed/anilist/media.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "En": { - "full_name": "Full name: ", - "user_pref": " / User preferred: ", - "role": " / Role: ", - "format": "format: ", - "source": " / source: ", - "start_date": "\nStart date: ", - "end_date": "\nEnd date: ", - "fields_name_1": "Genre", - "fields_name_2": "Tag", - "desc_title": "Info", - "error_no_media": "Unable to find this media" - }, - "Fr": { - "full_name": "Nom complet : ", - "user_pref": " / Préférence utilisateur : ", - "role": " / Rôle : ", - "format": "format : ", - "source": " / source : ", - "start_date": "\nDate de début : ", - "end_date": "\nDate de fin : ", - "fields_name_1": "Genre", - "fields_name_2": "Tag", - "desc_title": "Info", - "error_no_media": "Impossible de trouver ce média" - }, - "Jp": { - "full_name": "フルネーム:", - "user_pref": " / ユーザーの優先設定:", - "role": " / 役割:", - "format": "フォーマット:", - "source": " / ソース:", - "start_date": "\n開始日:", - "end_date": "\n終了日:", - "fields_name_1": "ジャンル", - "fields_name_2": "タグ", - "desc_title": "情報", - "error_no_media": "このメディアが見つかりません" - }, - "De": { - "full_name": "Voller Name: ", - "user_pref": " / Benutzer bevorzugt: ", - "role": " / Rolle: ", - "format": "Format: ", - "source": " / Quelle: ", - "start_date": "\nStartdatum: ", - "end_date": "\nEnddatum: ", - "fields_name_1": "Genre", - "fields_name_2": "Tag", - "desc_title": "Info", - "error_no_media": "Dieses Medium konnte nicht gefunden werden" - } -} diff --git a/lang_file/embed/anilist/random.json b/lang_file/embed/anilist/random.json deleted file mode 100644 index 88d5650a..00000000 --- a/lang_file/embed/anilist/random.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "En": { - "error_title": "Something went wrong.", - "error_message": "How the heck did you manage this?", - "genre": "Genre: ", - "tag": ".\nTags: ", - "format": ".\nFormat: ", - "desc": ".\n\n\nDescription: ", - "error_slash_command": "Can't create slash command" - }, - "Fr": { - "error_title": "Quelque chose s'est mal passé.", - "error_message": "Comment diable avez-vous réussi cela ?", - "genre": "Genre : ", - "tag": ".\nTags : ", - "format": ".\nFormat : ", - "desc": ".\n\n\nDescription : ", - "error_slash_command": "Impossible de créer la commande slash" - }, - "Jp": { - "error_title": "何かがうまくいかなかったようです。", - "error_message": "一体どうやってこれをやり遂げたのですか?", - "genre": "ジャンル:", - "tag": "。\nタグ:", - "format": "。\nフォーマット:", - "desc": "。\n\n\n説明:", - "error_slash_command": "スラッシュコマンドを作成できません" - }, - "De": { - "error_title": "Etwas ist schiefgelaufen.", - "error_message": "Wie zum Teufel haben Sie das geschafft?", - "genre": "Genre: ", - "tag": ".\nTags: ", - "format": ".\nFormat: ", - "desc": ".\n\n\nBeschreibung: ", - "error_slash_command": "Slash-Befehl kann nicht erstellt werden" - } -} diff --git a/lang_file/embed/anilist/register.json b/lang_file/embed/anilist/register.json deleted file mode 100644 index ce5cd019..00000000 --- a/lang_file/embed/anilist/register.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "En": { - "part_1": "The user ", - "part_2": " was linked to ", - "part_3": " AniList", - "error_slash_command": "Can't create slash command" - }, - "Fr": { - "part_1": "L'utilisateur ", - "part_2": " a été lié à ", - "part_3": " AniList", - "error_slash_command": "Impossible de créer la commande slash" - }, - "Jp": { - "part_1": "ユーザー", - "part_2": " が ", - "part_3": " AniList にリンクされました", - "error_slash_command": "スラッシュコマンドを作成できません" - }, - "De": { - "part_1": "Der Benutzer ", - "part_2": " wurde mit ", - "part_3": " AniList verknüpft", - "error_slash_command": "Slash-Befehl kann nicht erstellt werden" - } -} diff --git a/lang_file/embed/anilist/staff.json b/lang_file/embed/anilist/staff.json deleted file mode 100644 index 544522f9..00000000 --- a/lang_file/embed/anilist/staff.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "En": { - "desc_title": "Info", - "date_of_birth": "Date of Birth: ", - "date_of_death": ".\nDate of Death: ", - "hometown": ".\nHometown: ", - "primary_language": ". Primary Language: ", - "primary_occupation": ".\nOccupation: ", - "media": "MEDIA", - "va": "VA", - "error_slash_command": "Can't create slash command" - }, - "Fr": { - "desc_title": "Infos", - "date_of_birth": "Date de naissance : ", - "date_of_death": ".\nDate de décès : ", - "hometown": ".\nVille natale : ", - "primary_language": ". Langue principale : ", - "primary_occupation": ".\nProfession : ", - "media": "MÉDIA", - "va": "Doubleur", - "error_slash_command": "Impossible de créer la commande slash" - }, - "Jp": { - "desc_title": "情報", - "date_of_birth": "生年月日:", - "date_of_death": "。\n死亡日:", - "hometown": "。\n出身地:", - "primary_language": "。主要言語:", - "primary_occupation": "。\n職業:", - "media": "メディア", - "va": "声優", - "error_slash_command": "スラッシュコマンドを作成できません" - }, - "De": { - "desc_title": "Info", - "date_of_birth": "Geburtsdatum: ", - "date_of_death": ".\nTodesdatum: ", - "hometown": ".\nHeimatstadt: ", - "primary_language": ". Hauptsprache: ", - "primary_occupation": ".\nBeruf: ", - "media": "MEDIEN", - "va": "Synchronsprecher", - "error_slash_command": "Slash-Befehl kann nicht erstellt werden" - } -} \ No newline at end of file diff --git a/lang_file/embed/anilist/studio.json b/lang_file/embed/anilist/studio.json deleted file mode 100644 index 8db21852..00000000 --- a/lang_file/embed/anilist/studio.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "En": { - "anime_or_manga": "Here are some Anime or Manga produced by the studio", - "error_slash_command": "Can't create slash command", - "favorite": "Favorite: " - }, - "Fr": { - "anime_or_manga": "Voici quelques Anime ou Manga produits par le studio", - "error_slash_command": "Impossible de créer la commande slash", - "favorite": "Favori : " - }, - "De": { - "anime_or_manga": "Hier sind einige Anime oder Manga, die vom Studio produziert wurden", - "error_slash_command": "Slash-Befehl kann nicht erstellt werden", - "favorite": "Favorit: " - }, - "Jp": { - "anime_or_manga": "ここにはスタジオによって制作されたいくつかのアニメやマンガがあります", - "error_slash_command": "スラッシュコマンドを作成できません", - "favorite": "お気に入り:" - } -} diff --git a/lang_file/embed/anilist/user.json b/lang_file/embed/anilist/user.json deleted file mode 100644 index 1620160e..00000000 --- a/lang_file/embed/anilist/user.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "En": { - "manga_title": "Manga", - "manga_count": "\nCount: ", - "manga_completed": " And completed: ", - "manga_chapter_read": "\nChapters read: ", - "manga_mean_score": "\nMean score: ", - "manga_standard_deviation": "\nStandard deviation: ", - "manga_pref_tag": "\nPreferred tag: ", - "manga_pref_genre": "\nPreferred genre: ", - "anime_title": "Anime", - "anime_count": "\nCount: ", - "anime_completed": " And completed: ", - "anime_time_watch": "\nTime watched: ", - "anime_mean_score": "\nMean score: ", - "anime_standard_deviation": "\nStandard deviation: ", - "anime_pref_tag": "\nPreferred tag: ", - "anime_pref_genre": "\nPreferred genre: ", - "week": " week(s), ", - "day": " day(s), ", - "hour": " hour(s), ", - "minute": " minute(s)", - "error_slash_command": "Can't create slash command" - }, - "Fr": { - "manga_title": "Manga", - "manga_count": "\nNombre : ", - "manga_completed": " Et terminés : ", - "manga_chapter_read": "\nChapitres lus : ", - "manga_mean_score": "\nScore moyen : ", - "manga_standard_deviation": "\nÉcart-type : ", - "manga_pref_tag": "\nTag préféré : ", - "manga_pref_genre": "\nGenre préféré : ", - "anime_title": "Anime", - "anime_count": "\nNombre : ", - "anime_completed": " Et terminés : ", - "anime_time_watch": "\nTemps regardé : ", - "anime_mean_score": "\nScore moyen : ", - "anime_standard_deviation": "\nÉcart-type : ", - "anime_pref_tag": "\nTag préféré : ", - "anime_pref_genre": "\nGenre préféré : ", - "week": " semaine(s), ", - "day": " jour(s), ", - "hour": " heure(s), ", - "minute": " minute(s)", - "error_slash_command": "Impossible de créer la commande slash" - }, - "Jp": { - "manga_title": "マンガ", - "manga_count": "\nカウント:", - "manga_completed": " 完了済み:", - "manga_chapter_read": "\n読んだチャプター数:", - "manga_mean_score": "\n平均スコア:", - "manga_standard_deviation": "\n標準偏差:", - "manga_pref_tag": "\nお気に入りのタグ:", - "manga_pref_genre": "\nお気に入りのジャンル:", - "anime_title": "アニメ", - "anime_count": "\nカウント:", - "anime_completed": " 完了済み:", - "anime_time_watch": "\n視聴時間:", - "anime_mean_score": "\n平均スコア:", - "anime_standard_deviation": "\n標準偏差:", - "anime_pref_tag": "\nお気に入りのタグ:", - "anime_pref_genre": "\nお気に入りのジャンル:", - "week": " 週間、", - "day": " 日、", - "hour": " 時間、", - "minute": " 分", - "error_slash_command": "スラッシュコマンドを作成できません" - }, - "De": { - "manga_title": "Manga", - "manga_count": "\nAnzahl: ", - "manga_completed": " Und abgeschlossen: ", - "manga_chapter_read": "\nGelesene Kapitel: ", - "manga_mean_score": "\nDurchschnittswertung: ", - "manga_standard_deviation": "\nStandardabweichung: ", - "manga_pref_tag": "\nBevorzugter Tag: ", - "manga_pref_genre": "\nBevorzugtes Genre: ", - "anime_title": "Anime", - "anime_count": "\nAnzahl: ", - "anime_completed": " Und abgeschlossen: ", - "anime_time_watch": "\nGeschaute Zeit: ", - "anime_mean_score": "\nDurchschnittswertung: ", - "anime_standard_deviation": "\nStandardabweichung: ", - "anime_pref_tag": "\nBevorzugter Tag: ", - "anime_pref_genre": "\nBevorzugtes Genre: ", - "week": " Woche(n), ", - "day": " Tag(e), ", - "hour": " Stunde(n), ", - "minute": " Minute(n)", - "error_slash_command": "Slash-Befehl kann nicht erstellt werden" - } -} diff --git a/lang_file/embed/error.json b/lang_file/embed/error.json deleted file mode 100644 index c97c3b3b..00000000 --- a/lang_file/embed/error.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "En": { - "error_title": "Error", - "module_off": "Module deactivated", - "forgot_module": "You need to specify the module", - "no_token": "No token specified", - "no_base_url": "Please specify a base URL", - "not_implemented": "This is not implemented yet", - "error_request": "Failed to make the request to the remote server", - "no_url": "There was no URL in the response from the API", - "error_no_avatar": "This user's avatar was not found.", - "error_parsing_json": "Error while parsing a JSON file.", - "error_url": "The response URL was not found", - "error_resolving_value": "Unable to resolve the value of this variable", - "admin_instance_error": "The admin of this instance forgot to specify a model for image generation", - "error_option": "There is an error with the option", - "error_creating_header": "The was an error while creating the header", - "error_getting_response_from_url": "Impossible to download the response from the url", - "error_getting_bytes": "Impossible to get bytes from the downloaded response", - "error_writing_file": "Error while writing file to disk", - "error_file_type": "Wrong file type", - "error_file_extension": "Wrong file extension", - "error_no_anime_specified": "Please specify a valid anime", - "error_not_nsfw": "This channel is not an NSFW channel" - }, - "Fr": { - "error_title": "Erreur", - "module_off": "Module désactivé", - "forgot_module": "Vous devez spécifier le module", - "no_token": "Aucun jeton spécifié", - "no_base_url": "Veuillez spécifier une URL de base", - "not_implemented": "Ceci n'est pas encore implémenté", - "error_request": "Échec de la requête vers le serveur distant", - "no_url": "Il n'y avait pas d'URL dans la réponse de l'API", - "error_no_avatar": "L'avatar de cet utilisateur n'a pas été trouvé.", - "error_parsing_json": "Erreur lors de l'analyse d'un fichier JSON.", - "error_url": "L'URL de réponse n'a pas été trouvée", - "error_resolving_value": "Impossible de résoudre la valeur de cette variable", - "admin_instance_error": "L'administrateur de cette instance a oublié de spécifier un modèle pour la génération d'images", - "error_option": "Il y a une erreur avec l'option", - "error_creating_header": "Il y a eu une erreur lors de la création de l'en-tête", - "error_getting_response_from_url": "Impossible de télécharger la réponse depuis l'URL", - "error_getting_bytes": "Impossible d'obtenir les octets de la réponse téléchargée", - "error_writing_file": "Erreur lors de l'écriture du fichier sur le disque", - "error_file_type": "Type de fichier incorrect", - "error_file_extension": "Extension de fichier incorrecte", - "error_no_anime_specified": "Veuillez spécifier un anime", - "error_not_nsfw": "Ce salon n'est pas un salon NSFW" - }, - "Jp": { - "error_title": "エラー", - "module_off": "モジュールが非アクティブです", - "forgot_module": "モジュールを指定する必要があります", - "no_token": "トークンが指定されていません", - "no_base_url": "ベースURLを指定してください", - "not_implemented": "まだ実装されていません", - "error_request": "リモートサーバーへのリクエストに失敗しました", - "no_url": "APIからの応答にURLが含まれていません", - "error_no_avatar": "このユーザーのアバターが見つかりませんでした。", - "error_parsing_json": "JSONファイルの解析中にエラーが発生しました。", - "error_url": "応答のURLが見つかりませんでした", - "error_resolving_value": "この変数の値を解決できません", - "admin_instance_error": "このインスタンスの管理者が画像生成のためのモデルを指定し忘れました", - "error_option": "オプションにエラーがあります", - "error_creating_header": "ヘッダーを作成する際にエラーが発生しました", - "error_getting_response_from_url": "URLからの応答を取得できません", - "error_getting_bytes": "ダウンロードした応答からバイトを取得できません", - "error_writing_file": "ファイルをディスクに書き込む際にエラーが発生しました", - "error_file_type": "文件类型不正确", - "error_file_extension": "文件扩展名不正确", - "error_no_anime_specified": "アニメを指定してください", - "error_not_nsfw": "このチャンネルはNSFWチャンネルではありません" - }, - "De": { - "error_title": "Fehler", - "module_off": "Modul deaktiviert", - "forgot_module": "Sie müssen das Modul angeben", - "no_token": "Kein Token angegeben", - "no_base_url": "Bitte geben Sie eine Basis-URL an", - "not_implemented": "Dies ist noch nicht implementiert", - "error_request": "Anfrage an den Remote-Server fehlgeschlagen", - "no_url": "In der API-Antwort wurde keine URL gefunden", - "error_no_avatar": "Der Avatar dieses Benutzers wurde nicht gefunden.", - "error_parsing_json": "Fehler beim Analysieren einer JSON-Datei.", - "error_url": "Die Antwort-URL wurde nicht gefunden", - "error_resolving_value": "Der Wert dieser Variable konnte nicht aufgelöst werden", - "admin_instance_error": "Der Administrator dieser Instanz hat vergessen, ein Modell für die Bildgenerierung anzugeben", - "error_option": "Es liegt ein Fehler bei der Option vor", - "error_creating_header": "Beim Erstellen des Headers ist ein Fehler aufgetreten", - "error_getting_response_from_url": "Die Antwort von der URL konnte nicht abgerufen werden", - "error_getting_bytes": "Die Bytes aus der heruntergeladenen Antwort konnten nicht erhalten werden", - "error_writing_file": "Beim Schreiben der Datei auf die Festplatte ist ein Fehler aufgetreten", - "error_file_type": "Falscher Dateityp", - "error_file_extension": "Falsche Dateierweiterung", - "error_no_anime_specified": "Bitte einen Anime angeben", - "error_not_nsfw": "Dieser Kanal ist kein NSFW-Kanal" - } -} \ No newline at end of file diff --git a/lang_file/embed/general/avatar.json b/lang_file/embed/general/avatar.json deleted file mode 100644 index eaacb11f..00000000 --- a/lang_file/embed/general/avatar.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "En": { - "title": "Here's the avatar of ", - "no_banner_title": "No avatar", - "error_no_user": "Error getting user" - }, - "Fr": { - "title": "Voici l'avatar de ", - "no_banner_title": "Pas d'avatar", - "error_no_user": "Erreur lors de l'obtention de l'utilisateur" - }, - "De": { - "title": "Hier ist das Avatar von ", - "no_banner_title": "Kein Avatar", - "error_no_user": "Fehler beim Abrufen des Benutzers" - }, - "Jp": { - "title": "こちらがのアバターです ", - "no_banner_title": "アバターなし", - "error_no_user": "ユーザーの取得エラー" - } -} \ No newline at end of file diff --git a/lang_file/embed/general/banner.json b/lang_file/embed/general/banner.json deleted file mode 100644 index f6192b42..00000000 --- a/lang_file/embed/general/banner.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "En": { - "error_slash_command": "Can't create slash command", - "title": "Here's the banner of ", - "description": "This user does not have a banner", - "no_banner_title": "No banner", - "error_no_user": "Error getting user" - }, - "Fr": { - "error_slash_command": "Impossible de créer la commande slash", - "title": "Voici la bannière de ", - "description": "Cet utilisateur n'a pas de bannière", - "no_banner_title": "Pas de bannière", - "error_no_user": "Erreur lors de la récupération de l'utilisateur" - }, - "De": { - "error_slash_command": "Slash-Befehl kann nicht erstellt werden", - "title": "Hier ist das Banner von ", - "description": "Dieser Benutzer hat kein Banner", - "no_banner_title": "Kein Banner", - "error_no_user": "Fehler beim Abrufen des Benutzers" - }, - "Jp": { - "error_slash_command": "スラッシュコマンドを作成できません", - "title": "以下は、", - "description": "このユーザーはバナーを持っていません", - "no_banner_title": "バナーなし", - "error_no_user": "ユーザーの取得中にエラーが発生しました" - } -} diff --git a/lang_file/embed/general/credit.json b/lang_file/embed/general/credit.json deleted file mode 100644 index 3644822d..00000000 --- a/lang_file/embed/general/credit.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "En": { - "title": "credit", - "list": [ - { - "text": "Valgul: Main dev / original creator / french and english translation \n" - }, - { - "text": "Sionnakh: Dev of the website. \n" - } - ] - }, - "Fr": { - "title": "crédits", - "list": [ - { - "text": "Valgul : Développeur principal / Créateur original / Traduction française et anglaise \n" - }, - { - "text": "Sionnakh : Développeur du site web. \n" - } - ] - }, - "Jp": { - "title": "クレジット", - "list": [ - { - "text": "バルガル:メインデベロッパー/オリジナルクリエイター/フランス語と英語の翻訳 \n" - }, - { - "text": "Sionnakh:ウェブサイトの開発者。 \n" - } - ] - }, - "De": { - "title": "Credits", - "list": [ - { - "text": "Valgul: Hauptentwickler / Originaler Schöpfer / Französische und englische Übersetzung \n" - }, - { - "text": "Sionnakh: Entwickler der Website. \n" - } - ] - } -} \ No newline at end of file diff --git a/lang_file/embed/general/in_progress.json b/lang_file/embed/general/in_progress.json deleted file mode 100644 index dce8631d..00000000 --- a/lang_file/embed/general/in_progress.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "En": { - "title": "In progress", - "description": "The task is being processed... Please be patient, as it may take some time!" - }, - "Fr": { - "title": "En cours", - "description": "La tâche est en cours de traitement... Veuillez patienter, car cela peut prendre du temps !" - }, - "De": { - "title": "In Bearbeitung", - "description": "Die Aufgabe wird verarbeitet... Bitte haben Sie Geduld, da dies einige Zeit dauern kann!" - }, - "Jp": { - "title": "処理中", - "description": "タスクは処理中です... しばらくお待ちください。" - } -} diff --git a/lang_file/embed/general/info.json b/lang_file/embed/general/info.json deleted file mode 100644 index 26117333..00000000 --- a/lang_file/embed/general/info.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "En": { - "title": "Info", - "description": "This bot uses the AniList API to provide information on a show or a user.", - "server_specific_info": "Here's some information about this server's configuration:", - "footer": "Creator: valgul.", - "button_see_on_github": "See on GitHub", - "button_official_website": "Official website", - "button_official_discord": "Official Discord", - "button_add_the_bot": "Add the bot", - "on": "on", - "off": "off" - }, - "Fr": { - "title": "Informations", - "description": "Ce bot utilise l'API AniList pour fournir des informations sur un spectacle ou un utilisateur.", - "server_specific_info": "Voici quelques informations sur la configuration de ce serveur :", - "footer": "Créateur : valgul.", - "button_see_on_github": "Voir sur GitHub", - "button_official_website": "Site Web officiel", - "button_official_discord": "Discord officiel", - "button_add_the_bot": "Ajouter le bot", - "on": "activé", - "off": "désactivé" - }, - "De": { - "title": "Info", - "description": "Dieser Bot verwendet die AniList API, um Informationen zu einer Show oder einem Benutzer bereitzustellen.", - "server_specific_info": "Hier sind einige Informationen zur Konfiguration dieses Servers:", - "footer": "Ersteller: valgul.", - "button_see_on_github": "Auf GitHub ansehen", - "button_official_website": "Offizielle Website", - "button_official_discord": "Offizieller Discord", - "button_add_the_bot": "Bot hinzufügen", - "on": "aktiviert", - "off": "deaktiviert" - }, - "Jp": { - "title": "情報", - "description": "このボットはAniList APIを使用して番組やユーザーの情報を提供します。", - "server_specific_info": "こちらはこのサーバーの設定に関する情報です:", - "footer": "作成者:valgul.", - "button_see_on_github": "GitHubで見る", - "button_official_website": "公式ウェブサイト", - "button_official_discord": "公式Discord", - "button_add_the_bot": "ボットを追加", - "on": "オン", - "off": "オフ" - } -} diff --git a/lang_file/embed/general/lang.json b/lang_file/embed/general/lang.json deleted file mode 100644 index ac313dd7..00000000 --- a/lang_file/embed/general/lang.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "En": { - "title": "Lang", - "description": "The bot language was set to: ", - "error_perm": "The user does not have the required permission to change this." - }, - "Fr": { - "title": "Langue", - "description": "La langue du bot a été définie sur : ", - "error_perm": "L'utilisateur n'a pas l'autorisation requise pour effectuer ce changement." - }, - "Jp": { - "title": "言語", - "description": "ボットの言語が設定されました:", - "error_perm": "ユーザーには、この変更を行うための必要な権限がありません。" - }, - "De": { - "title": "Sprache", - "description": "Die Botsprache wurde festgelegt auf: ", - "error_perm": "Der Benutzer hat nicht die erforderliche Berechtigung, um dies zu ändern." - } -} diff --git a/lang_file/embed/general/module_activation.json b/lang_file/embed/general/module_activation.json deleted file mode 100644 index dc002e89..00000000 --- a/lang_file/embed/general/module_activation.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "En": { - "on": "Le module a été activé sur le serveur.", - "off": "Le module a été désactivé sur le serveur." - }, - "Fr": { - "on": "Le module a été activé sur le serveur.", - "off": "Le module a été désactivé sur le serveur." - }, - "Jp": { - "on": "モジュールはサーバーでアクティブになりました。", - "off": "モジュールはサーバーで非アクティブになりました。" - }, - "De": { - "on": "Das Modul wurde auf dem Server aktiviert.", - "off": "Das Modul wurde auf dem Server deaktiviert." - } -} diff --git a/lang_file/embed/general/ping.json b/lang_file/embed/general/ping.json deleted file mode 100644 index 20f302e1..00000000 --- a/lang_file/embed/general/ping.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "En": { - "title": "Ping", - "description_part_1": "Hey, I'm alive! \n ", - "description_part_2": "You are running on shard: ", - "description_part_3": "\n Latency is: " - }, - "Fr": { - "title": "Ping", - "description_part_1": "Hey, je suis en ligne ! \n ", - "description_part_2": "Vous êtes connecté sur le fragment : ", - "description_part_3": "\n La latence est de : " - }, - "Jp": { - "title": "Ping", - "description_part_1": "こんにちは、私は生きています!\n ", - "description_part_2": "シャード上で実行中です: ", - "description_part_3": "\n レイテンシは: " - }, - "De": { - "title": "Ping", - "description_part_1": "Hey, ich lebe! \n ", - "description_part_2": "Du bist auf Shard: ", - "description_part_3": "\n Die Latenz beträgt: " - } -} diff --git a/lang_file/embed/general/profile.json b/lang_file/embed/general/profile.json deleted file mode 100644 index cb34cd31..00000000 --- a/lang_file/embed/general/profile.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "En": { - "title": "Here's the profile of ", - "error_no_user": "Error getting user", - "user_id": "This user id is: ", - "is_bot": "Is this user a bot? ", - "public_flag": "Here are the public flags: ", - "joined_at": "Here's the join date: ", - "created_at": "Here's the account creation date: " - }, - "Fr": { - "title": "Voici le profil de ", - "error_no_user": "Erreur lors de la récupération de l'utilisateur", - "user_id": "Cet identifiant d'utilisateur est : ", - "is_bot": "Cet utilisateur est-il un bot ? ", - "public_flag": "Voici les indicateurs publics : ", - "joined_at": "Voici la date d'adhésion : ", - "created_at": "Voici la date de création du compte : " - }, - "De": { - "title": "Hier ist das Profil von ", - "error_no_user": "Fehler beim Abrufen des Benutzers", - "user_id": "Diese Benutzer-ID lautet: ", - "is_bot": "Ist dieser Benutzer ein Bot? ", - "public_flag": "Hier sind die öffentlichen Flags: ", - "joined_at": "Hier ist das Beitrittsdatum: ", - "created_at": "Hier ist das Kontoerstellungsdatum: " - }, - "Jp": { - "title": "以下はプロフィールです:", - "error_no_user": "ユーザーの取得中にエラーが発生しました", - "user_id": "このユーザーのIDは:", - "is_bot": "このユーザーはボットですか?", - "public_flag": "ここに公開フラグがあります:", - "joined_at": "ここに参加日があります:", - "created_at": "ここにアカウント作成日があります:" - } -} diff --git a/readme.md b/readme.md index f420327c..7eb0bed7 100644 --- a/readme.md +++ b/readme.md @@ -3,114 +3,29 @@ [![Rust Clippy](https://github.com/ValgulNecron/kasuki/actions/workflows/linting.yml/badge.svg?branch=master)](https://github.com/ValgulNecron/kasuki/actions/workflows/linting.yml) [![Rust Testing](https://github.com/ValgulNecron/kasuki/actions/workflows/testing.yml/badge.svg)](https://github.com/ValgulNecron/kasuki/actions/workflows/testing.yml) ![Code Activity](https://img.shields.io/github/commit-activity/w/valgulnecron/kasuki/master?style=plastic) +![Dev Code Activity](https://img.shields.io/github/commit-activity/w/valgulnecron/kasuki/dev?style=plastic&label=Dev) -# TODO - - -## BOT - -- General part: - - [X] Change how the text is displayed to support localization. — Done. - - [ ] Clean the code. — It will never be done. - - [X] Find a name for the bot. — Found one kasuki. if you have any better, don't hesitate to recommend. - - [x] Add a bdd for some stuff prob sqlite but not sure. — Added sqlite db. (change to PostgreSQL) - — will add a var variable that will either be "sqlite" or "postgresql" with a url var for PostgreSQL - — will need to do some change and move db logic to it's own funtion. - - [X] Banner. Show your or a specified user banner. — Done. - - [X] Profil. Show a user profile and some info. — Done. - - [ ] Avatar. show you the profile picture of a user. — In progress - - [X] Add support to turn on and off module. — Done. - - [X] Create a parser because some desc uses html and not markdown. — Done. Will need to check to be sure all is - done. - - [ ] Poll feature with custom choice and a graph afterward for comparison. - - [X] Figure out the necessary deps to work. Once found, change the dockerfile to use a debian base image to reduce - size — Done. - - [ ] Better error handling. - Different error messages, - type and replies everytime not in certain condition. - — working on it. - Should be okay believe there is still some risk of panic will need to see in the long term. - - [ ] Localisation for response — Done except for some other "minor" stuff. Lazy to continue will need some help to - proofread the json file and continue to complete the not finished one. - - [ ] Localisation for command — Working on it. Compare will be done later. Lazy to continue will need some help to - proofread the json file and continue to complete the not finished one. - - [ ] Rename function, structure, command name etc... so it makes more sense. Started lazy to continue - - [ ] Add docs to every public function. (Run, Register and Autocomplete do not need this.) - -- Anime submodule: - - [X] Finish comparison function. - — V1 done. — Will need to be better, but it works. - Ideas of improvement add a score on - how close two people are (affinity score). - — And have better formatting for the text. - - [X] Add character search function. — Added character research with name. - - [X] Add staff search function. — Added staff research with name. - - [X] Add search feature with a type. — Work for all. - - [X] Bind anilist account to discord for /user. — Added register command and edited user command. - - [X] Random /random {anime, manga}. — Added random for both anime and manga. Manga random can give ln. - - [ ] Rework the xp in struct_level to something easier. — Too lazy to balance - - [X] Add caching to all requests. — Done now will need to rework random.rs cause it double cache. 3days cache - except - for "high" priority request like user data. - - [X] Send anime release to a channel. - — Done - - [ ] List all activity - - [ ] Delete an activity. - - [ ] Try to do the same for manga. - with [https://www.mangaupdates.com/series.html?id=70263](https://www.mangaupdates.com/series.html?id=70263) - (for - this one only selected manga not all seasonal). - — Did some digging seem possible. I will do anime-first trough. - - [ ] Activity command (auto sends activity of a user to a channel). - — Same as anime, but this one will be hard since - a user can do update every second like every year. Will either have delay or be resource intensive. - - [X] Add a "delay" option to delay notification. - — (like 1h for a translation). - — Need anime notification first. - it will be easy once anime is done. On wait but will be done - - [ ] Take [https://anilist.co/forum/thread/64835](https://anilist.co/forum/thread/64835) - idea of generating image - with a seiyuu and va role. - — This is possible, I'm not competent enough. - - [ ] Get all the register users of the server. - Working on it after finishing anime activity. - - [X] Add studio search. - - [X] Add commands that give the best waifu. — Done. - -- AI submodule: - - [X] Image generation with AI. — Done. - - [X] Video transcription. — Done. - - [X] Video translation. — Done. - - [ ] Ask a question and reply the response. — Not a priority. - -- Moderation submodule: - - [ ] General moderation stuff - -- Games module: - - [ ] get game price from different platform (ubi, steam, epic, ea, etc....) - - [ ] get player stat - - [ ] get free promotion notification - -## Website - -for those of you who prefer web dev.\ -[https://github.com/ValgulNecron/kasuki_website](https://github.com/ValgulNecron/kasuki_website) # Vision + The bot is in the first place, a bot that interfaces discord and the anilist api, letting users get different information from it. There are also multiple secondary modules that will be added when I have ideas or want to test things. + # Contributing + ## I know how to code in rust + Then please check the todo and follow CONTRIBUTING.md to add feature if the todo is complete, or you want to do something else, just do it and open a pr afterward. + ## I don't know how to code in rust but still want to contribute 1. You can add new langage by adding a translation in the file located in lang_file and adding it to @@ -125,23 +40,30 @@ ISO-639-3 code for mandarin chinese (cmn) and added a p for pinyin if working on command_register please use the same structure, but the "code" field should respect discord locale https://discord.com/developers/docs/reference#locales + # How to use + ## 1. Add the bot to your server + you can add my instance of the bot with [this link](https://discord.com/api/oauth2/authorize?client_id=923286536445894697&permissions=533113194560&scope=bot) + ## 2. Self-host your instance + ### tested on: + linux: ubuntu 22.04.2 x86-64 Requirement: libssl-dev libsqlite3-dev libpng-dev libjpeg-dev ca-certificates windows: windows 10 and 11 + ### Docker - Install docker and docker compose. @@ -185,31 +107,110 @@ git clone https://github.com/ValgulNecron/DIscordAnilistBotRS.git cargo run --release ``` -# Commands - -## /!\ Not updated /!\ - -- General: - - /info - Show info about bot. - - /ping - Check if the bot responds to command. - - /lang - let you change the langage for your guild. require admin perm. -- Anime: - - /anime - Show info about anime. - - /character - Show info on a character. - - /compare - Compare 2 different user. - - /level - Show your level based on what you read and watched. - - /ln - Show info about light novel. - - /manga - Show info about manga. - - /random - Give a random anime or manga. - - /register - Link your anilist and discord account. - - /search - Let you search for a different type. Like ln, manga, etc... - - /staff - Give information about a specified staff. - - /user - Show info about user. -- AI: - - /image - Generate an image from a description. - - /transcript - Transcript a video or an audio file with a size limit of 25mb. - - /translation - — Create a translated transcript of video or an audio file with a size limit of 25mb. +# TODO + + +## BOT + +- General part: + - [X] Change how the text is displayed to support localization. — Done. + - [X] Clean the code. — It will never be done. + can be called done for now. + - [X] Find a name for the bot. — Found one kasuki. if you have any better, don't hesitate to recommend. + - [x] Add a bdd for some stuff prob sqlite but not sure. — Added sqlite db. (change to PostgreSQL) + — will add a var variable that will either be "sqlite" or "postgresql" with a url var for PostgreSQL + — will need to do some change and move db logic to it's own funtion. + - [X] Banner. Show your or a specified user banner. — Done. + - [X] Profil. Show a user profile and some info. — Done. + - [X] Avatar. show you the profile picture of a user. — Done + - [X] Add support to turn on and off module. — Done. + - [X] Create a parser because some desc uses html and not markdown. — Done. Will need to check to be sure all is + done. + - [ ] Poll feature with custom choice and a graph afterward for comparison. + - [X] Figure out the necessary deps to work. Once found, change the dockerfile to use a debian base image to reduce + size — Done. + - [ ] Better error handling. + Different error messages, + type and replies everytime not in certain condition. + — working on it. + Should be okay believe there is still some risk of panic will need to see in the long term. + - [ ] Localisation for response — Done except for some other "minor" stuff. Lazy to continue will need some help to + proofread the json file and continue to complete the not finished one. + - [ ] Localisation for command — Working on it. Compare will be done later. Lazy to continue will need some help to + proofread the json file and continue to complete the not finished one. + - [ ] Rename function, structure, command name etc... so it makes more sense. Doing it should be good atm. + - [ ] Add docs to every public function. (Run, Register and Autocomplete do not need this.). started and clearly not + finished. + - [X] Logging + - [X] Updating to serenity 0.12 — Working on it. + - [X] Support for command in dm (would need a rewrite on the langage part to default to en even when there is no guild + id) — Now working. + (for the command I want to be on in dm) + - [ ] Make an anilist forum post (when v2 is done); + +- Anime submodule: + - [X] Finish comparison function. + — V1 done. — Will need to be better, but it works. + Ideas of improvement add a score on + how close two people are (affinity score). + — And have better formatting for the text. + - [X] Add character search function. — Added character research with name. + - [X] Add staff search function. — Added staff research with name. + - [X] Add search feature with a type. — Work for all. + - [X] Bind anilist account to discord for /user. — Added register command and edited user command. + - [X] Random /random {anime, manga}. — Added random for both anime and manga. Manga random can give ln. + - [ ] Rework the xp in struct_level to something easier. — Too lazy to balance + - [X] Add caching to all requests. — Done now will need to rework random.rs cause it double cache. 3days cache + except + for "high" priority request like user data. + - [X] Send anime release to a channel. + — Done + - [ ] List all activity + - [ ] Delete an activity. + - [ ] Try to do the same for manga. + with [https://www.mangaupdates.com/series.html?id=70263](https://www.mangaupdates.com/series.html?id=70263) + (for + this one only selected manga not all seasonal). + — Did some digging seem possible. I will do anime-first trough. + - [ ] Activity command (auto sends activity of a user to a channel). + — Same as anime, but this one will be hard since + a user can do update every second like every year. Will either have delay or be resource intensive. + - [X] Add a "delay" option to delay notification. + — (like 1h for a translation). + — Need anime notification first. + it will be easy once anime is done. On wait but will be done + - [X] Take [https://anilist.co/forum/thread/64835](https://anilist.co/forum/thread/64835) + idea of generating image + with a seiyuu and va role. + — This is possible, I'm not competent enough. + — Done + - [ ] Get all the register users of the server. + Working on it after finishing anime activity. + - [X] Add studio search. + - [X] Add commands that give the best waifu. — Done. + +- AI submodule: + - [X] Image generation with AI. — Done. + - [X] Video transcription. — Done. + - [X] Video translation. — Done. + - [ ] Ask a question and reply the response. — Not a priority. + +- Moderation submodule: + - [ ] General moderation stuff + +- Games module: + - [ ] get game price from different platform (ubi, steam, epic, ea, etc....) + Set "server" curency. + Get game price + - [ ] get player stat + - [ ] get free promotion notification + +## Website + + +for those of you who prefer web dev.\ +[https://github.com/ValgulNecron/kasuki_website](https://github.com/ValgulNecron/kasuki_website) + # Credit diff --git a/src/activity/anime_activity.rs b/src/activity/anime_activity.rs new file mode 100644 index 00000000..a1a332cd --- /dev/null +++ b/src/activity/anime_activity.rs @@ -0,0 +1,106 @@ +use std::env; +use std::time::Duration; + +use chrono::Utc; +use serenity::all::{CreateEmbed, ExecuteWebhook, Http, Webhook}; +use tokio::time::sleep; + +use crate::anilist_struct::run::minimal_anime::{ActivityData, MinimalAnimeWrapper}; +use crate::constant::{COLOR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::anilist::send_activity::load_localization_send_activity; +use crate::sqls::general::data::{get_data_activity, set_data_activity}; + +pub async fn manage_activity() { + loop { + tokio::spawn(async move { send_activity().await }); + sleep(Duration::from_secs(1)).await; + } +} + +pub async fn send_activity() { + let now = Utc::now().timestamp().to_string(); + let rows = get_data_activity(now.clone()).await.unwrap(); + for row in rows { + if Utc::now().timestamp().to_string() != row.timestamp.clone().unwrap() { + } else { + let row2 = row.clone(); + let guild_id = row.server_id.clone(); + if row.delays.unwrap() != 0 { + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs((row2.delays.unwrap()) as u64)).await; + send_specific_activity(row, guild_id.unwrap(), row2) + .await + .unwrap() + }); + } else { + send_specific_activity(row, guild_id.unwrap(), row2) + .await + .unwrap() + } + } + } +} + +pub async fn send_specific_activity( + row: ActivityData, + guild_id: String, + row2: ActivityData, +) -> Result<(), AppError> { + let localised_text = load_localization_send_activity(guild_id.clone()) + .await + .unwrap(); + let my_path = "./.env"; + let path = std::path::Path::new(my_path); + let _ = dotenv::from_path(path); + let token = env::var("DISCORD_TOKEN").expect("discord token"); + let http = Http::new(token.as_str()); + let webhook = Webhook::from_url(&http, row.webhook.clone().unwrap().as_ref()) + .await + .unwrap(); + let embed = CreateEmbed::new() + .color(COLOR) + .description( + &localised_text + .desc + .replace("$ep$", row.episode.unwrap_or(String::from("0")).as_str()) + .replace("$anime$", row.name.unwrap_or(String::from("none")).as_str()), + ) + .url(format!( + "https://anilist.co/anime/{}", + row.anime_id.unwrap_or(String::from("0")) + )) + .title(&localised_text.title); + + let builder_message = ExecuteWebhook::new().embed(embed); + + webhook + .execute(&http, false, builder_message) + .await + .unwrap(); + tokio::spawn(async move { update_info(row2, guild_id).await }); + Ok(()) +} + +pub async fn update_info(row: ActivityData, guild_id: String) -> Result<(), AppError> { + let data = MinimalAnimeWrapper::new_minimal_anime_by_id( + row.anime_id.clone().ok_or(OPTION_ERROR.clone())?, + ) + .await?; + let media = data.data.media; + let next_airing = media.next_airing_episode.ok_or(OPTION_ERROR.clone())?; + let title = media.title.ok_or(OPTION_ERROR.clone())?; + let rj = title.romaji; + let en = title.english; + let name = en.unwrap_or(rj.unwrap_or(String::from("nothing"))); + set_data_activity( + media.id, + next_airing.airing_at.unwrap(), + guild_id, + row.webhook.unwrap(), + next_airing.episode.unwrap(), + name.clone(), + row.delays.unwrap_or(0) as i64, + ) + .await +} diff --git a/src/activity/mod.rs b/src/activity/mod.rs new file mode 100644 index 00000000..3eb8ca7d --- /dev/null +++ b/src/activity/mod.rs @@ -0,0 +1 @@ +pub mod anime_activity; diff --git a/src/anilist_struct/autocomplete/character.rs b/src/anilist_struct/autocomplete/character.rs new file mode 100644 index 00000000..80772c63 --- /dev/null +++ b/src/anilist_struct/autocomplete/character.rs @@ -0,0 +1,57 @@ +use crate::constant::AUTOCOMPLETE_COUNT; +use serde::Deserialize; +use serde_json::json; + +use crate::common::make_anilist_request::make_request_anilist; + +#[derive(Debug, Deserialize, Clone)] +pub struct AutocompleteName { + pub full: String, + #[serde(rename = "userPreferred")] + pub user_preferred: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct AutocompleteCharacter { + pub id: u32, + pub name: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct CharacterPage { + pub characters: Option>>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct CharacterPageData { + #[serde(rename = "Page")] + pub page: CharacterPage, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct CharacterPageWrapper { + pub data: CharacterPageData, +} + +impl CharacterPageWrapper { + pub async fn new_autocomplete_character(search: &String) -> CharacterPageWrapper { + let query_str = "query ($search: String, $count: Int) { + Page(perPage: $count) { + characters(search: $search) { + id + name { + full + } + } + } + } + "; + let json = json!({"query": query_str, "variables": { + "search": search, + "count": AUTOCOMPLETE_COUNT, + }}); + let res = make_request_anilist(json, true).await; + let data: CharacterPageWrapper = serde_json::from_str(&res).unwrap(); + data + } +} diff --git a/src/anilist_struct/autocomplete/media.rs b/src/anilist_struct/autocomplete/media.rs new file mode 100644 index 00000000..dd7bd16e --- /dev/null +++ b/src/anilist_struct/autocomplete/media.rs @@ -0,0 +1,134 @@ +use crate::constant::AUTOCOMPLETE_COUNT; +use serde::Deserialize; +use serde_json::json; +use serenity::all::{ + AutocompleteChoice, CommandInteraction, Context, CreateAutocompleteResponse, + CreateInteractionResponse, +}; + +use crate::common::make_anilist_request::make_request_anilist; + +#[derive(Debug, Deserialize, Clone)] +pub struct AutocompleteTitle { + pub romaji: String, + pub english: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct AutocompleteMedia { + pub id: u32, + pub title: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct MediaPage { + pub media: Option>>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct MediaPageData { + #[serde(rename = "Page")] + pub page: MediaPage, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct MediaPageWrapper { + pub data: MediaPageData, +} + +impl MediaPageWrapper { + pub async fn new_autocomplete_anime(search: &String) -> MediaPageWrapper { + let query_str = + "query($search: String, $type: MediaType, $count: Int, $format: MediaFormat) { + Page(perPage: $count) { + media(search: $search, type: $type, format_not: $format) { + id + title { + romaji + english + } + } + } + }"; + let json = json!({"query": query_str, "variables": { + "search": search, + "type": "ANIME", + "count": AUTOCOMPLETE_COUNT, + }}); + + let res = make_request_anilist(json, true).await; + let data: MediaPageWrapper = serde_json::from_str(&res).unwrap(); + data + } + + pub async fn new_autocomplete_manga(search: &String) -> MediaPageWrapper { + let query_str = + "query($search: String, $type: MediaType, $count: Int, $format: MediaFormat) { + Page(perPage: $count) { + media(search: $search, type: $type, format_not: $format) { + id + title { + romaji + english + } + } + } + }"; + let json = json!({"query": query_str, "variables": { + "search": search, + "type": "MANGA", + "count": AUTOCOMPLETE_COUNT, + "format": "NOVEL" + }}); + + let res = make_request_anilist(json, true).await; + let data: MediaPageWrapper = serde_json::from_str(&res).unwrap(); + data + } + + pub async fn new_autocomplete_ln(search: &String) -> MediaPageWrapper { + let query_str = + "query($search: String, $type: MediaType, $count: Int, $format: MediaFormat) { + Page(perPage: $count) { + media(search: $search, type: $type, format: $format) { + id + title { + romaji + english + } + } + } + }"; + let json = json!({"query": query_str, "variables": { + "search": search, + "type": "MANGA", + "count": AUTOCOMPLETE_COUNT, + "format": "NOVEL" + }}); + + let res = make_request_anilist(json, true).await; + let data: MediaPageWrapper = serde_json::from_str(&res).unwrap(); + data + } +} + +pub async fn send_auto_complete( + ctx: Context, + command: CommandInteraction, + media: MediaPageWrapper, +) { + let mut choices = Vec::new(); + for page in media.data.page.media.unwrap() { + let data = page.unwrap(); + let title_data = data.title.unwrap(); + let english = title_data.english; + let romaji = title_data.romaji; + let title = english.unwrap_or(romaji); + choices.push(AutocompleteChoice::new(title, data.id.to_string())) + } + + let data = CreateAutocompleteResponse::new().set_choices(choices); + let builder = CreateInteractionResponse::Autocomplete(data); + + let _ = command.create_response(ctx.http, builder).await; +} diff --git a/src/anilist_struct/autocomplete/mod.rs b/src/anilist_struct/autocomplete/mod.rs new file mode 100644 index 00000000..93735519 --- /dev/null +++ b/src/anilist_struct/autocomplete/mod.rs @@ -0,0 +1,5 @@ +pub mod character; +pub mod media; +pub mod staff; +pub mod studio; +pub mod user; diff --git a/src/structure/anilist/staff/struct_autocomplete_staff.rs b/src/anilist_struct/autocomplete/staff.rs similarity index 54% rename from src/structure/anilist/staff/struct_autocomplete_staff.rs rename to src/anilist_struct/autocomplete/staff.rs index 87b1520d..f63226b9 100644 --- a/src/structure/anilist/staff/struct_autocomplete_staff.rs +++ b/src/anilist_struct/autocomplete/staff.rs @@ -1,7 +1,8 @@ -use crate::function::requests::request::make_request_anilist; -use crate::structure::anilist::struct_autocomplete::AutocompleteOption; +use crate::constant::AUTOCOMPLETE_COUNT; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::json; + +use crate::common::make_anilist_request::make_request_anilist; #[derive(Debug, Deserialize, Clone)] pub struct AutocompleteName { @@ -33,7 +34,7 @@ pub struct StaffPageWrapper { } impl StaffPageWrapper { - pub async fn new_autocomplete_staff(search: &Value, count: i32) -> StaffPageWrapper { + pub async fn new_autocomplete_staff(search: &String) -> StaffPageWrapper { let query_str = "query ($search: String, $count: Int) { Page(perPage: $count) { staff(search: $search) { @@ -47,32 +48,11 @@ impl StaffPageWrapper { }"; let json = json!({"query": query_str, "variables": { "search": search, - "count": count, + "count": AUTOCOMPLETE_COUNT, }}); let res = make_request_anilist(json, true).await; let data: StaffPageWrapper = serde_json::from_str(&res).unwrap(); data } - - pub fn get_choice(&self) -> Vec { - if let Some(users) = &self.data.page.staff { - users - .iter() - .filter_map(|item| { - item.as_ref().map(|item| AutocompleteOption { - name: item - .name - .user_preferred - .as_ref() - .unwrap_or(&item.name.full) - .to_string(), - value: item.id.to_string(), - }) - }) - .collect::>() - } else { - vec![] - } - } } diff --git a/src/anilist_struct/autocomplete/studio.rs b/src/anilist_struct/autocomplete/studio.rs new file mode 100644 index 00000000..2a8a43d9 --- /dev/null +++ b/src/anilist_struct/autocomplete/studio.rs @@ -0,0 +1,47 @@ +use crate::constant::AUTOCOMPLETE_COUNT; +use serde::Deserialize; +use serde_json::json; + +use crate::common::make_anilist_request::make_request_anilist; + +#[derive(Debug, Deserialize, Clone)] +pub struct AutocompleteStudio { + pub id: u32, + pub name: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StudioPage { + pub studios: Option>>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StudioPageData { + #[serde(rename = "Page")] + pub page: StudioPage, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StudioPageWrapper { + pub data: StudioPageData, +} + +impl StudioPageWrapper { + pub async fn new_autocomplete_staff(search: &String) -> StudioPageWrapper { + let query_str = "query ($search: String, $count: Int) { + Page(perPage: $count) { + studios(search: $search) { + id + name + } + } + }"; + let json = json!({"query": query_str, "variables": { + "search": search, + "count": AUTOCOMPLETE_COUNT, + }}); + + let res = make_request_anilist(json, true).await; + serde_json::from_str(&res).unwrap() + } +} diff --git a/src/anilist_struct/autocomplete/user.rs b/src/anilist_struct/autocomplete/user.rs new file mode 100644 index 00000000..aa48146c --- /dev/null +++ b/src/anilist_struct/autocomplete/user.rs @@ -0,0 +1,48 @@ +use crate::constant::AUTOCOMPLETE_COUNT; +use serde::Deserialize; +use serde_json::json; + +use crate::common::make_anilist_request::make_request_anilist; + +#[derive(Debug, Deserialize, Clone)] +pub struct AutocompleteUser { + pub id: u32, + pub name: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct UserPage { + pub users: Option>>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct UserPageData { + #[serde(rename = "Page")] + pub page: UserPage, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct UserPageWrapper { + pub data: UserPageData, +} + +impl UserPageWrapper { + pub async fn new_autocomplete_user(search: &String) -> UserPageWrapper { + let query_str = "query ($search: String, $count: Int) { + Page(perPage: $count) { + users(search: $search) { + id + name + } + } + }"; + let json = json!({"query": query_str, "variables": { + "search": search, + "count": AUTOCOMPLETE_COUNT, + }}); + + let res = make_request_anilist(json, true).await; + let data: UserPageWrapper = serde_json::from_str(&res).unwrap(); + data + } +} diff --git a/src/anilist_struct/mod.rs b/src/anilist_struct/mod.rs new file mode 100644 index 00000000..50897930 --- /dev/null +++ b/src/anilist_struct/mod.rs @@ -0,0 +1,2 @@ +pub mod autocomplete; +pub mod run; diff --git a/src/anilist_struct/run/character.rs b/src/anilist_struct/run/character.rs new file mode 100644 index 00000000..63dd67eb --- /dev/null +++ b/src/anilist_struct/run/character.rs @@ -0,0 +1,222 @@ +use serde::Deserialize; +use serde_json::json; +use serenity::all::{ + CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; + +use crate::common::html_parser::convert_to_discord_markdown; +use crate::common::make_anilist_request::make_request_anilist; +use crate::common::trimer::trim; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::MediaGettingError; +use crate::lang_struct::anilist::character::load_localization_character; + +#[derive(Debug, Deserialize, Clone)] +pub struct CharacterWrapper { + pub data: CharacterData, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct CharacterData { + #[serde(rename = "Character")] + pub character: Character, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Character { + pub id: u32, + pub name: Name, + #[serde(rename = "siteUrl")] + pub site_url: String, + pub description: String, + pub gender: String, + pub age: String, + #[serde(rename = "dateOfBirth")] + pub date_of_birth: DateOfBirth, + pub image: Image, + pub favourites: u32, + #[serde(rename = "modNotes")] + pub mod_notes: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Name { + pub full: String, + pub native: String, + #[serde(rename = "userPreferred")] + pub user_preferred: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct DateOfBirth { + pub year: Option, + pub month: Option, + pub day: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Image { + pub large: String, +} + +impl CharacterWrapper { + pub async fn new_character_by_id(value: i32) -> Result { + let query_id: &str = " + query ($name: Int) { + Character(id: $name) { + id + name { + full + native + userPreferred + } + siteUrl + description + gender + age + dateOfBirth { + year + month + day + } + image { + large + } + favourites + modNotes + } + } + "; + let json = json!({"query": query_id, "variables": {"name": value}}); + let resp = make_request_anilist(json, false).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_character_by_search(value: &String) -> Result { + let query_string: &str = " +query ($name: String) { + Character(search: $name) { + id + name { + full + native + userPreferred + } + siteUrl + description + gender + age + dateOfBirth { + year + month + day + } + image { + large + } + favourites + modNotes + } +} +"; + let json = json!({"query": query_string, "variables": {"name": value}}); + let resp = make_request_anilist(json, false).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } +} + +pub async fn send_embed( + ctx: &Context, + command: &CommandInteraction, + data: CharacterWrapper, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let character = data.data.character.clone(); + + let character_localised = load_localization_character(guild_id).await?; + + let dob_data = character.date_of_birth.clone(); + let mut dob_string = String::new(); + + let mut mo: bool = false; + let mut da: bool = false; + + match dob_data.month { + Some(m) => { + dob_string.push_str(format!("{:02}", m).as_str()); + mo = true + } + None => {} + } + + match dob_data.day { + Some(d) => { + if mo { + dob_string.push_str("/") + } + dob_string.push_str(format!("{:02}", d).as_str()); + da = true + } + None => {} + } + + match dob_data.year { + Some(y) => { + if da { + dob_string.push_str("/") + } + dob_string.push_str(format!("{:04}", y).as_str()); + } + None => {} + } + + let mut dob = String::new(); + if dob_string != String::new() { + dob = character_localised + .date_of_birth + .replace("$date$", dob_string.as_str()) + } + + let mut desc = character_localised + .desc + .replace("$age$", character.age.as_str()) + .replace("$gender$", character.gender.as_str()) + .replace("$date_of_birth$", dob.as_str()) + .replace("$fav$", character.favourites.to_string().as_str()) + .replace("$desc$", character.description.as_str()); + + desc = convert_to_discord_markdown(desc); + let lenght_diff = 4096 - desc.len() as i32; + if lenght_diff <= 0 { + desc = trim(desc, lenght_diff) + } + + let native = character.name.native; + let user_pref = character.name.user_preferred; + let character_name = format!("{}/{}", user_pref, native); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(desc) + .thumbnail(character.image.large) + .title(character_name) + .url(character.site_url); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/anilist_struct/run/media.rs b/src/anilist_struct/run/media.rs new file mode 100644 index 00000000..a1ef1875 --- /dev/null +++ b/src/anilist_struct/run/media.rs @@ -0,0 +1,686 @@ +use serde::Deserialize; +use serde_json::json; +use serenity::all::{ + CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; + +use crate::common::html_parser::convert_to_discord_markdown; +use crate::common::make_anilist_request::make_request_anilist; +use crate::common::trimer::trim; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, UNKNOWN}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::MediaGettingError; +use crate::lang_struct::anilist::media::{load_localization_media, MediaLocalised}; + +#[derive(Debug, Deserialize, Clone)] +pub struct MediaWrapper { + pub data: MediaData, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct MediaData { + #[serde(rename = "Media")] + pub media: Media, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Media { + pub id: i64, + pub description: Option, + pub title: Title, + pub r#type: Option, + pub format: Option, + pub source: Option, + #[serde(rename = "isAdult")] + pub is_adult: bool, + #[serde(rename = "startDate")] + pub start_date: StartEndDate, + #[serde(rename = "endDate")] + pub end_date: StartEndDate, + pub chapters: Option, + pub volumes: Option, + pub status: Option, + pub season: Option, + #[serde(rename = "isLicensed")] + pub is_licensed: bool, + #[serde(rename = "coverImage")] + pub cover_image: CoverImage, + #[serde(rename = "bannerImage")] + pub banner_image: Option, + pub genres: Vec>, + pub tags: Vec, + #[serde(rename = "averageScore")] + pub average_score: Option, + #[serde(rename = "meanScore")] + pub mean_score: Option, + pub popularity: Option, + pub favourites: Option, + #[serde(rename = "siteUrl")] + pub site_url: Option, + pub staff: Staff, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Title { + pub romaji: Option, + pub english: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StartEndDate { + pub year: Option, + pub month: Option, + pub day: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct CoverImage { + #[serde(rename = "extraLarge")] + pub extra_large: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Tag { + pub name: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Staff { + pub edges: Vec, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Edge { + pub node: Node, + pub id: Option, + pub role: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Node { + pub id: Option, + pub name: Name, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Name { + pub full: Option, + #[serde(rename = "userPreferred")] + pub user_preferred: Option, +} + +impl MediaWrapper { + pub async fn new_anime_by_id(search: String) -> Result { + let query_id: &str = " + query ($search: Int, $limit: Int = 5) { + Media (id: $search, type: ANIME){ + id + description + title{ + romaji + english + } + type + format + source + isAdult + startDate { + year + month + day + } + endDate { + year + month + day + } + chapters + volumes + status + season + isLicensed + coverImage { + extraLarge + } + bannerImage + genres + tags { + name + } + averageScore + meanScore + popularity + favourites + siteUrl + staff(perPage: $limit) { + edges { + node { + id + name { + full + userPreferred + } + } + id + role + } + } + } +} +"; + + let json = json!({"query": query_id, "variables": {"search": search}}); + let resp = make_request_anilist(json, false).await; + // Get json + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_anime_by_search(search: &String) -> Result { + let query_string: &str = " + query ($search: String, $limit: Int = 5) { + Media (search: $search, type: ANIME){ + id + description + title{ + romaji + english + } + type + format + source + isAdult + startDate { + year + month + day + } + endDate { + year + month + day + } + chapters + volumes + status + season + isLicensed + coverImage { + extraLarge + } + bannerImage + genres + tags { + name + } + averageScore + meanScore + popularity + favourites + siteUrl + staff(perPage: $limit) { + edges { + node { + id + name { + full + userPreferred + } + } + id + role + } + } + } +} +"; + let json = json!({"query": query_string, "variables": {"search": search}}); + let resp = make_request_anilist(json, false).await; + // Get json + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_manga_by_id(search: String) -> Result { + let query_id: &str = " + query ($search: Int, $limit: Int = 5, $format: MediaFormat = NOVEL) { + Media (id: $search, type: MANGA, format_not: $format){ + id + description + title{ + romaji + english + } + type + format + source + isAdult + startDate { + year + month + day + } + endDate { + year + month + day + } + chapters + volumes + status + season + isLicensed + coverImage { + extraLarge + } + bannerImage + genres + tags { + name + } + averageScore + meanScore + popularity + favourites + siteUrl + staff(perPage: $limit) { + edges { + node { + id + name { + full + userPreferred + } + } + id + role + } + } + } +} +"; + + let json = json!({"query": query_id, "variables": {"search": search}}); + let resp = make_request_anilist(json, false).await; + // Get json + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_manga_by_search(search: &String) -> Result { + let query_string: &str = " + query ($search: String, $limit: Int = 5, $format: MediaFormat = NOVEL) { + Media (search: $search, type: MANGA, format_not: $format){ + id + description + title{ + romaji + english + } + type + format + source + isAdult + startDate { + year + month + day + } + endDate { + year + month + day + } + chapters + volumes + status + season + isLicensed + coverImage { + extraLarge + } + bannerImage + genres + tags { + name + } + averageScore + meanScore + popularity + favourites + siteUrl + staff(perPage: $limit) { + edges { + node { + id + name { + full + userPreferred + } + } + id + role + } + } + } +} +"; + let json = json!({"query": query_string, "variables": {"search": search}}); + let resp = make_request_anilist(json, false).await; + // Get json + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_ln_by_id(search: String) -> Result { + let query_id: &str = " + query ($search: Int, $limit: Int = 5, $format: MediaFormat = NOVEL) { + Media (id: $search, type: MANGA, format: $format){ + id + description + title{ + romaji + english + } + type + format + source + isAdult + startDate { + year + month + day + } + endDate { + year + month + day + } + chapters + volumes + status + season + isLicensed + coverImage { + extraLarge + } + bannerImage + genres + tags { + name + } + averageScore + meanScore + popularity + favourites + siteUrl + staff(perPage: $limit) { + edges { + node { + id + name { + full + userPreferred + } + } + id + role + } + } + } +} +"; + + let json = json!({"query": query_id, "variables": {"search": search}}); + let resp = make_request_anilist(json, false).await; + // Get json + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_ln_by_search(search: &String) -> Result { + let query_string: &str = " + query ($search: String, $limit: Int = 5, $format: MediaFormat = NOVEL) { + Media (search: $search, type: MANGA, format: $format){ + id + description + title{ + romaji + english + } + type + format + source + isAdult + startDate { + year + month + day + } + endDate { + year + month + day + } + chapters + volumes + status + season + isLicensed + coverImage { + extraLarge + } + bannerImage + genres + tags { + name + } + averageScore + meanScore + popularity + favourites + siteUrl + staff(perPage: $limit) { + edges { + node { + id + name { + full + userPreferred + } + } + id + role + } + } + } +} +"; + + let json = json!({"query": query_string, "variables": {"search": search}}); + let resp = make_request_anilist(json, false).await; + // Get json + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } +} + +fn embed_title(data: &MediaWrapper) -> String { + let en = data.data.media.title.english.clone(); + let rj = data.data.media.title.romaji.clone(); + let en = en.unwrap_or(String::from("")); + let rj = rj.unwrap_or(String::from("")); + let mut title = String::new(); + let mut total = 0; + match en.as_str() { + "" => {} + _ => { + total += 1; + title.push_str(en.as_str()) + } + } + + match rj.as_str() { + "\"\"" => {} + _ => { + if total == 1 { + title.push_str(" / "); + title.push_str(rj.as_str()) + } else { + title.push_str(rj.as_str()) + } + } + } + + title +} + +fn embed_desc(data: &MediaWrapper) -> String { + let mut desc = data + .data + .media + .description + .clone() + .unwrap_or_else(|| "".to_string()); + desc = convert_to_discord_markdown(desc); + let lenght_diff = 4096 - desc.len() as i32; + if lenght_diff <= 0 { + desc = trim(desc, lenght_diff) + } + desc +} + +fn get_genre(data: &MediaWrapper) -> String { + data.data + .media + .genres + .iter() + .filter_map(|x| x.as_ref()) + .map(|s| s.as_str()) + .take(5) + .collect::>() + .join("\n") +} + +fn get_tag(data: &MediaWrapper) -> String { + data.data + .media + .tags + .iter() + .filter_map(|x| x.name.as_ref()) + .map(|s| s.as_str()) + .take(5) + .collect::>() + .join("\n") +} + +fn get_url(data: &MediaWrapper) -> String { + data.data + .media + .site_url + .clone() + .unwrap_or("https://example.com".to_string()) +} + +fn get_thumbnail(data: &MediaWrapper) -> String { + data.data.media.cover_image.extra_large.clone().unwrap_or("https://imgs.search.brave.com/CYnhSvdQcm9aZe3wG84YY0B19zT2wlAuAkiAGu0mcLc/rs:fit:640:400:1/g:ce/aHR0cDovL3d3dy5m/cmVtb250Z3VyZHdh/cmEub3JnL3dwLWNv/bnRlbnQvdXBsb2Fk/cy8yMDIwLzA2L25v/LWltYWdlLWljb24t/Mi5wbmc".to_string()) +} + +pub fn get_banner(data: &MediaWrapper) -> String { + format!("https://img.anili.st/media/{}", data.data.media.id) +} + +fn media_info(data: &MediaWrapper, media_localised: &MediaLocalised) -> String { + let mut desc = format!( + "{} \n\n\ + {}", + embed_desc(data), + get_info(&data.data.media, media_localised) + ); + desc = convert_to_discord_markdown(desc); + let lenght_diff = 4096 - desc.len() as i32; + if lenght_diff <= 0 { + desc = trim(desc, lenght_diff) + } + desc +} + +fn get_info(data: &Media, media_localised: &MediaLocalised) -> String { + let text = media_localised.desc.clone(); + let format = data.format.clone(); + let source = data.source.clone(); + text.replace("$format$", format.unwrap_or(UNKNOWN.to_string()).as_str()) + .replace("$source$", source.unwrap_or(UNKNOWN.to_string()).as_str()) + .replace("$start_date$", get_date(&data.start_date).as_str()) + .replace("$end_date$", get_date(&data.end_date).as_str()) + .replace( + "$staff_list$", + get_staff(&data.staff.edges, &media_localised.staff_text).as_str(), + ) +} + +fn get_date(date: &StartEndDate) -> String { + let date_y = date.year.unwrap_or(0); + let date_d = date.day.unwrap_or(0); + let date_m = date.month.unwrap_or(0); + if date_y == 0 && date_d == 0 && date_m == 0 { + UNKNOWN.to_string() + } else { + format!("{}/{}/{}", date_d, date_m, date_y) + } +} + +fn get_staff(staff: &Vec, staff_string: &String) -> String { + let mut staff_text = String::new(); + for s in staff { + let text = staff_string.clone(); + let node = &s.node; + let name = &node.name; + let full = name.full.clone(); + let user_pref = name.user_preferred.clone(); + let staff_name = user_pref.unwrap_or(full.unwrap_or(UNKNOWN.to_string())); + let s_role = s.role.clone(); + let role = s_role.unwrap_or(UNKNOWN.to_string()); + staff_text.push_str( + text.replace("$name$", staff_name.as_str()) + .replace("$role$", role.as_str()) + .as_str(), + ) + } + + staff_text +} + +pub async fn send_embed( + ctx: &Context, + command: &CommandInteraction, + data: MediaWrapper, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let media_localised = load_localization_media(guild_id).await?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(media_info(&data, &media_localised)) + .title(embed_title(&data)) + .url(get_url(&data)) + .field(&media_localised.field1_title, get_genre(&data), true) + .field(&media_localised.field2_title, get_tag(&data), true) + .thumbnail(get_thumbnail(&data)) + .image(get_banner(&data)); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/anilist_struct/run/minimal_anime.rs b/src/anilist_struct/run/minimal_anime.rs new file mode 100644 index 00000000..70513f60 --- /dev/null +++ b/src/anilist_struct/run/minimal_anime.rs @@ -0,0 +1,122 @@ +use serde::{Deserialize, Serialize}; +use serde_json::json; +use sqlx::FromRow; +use tracing::log::trace; + +use crate::common::make_anilist_request::make_request_anilist; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NoMediaDifferedError; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct NextAiringEpisode { + #[serde(rename = "airingAt")] + pub airing_at: Option, + #[serde(rename = "timeUntilAiring")] + pub time_until_airing: Option, + pub episode: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Title { + pub romaji: Option, + pub english: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct MinimalAnime { + pub id: i32, + pub title: Option, + #[serde(rename = "nextAiringEpisode")] + pub next_airing_episode: Option<NextAiringEpisode>, + #[serde(rename = "coverImage")] + pub cover_image: Option<CoverImage>, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct MinimalAnimeWrapper { + pub data: MinimalAnimeData, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct MinimalAnimeData { + #[serde(rename = "Media")] + pub media: MinimalAnime, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct CoverImage { + #[serde(rename = "extraLarge")] + pub extra_large: Option<String>, +} + +impl MinimalAnimeWrapper { + pub async fn new_minimal_anime_by_id(search: String) -> Result<MinimalAnimeWrapper, AppError> { + let query = " + query ($name: Int) { + Media(type: ANIME, id: $name) { + id + coverImage { + extraLarge + } + title { + romaji + english + } + nextAiringEpisode { + airingAt + timeUntilAiring + episode + } + } + } + "; + let json = json!({"query": query, "variables": {"name": search}}); + let resp = make_request_anilist(json, true).await; + trace!("{:?}", resp); + // Get json + let data = serde_json::from_str(&resp) + .map_err(|_| NoMediaDifferedError(String::from("No media")))?; + Ok(data) + } + + pub async fn new_minimal_anime_by_search( + search: String, + ) -> Result<MinimalAnimeWrapper, AppError> { + let query = " + query ($name: String) { + Media(type: ANIME, search: $name) { + id + coverImage { + extraLarge + } + title { + romaji + english + } + nextAiringEpisode { + airingAt + timeUntilAiring + episode + } + } + } + "; + let json = json!({"query": query, "variables": {"name": search}}); + let resp = make_request_anilist(json, true).await; + // Get json + let data = serde_json::from_str(&resp) + .map_err(|_| NoMediaDifferedError(String::from("No media")))?; + Ok(data) + } +} + +#[derive(Debug, FromRow, Clone)] +pub struct ActivityData { + pub anime_id: Option<String>, + pub timestamp: Option<String>, + pub server_id: Option<String>, + pub webhook: Option<String>, + pub episode: Option<String>, + pub name: Option<String>, + pub delays: Option<i32>, +} diff --git a/src/anilist_struct/run/mod.rs b/src/anilist_struct/run/mod.rs new file mode 100644 index 00000000..1916842d --- /dev/null +++ b/src/anilist_struct/run/mod.rs @@ -0,0 +1,10 @@ +pub mod character; +pub mod media; +pub mod minimal_anime; +pub mod random; +pub mod seiyuu; +pub mod site_statistic_anime; +pub mod site_statistic_manga; +pub mod staff; +pub mod studio; +pub mod user; diff --git a/src/structure/anilist/random/struct_random.rs b/src/anilist_struct/run/random.rs similarity index 63% rename from src/structure/anilist/random/struct_random.rs rename to src/anilist_struct/run/random.rs index 1bf1e6bb..2f5994bb 100644 --- a/src/structure/anilist/random/struct_random.rs +++ b/src/anilist_struct/run/random.rs @@ -1,9 +1,10 @@ -use crate::function::general::html_parser::convert_to_discord_markdown; -use crate::function::general::trim::trim; -use crate::function::requests::request::make_request_anilist; use serde::Deserialize; use serde_json::json; +use crate::common::make_anilist_request::make_request_anilist; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NoMediaDifferedError; + #[derive(Debug, Deserialize, Clone)] pub struct Media { pub id: i32, @@ -54,7 +55,7 @@ pub struct CoverImage { } impl PageWrapper { - pub async fn new_anime_page(number: i64) -> PageWrapper { + pub async fn new_anime_page(number: i64) -> Result<PageWrapper, AppError> { let query = " query($anime_page: Int){ Page(page: $anime_page, perPage: 1){ @@ -81,11 +82,12 @@ impl PageWrapper { let json = json!({"query": query, "variables": {"anime_page": number}}); let res = make_request_anilist(json, false).await; - - serde_json::from_str(&res).unwrap() + let res = serde_json::from_str(&res) + .map_err(|_| NoMediaDifferedError(String::from("No media")))?; + Ok(res) } - pub async fn new_manga_page(number: i64) -> PageWrapper { + pub async fn new_manga_page(number: i64) -> Result<PageWrapper, AppError> { let query = " query($manga_page: Int){ Page(page: $manga_page, perPage: 1){ @@ -113,62 +115,8 @@ impl PageWrapper { let json = json!({"query": query, "variables": {"manga_page": number}}); let res = make_request_anilist(json, false).await; - serde_json::from_str(&res).unwrap() - } - - pub fn get_media(&self) -> Media { - self.data.page.media[0].clone() - } - - pub fn get_user_pref_title(&self) -> String { - self.get_media().title.user_preferred - } - - pub fn get_native_title(&self) -> String { - self.get_media().title.native - } - - pub fn get_genre(&self) -> String { - self.get_media().genres.join("/") - } - - pub fn get_tags(&self) -> String { - self.get_media() - .tags - .into_iter() - .map(|tag| tag.name.clone()) - .collect::<Vec<String>>() - .join("/") - } - - pub fn get_cover_image(&self) -> String { - self.get_media().cover_image.extra_large - } - - pub fn get_description(&self) -> String { - let mut desc = self.get_media().description; - desc = convert_to_discord_markdown(desc); - let lenght_diff = 4096 - desc.len() as i32; - if lenght_diff <= 0 { - trim(desc, lenght_diff) - } else { - desc - } - } - - pub fn get_format(&self) -> String { - self.get_media().format - } - - pub fn get_anime_url(&self) -> String { - format!("https://anilist.co/anime/{}", self.get_id()) - } - - pub fn get_manga_url(&self) -> String { - format!("https://anilist.co/manga/{}", self.get_id()) - } - - pub fn get_id(&self) -> i32 { - self.get_media().id + let res = serde_json::from_str(&res) + .map_err(|_| NoMediaDifferedError(String::from("No media")))?; + Ok(res) } } diff --git a/src/anilist_struct/run/seiyuu.rs b/src/anilist_struct/run/seiyuu.rs new file mode 100644 index 00000000..3b2930e5 --- /dev/null +++ b/src/anilist_struct/run/seiyuu.rs @@ -0,0 +1,86 @@ +use serde::Deserialize; +use serde_json::json; + +use crate::common::make_anilist_request::make_request_anilist; +use crate::error_enum::AppError; +use crate::error_enum::AppError::MediaGettingError; + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffImageWrapper { + pub data: StaffImageData, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffImageData { + #[serde(rename = "Staff")] + pub staff: Staff, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Staff { + pub image: StaffImageImage, + pub characters: StaffImageCharacters, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffImageImage { + pub large: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffImageCharacters { + pub nodes: Vec<StaffImageNodes>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffImageNodes { + pub image: StaffImageImage, +} + +impl StaffImageWrapper { + pub async fn new_staff_by_id(id: i32) -> Result<StaffImageWrapper, AppError> { + let query_id: &str = " + query ($name: Int, $limit: Int = 4) { + Staff(id: $name){ + image{ + large + } + characters(perPage: $limit, sort: FAVOURITES_DESC) { + nodes { + image { + large + } + } + } + } +} +"; + let json = json!({"query": query_id, "variables": {"name": id}}); + let resp = make_request_anilist(json, false).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_staff_by_search(search: &String) -> Result<StaffImageWrapper, AppError> { + let query_string: &str = " +query ($name: String, $limit: Int = 4) { + Staff(search: $name){ + image{ + large + } + characters(perPage: $limit, sort: FAVOURITES_DESC) { + nodes { + image { + large + } + } + } + } +} +"; + let json = json!({"query": query_string, "variables": {"name": search}}); + let resp = make_request_anilist(json, false).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } +} diff --git a/src/structure/anilist/random/struct_site_statistic_anime.rs b/src/anilist_struct/run/site_statistic_anime.rs similarity index 75% rename from src/structure/anilist/random/struct_site_statistic_anime.rs rename to src/anilist_struct/run/site_statistic_anime.rs index 76e6446b..6f912086 100644 --- a/src/structure/anilist/random/struct_site_statistic_anime.rs +++ b/src/anilist_struct/run/site_statistic_anime.rs @@ -1,31 +1,34 @@ -use crate::function::requests::request::make_request_anilist; use serde::Deserialize; use serde_json::json; -#[derive(Debug, Deserialize)] +use crate::common::make_anilist_request::make_request_anilist; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NoStatisticDifferedError; + +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsAnimeWrapper { pub data: SiteStatisticsAnimeData, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsAnimeData { #[serde(rename = "SiteStatistics")] pub site_statistics: SiteStatisticsAnimeContainer, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsAnimeContainer { pub anime: SiteStatisticAnime, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticAnime { #[serde(rename = "pageInfo")] pub page_info: SiteStatisticsAnimePageInfo, pub nodes: Vec<SiteStatisticsAnimeNode>, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsAnimePageInfo { #[serde(rename = "currentPage")] pub current_page: i32, @@ -36,7 +39,7 @@ pub struct SiteStatisticsAnimePageInfo { pub has_next_page: bool, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsAnimeNode { pub date: i64, pub count: i32, @@ -44,7 +47,9 @@ pub struct SiteStatisticsAnimeNode { } impl SiteStatisticsAnimeWrapper { - pub async fn new_anime(page_number: i64) -> (SiteStatisticsAnimeWrapper, String) { + pub async fn new_anime( + page_number: i64, + ) -> Result<(SiteStatisticsAnimeWrapper, String), AppError> { let query = "query($page: Int){ SiteStatistics{ anime(perPage: 1, page: $page){ @@ -65,8 +70,9 @@ impl SiteStatisticsAnimeWrapper { "; let json = json!({"query": query, "variables": {"page": page_number}}); let res = make_request_anilist(json, false).await; - let api_response: SiteStatisticsAnimeWrapper = serde_json::from_str(&res).unwrap(); - (api_response, res) + let api_response: SiteStatisticsAnimeWrapper = serde_json::from_str(&res) + .map_err(|_| NoStatisticDifferedError(String::from("No media")))?; + Ok((api_response, res)) } pub fn has_next_page(&self) -> bool { self.data.site_statistics.anime.page_info.has_next_page diff --git a/src/structure/anilist/random/struct_site_statistic_manga.rs b/src/anilist_struct/run/site_statistic_manga.rs similarity index 75% rename from src/structure/anilist/random/struct_site_statistic_manga.rs rename to src/anilist_struct/run/site_statistic_manga.rs index 5e3926b3..27fee850 100644 --- a/src/structure/anilist/random/struct_site_statistic_manga.rs +++ b/src/anilist_struct/run/site_statistic_manga.rs @@ -1,31 +1,34 @@ -use crate::function::requests::request::make_request_anilist; use serde::Deserialize; use serde_json::json; -#[derive(Debug, Deserialize)] +use crate::common::make_anilist_request::make_request_anilist; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NoStatisticDifferedError; + +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsMangaWrapper { pub data: SiteStatisticsMangaData, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsMangaData { #[serde(rename = "SiteStatistics")] pub site_statistics: SiteStatisticsMangaContainer, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsMangaContainer { pub manga: SiteStatisticManga, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticManga { #[serde(rename = "pageInfo")] pub page_info: SiteStatisticsMangaPageInfo, pub nodes: Vec<SiteStatisticsMangaNode>, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsMangaPageInfo { #[serde(rename = "currentPage")] pub current_page: i32, @@ -36,7 +39,7 @@ pub struct SiteStatisticsMangaPageInfo { pub has_next_page: bool, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct SiteStatisticsMangaNode { pub date: i64, pub count: i32, @@ -44,7 +47,9 @@ pub struct SiteStatisticsMangaNode { } impl SiteStatisticsMangaWrapper { - pub async fn new_manga(page_number: i64) -> (SiteStatisticsMangaWrapper, String) { + pub async fn new_manga( + page_number: i64, + ) -> Result<(SiteStatisticsMangaWrapper, String), AppError> { let query = " query($page: Int){ SiteStatistics{ @@ -66,8 +71,9 @@ impl SiteStatisticsMangaWrapper { "; let json = json!({"query": query, "variables": {"page": page_number}}); let res = make_request_anilist(json, false).await; - let api_response: SiteStatisticsMangaWrapper = serde_json::from_str(&res).unwrap(); - (api_response, res) + let api_response: SiteStatisticsMangaWrapper = serde_json::from_str(&res) + .map_err(|_| NoStatisticDifferedError(String::from("No media")))?; + Ok((api_response, res)) } pub fn has_next_page(&self) -> bool { self.data.site_statistics.manga.page_info.has_next_page diff --git a/src/anilist_struct/run/staff.rs b/src/anilist_struct/run/staff.rs new file mode 100644 index 00000000..5fbd3c6f --- /dev/null +++ b/src/anilist_struct/run/staff.rs @@ -0,0 +1,226 @@ +use serde::Deserialize; +use serde_json::json; + +use crate::common::make_anilist_request::make_request_anilist; +use crate::error_enum::AppError; +use crate::error_enum::AppError::MediaGettingError; + +#[derive(Debug, Deserialize, Clone)] +pub struct Name { + pub full: Option<String>, + pub native: Option<String>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Image { + pub large: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Date { + pub year: Option<i32>, + pub month: Option<i32>, + pub day: Option<i32>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Title { + pub romaji: Option<String>, + pub english: Option<String>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Node { + pub title: Title, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffMedia { + pub edges: Vec<Edge>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Edge { + pub node: Node, + #[serde(rename = "roleNotes")] + pub role_notes: Option<String>, + #[serde(rename = "relationType")] + pub relation_type: Option<String>, + #[serde(rename = "staffRole")] + pub staff_role: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Character { + pub name: Name, + pub image: Image, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Characters { + pub nodes: Vec<Character>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Staff { + pub name: Name, + pub id: i32, + #[serde(rename = "languageV2")] + pub language_v2: String, + pub image: Image, + pub description: String, + #[serde(rename = "primaryOccupations")] + pub primary_occupations: Vec<String>, + pub gender: Option<String>, + #[serde(rename = "dateOfBirth")] + pub date_of_birth: Date, + #[serde(rename = "dateOfDeath")] + pub date_of_death: Date, + pub age: Option<i32>, + #[serde(rename = "yearsActive")] + pub years_active: Vec<i32>, + #[serde(rename = "homeTown")] + pub home_town: Option<String>, + #[serde(rename = "siteUrl")] + pub site_url: String, + #[serde(rename = "staffMedia")] + pub staff_media: StaffMedia, + pub characters: Characters, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffData { + #[serde(rename = "Staff")] + pub staff: Staff, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct StaffWrapper { + pub data: StaffData, +} + +impl StaffWrapper { + pub async fn new_staff_by_id(id: i32) -> Result<StaffWrapper, AppError> { + let query_id: &str = " +query ($name: Int, $limit1: Int = 5, $limit2: Int = 15) { + Staff(id: $name){ + name { + full + native + } + id + languageV2 + image { + large + } + description + primaryOccupations + gender + dateOfBirth { + year + month + day + } + dateOfDeath { + year + month + day + } + age + yearsActive + homeTown + siteUrl + staffMedia(perPage: $limit1){ + edges{ + node { + title { + romaji + english + } + } + roleNotes + relationType + staffRole + } + } + characters(perPage: $limit2) { + nodes { + name { + full + } + image { + large + } + } + } + } +} +"; + let json = json!({"query": query_id, "variables": {"name": id}}); + let resp = make_request_anilist(json, false).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_staff_by_search(search: &String) -> Result<StaffWrapper, AppError> { + let query_string: &str = " +query ($name: String, $limit1: Int = 5, $limit2: Int = 15) { + Staff(search: $name){ + name { + full + native + } + id + languageV2 + image { + large + } + description + primaryOccupations + gender + dateOfBirth { + year + month + day + } + dateOfDeath { + year + month + day + } + age + yearsActive + homeTown + siteUrl + staffMedia(perPage: $limit1){ + edges{ + node { + title { + romaji + english + } + } + roleNotes + relationType + staffRole + } + } + characters(perPage: $limit2) { + nodes { + name { + full + } + image { + large + } + } + } + } +} +"; + let json = json!({"query": query_string, "variables": {"name": search}}); + let resp = make_request_anilist(json, false).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } +} diff --git a/src/structure/anilist/studio/struct_studio.rs b/src/anilist_struct/run/studio.rs similarity index 52% rename from src/structure/anilist/studio/struct_studio.rs rename to src/anilist_struct/run/studio.rs index 9bfa4e59..8e8d8e8d 100644 --- a/src/structure/anilist/studio/struct_studio.rs +++ b/src/anilist_struct/run/studio.rs @@ -1,25 +1,27 @@ -use crate::function::requests::request::make_request_anilist; -use crate::structure::embed::anilist::struct_lang_studio::StudioLocalisedText; use serde::Deserialize; use serde_json::json; +use crate::common::make_anilist_request::make_request_anilist; +use crate::error_enum::AppError; +use crate::error_enum::AppError::MediaGettingError; + #[derive(Debug, Deserialize, Clone)] pub struct Title { - romaji: String, + pub romaji: String, #[serde(rename = "userPreferred")] - user_preferred: String, + pub user_preferred: String, } #[derive(Debug, Deserialize, Clone)] pub struct MediaNode { - title: Title, + pub title: Title, #[serde(rename = "siteUrl")] - site_url: String, + pub site_url: String, } #[derive(Debug, Deserialize, Clone)] pub struct Media { - nodes: Vec<MediaNode>, + pub nodes: Vec<MediaNode>, } #[derive(Debug, Deserialize, Clone)] @@ -46,7 +48,7 @@ pub struct StudioWrapper { } impl StudioWrapper { - pub async fn new_studio_by_id(id: i32) -> Result<StudioWrapper, String> { + pub async fn new_studio_by_id(id: i32) -> Result<StudioWrapper, AppError> { let query_id: &str = "\ query ($name: Int, $limit: Int = 15) { Studio(id: $name) { @@ -69,16 +71,11 @@ impl StudioWrapper { "; let json = json!({"query": query_id, "variables": {"name": id}}); let resp = make_request_anilist(json, false).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(String::from("Error: Failed to retrieve user data")) - } - } + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) } - pub async fn new_studio_by_search(search: &String) -> Result<StudioWrapper, String> { + pub async fn new_studio_by_search(search: &String) -> Result<StudioWrapper, AppError> { let query_string: &str = " query ($name: String, $limit: Int = 5) { Studio(search: $name) { @@ -101,54 +98,7 @@ impl StudioWrapper { "; let json = json!({"query": query_string, "variables": {"name": search}}); let resp = make_request_anilist(json, false).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(String::from("Error: Failed to retrieve user data")) - } - } - } - - pub fn get_studio_name(&self) -> String { - self.data.studio.name.clone() - } - - pub fn get_site_url(&self) -> String { - self.data.studio.site_url.clone() - } - - pub fn get_favourite(&self) -> String { - self.data.studio.favourites.to_string() - } - - pub fn get_anime_manga_list(&self, localised_text: StudioLocalisedText) -> String { - let list = self.data.studio.media.nodes.clone(); - let mut content = format!("{} \n", localised_text.anime_or_manga); - for m in list { - content.push_str(self.get_one_anime_manga(m).as_str()) - } - - content - } - pub fn get_one_anime_manga(&self, m: MediaNode) -> String { - format!( - "[{} / {}]({}) \n \n", - m.title.romaji, m.title.user_preferred, m.site_url - ) - } - - pub fn get_desc(&self, localised_text: StudioLocalisedText) -> String { - format!( - "id: {} \n {}{} \n {} \n \n \n ", - self.get_id(), - localised_text.favorite, - self.get_favourite(), - self.get_anime_manga_list(localised_text.clone()) - ) - } - - pub fn get_id(&self) -> u32 { - self.data.studio.id + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) } } diff --git a/src/anilist_struct/run/user.rs b/src/anilist_struct/run/user.rs new file mode 100644 index 00000000..9e357321 --- /dev/null +++ b/src/anilist_struct/run/user.rs @@ -0,0 +1,484 @@ +use serde::Deserialize; +use serde_json::json; +use serenity::all::{ + Colour, CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; + +use crate::common::make_anilist_request::make_request_anilist; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::MediaGettingError; +use crate::lang_struct::anilist::user::{load_localization_user, UserLocalised}; + +#[derive(Debug, Deserialize, Clone)] +pub struct UserWrapper { + pub data: UserData, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct UserData { + #[serde(rename = "User")] + pub user: User, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct User { + pub id: Option<i32>, + pub name: Option<String>, + pub avatar: Avatar, + pub statistics: Statistics, + pub options: Options, + #[serde(rename = "bannerImage")] + pub banner_image: Option<String>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Options { + #[serde(rename = "profileColor")] + pub profile_color: Option<String>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Avatar { + pub large: Option<String>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Statistics { + pub anime: Anime, + pub manga: Manga, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Anime { + pub count: Option<i32>, + #[serde(rename = "meanScore")] + pub mean_score: Option<f64>, + #[serde(rename = "standardDeviation")] + pub standard_deviation: Option<f64>, + #[serde(rename = "minutesWatched")] + pub minutes_watched: Option<i32>, + pub tags: Vec<Tag>, + pub genres: Vec<Genre>, + pub statuses: Vec<Statuses>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Manga { + pub count: Option<i32>, + #[serde(rename = "meanScore")] + pub mean_score: Option<f64>, + #[serde(rename = "standardDeviation")] + pub standard_deviation: Option<f64>, + #[serde(rename = "chaptersRead")] + pub chapters_read: Option<i32>, + pub tags: Vec<Tag>, + pub genres: Vec<Genre>, + pub statuses: Vec<Statuses>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Statuses { + pub count: i32, + pub status: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Tag { + pub tag: TagData, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct TagData { + pub name: Option<String>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Genre { + pub genre: Option<String>, +} + +impl UserWrapper { + pub async fn new_user_by_id(id: i32) -> Result<UserWrapper, AppError> { + let query_id: &str = " +query ($name: Int, $limit: Int = 5) { + User(id: $name) { + id + name + avatar { + large + } + statistics { + anime { + count + meanScore + standardDeviation + minutesWatched + tags(limit: $limit, sort: MEAN_SCORE_DESC) { + tag { + name + } + } + genres(limit: $limit, sort: MEAN_SCORE_DESC) { + genre + } + statuses(sort: COUNT_DESC){ + count + status + } + } + manga { + count + meanScore + standardDeviation + chaptersRead + tags(limit: $limit, sort: MEAN_SCORE_DESC) { + tag { + name + } + } + genres(limit: $limit, sort: MEAN_SCORE_DESC) { + genre + } + statuses(sort: COUNT_DESC){ + count + status + } + } + } +options{ + profileColor + } + bannerImage + } +} +"; + let json = json!({"query": query_id, "variables": {"name": id}}); + let resp = make_request_anilist(json, true).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } + + pub async fn new_user_by_search(search: &String) -> Result<UserWrapper, AppError> { + let query_string: &str = " +query ($name: String, $limit: Int = 5) { + User(name: $name) { + id + name + avatar { + large + } + statistics { + anime { + count + meanScore + standardDeviation + minutesWatched + tags(limit: $limit, sort: MEAN_SCORE_DESC) { + tag { + name + } + } + genres(limit: $limit, sort: MEAN_SCORE_DESC) { + genre + } + statuses(sort: COUNT_DESC){ + count + status + } + } + manga { + count + meanScore + standardDeviation + chaptersRead + tags(limit: $limit, sort: MEAN_SCORE_DESC) { + tag { + name + } + } + genres(limit: $limit, sort: MEAN_SCORE_DESC) { + genre + } + statuses(sort: COUNT_DESC){ + count + status + } + } + } +options{ + profileColor + } + bannerImage + } +} +"; + let json = json!({"query": query_string, "variables": {"name": search}}); + let resp = make_request_anilist(json, true).await; + serde_json::from_str(&resp) + .map_err(|_| MediaGettingError(String::from("Error getting this media."))) + } +} + +pub async fn send_embed( + ctx: &Context, + command: &CommandInteraction, + data: UserWrapper, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let user_localised = load_localization_user(guild_id).await?; + + let user = data.data.user.clone(); + + let mut field = Vec::new(); + + let manga = user.statistics.manga.clone(); + let anime = user.statistics.anime.clone(); + + match user.statistics.manga.count { + Some(m) => { + if m > 0 { + field.push(get_manga_field( + user.id.unwrap_or(0), + user_localised.clone(), + manga, + )) + } + } + _ => {} + } + match user.statistics.anime.count { + Some(a) => { + if a > 0 { + field.push(get_anime_field( + user.id.unwrap_or(0), + user_localised.clone(), + anime, + )) + } + } + _ => {} + } + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(get_color(user.clone())) + .title(user.name.unwrap_or(String::new())) + .url(get_user_url(&user.id.unwrap_or(0))) + .fields(field) + .image(get_banner(&user.id.unwrap_or(0))) + .thumbnail(user.avatar.large.unwrap()); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} + +pub fn get_user_url(user_id: &i32) -> String { + format!("https://anilist.co/user/{}", user_id) +} + +pub fn get_banner(user_id: &i32) -> String { + format!("https://img.anili.st/user/{}", user_id) +} + +fn get_user_manga_url(user_id: i32) -> String { + format!("https://anilist.co/user/{}/mangalist", user_id) +} + +fn get_user_anime_url(user_id: i32) -> String { + format!("https://anilist.co/user/{}/animelist", user_id) +} + +fn get_manga_field(user_id: i32, localised: UserLocalised, manga: Manga) -> (String, String, bool) { + ( + String::new(), + get_manga_desc(manga, localised, user_id), + false, + ) +} + +fn get_anime_field(user_id: i32, localised: UserLocalised, anime: Anime) -> (String, String, bool) { + ( + String::new(), + get_anime_desc(anime, localised, user_id), + false, + ) +} + +fn get_manga_desc(manga: Manga, localised: UserLocalised, user_id: i32) -> String { + localised + .manga + .replace("$url$", get_user_manga_url(user_id).as_str()) + .replace("$count$", manga.count.unwrap_or(0).to_string().as_str()) + .replace( + "$complete$", + get_completed(manga.statuses.clone()).to_string().as_str(), + ) + .replace( + "$chap$", + manga.chapters_read.unwrap_or(0).to_string().as_str(), + ) + .replace( + "$score$", + manga.mean_score.unwrap_or(0f64).to_string().as_str(), + ) + .replace( + "$sd$", + manga + .standard_deviation + .unwrap_or(0f64) + .to_string() + .as_str(), + ) + .replace("$tag_list$", get_tag_list(manga.tags.clone()).as_str()) + .replace( + "$genre_list$", + get_genre_list(manga.genres.clone()).as_str(), + ) +} + +fn get_tag_list(vec: Vec<Tag>) -> String { + let vec = vec + .iter() + .map(|tag| tag.tag.name.as_ref().unwrap().clone()) + .collect::<Vec<_>>(); + let vec = vec.into_iter().take(5).collect::<Vec<_>>(); + vec.join("/") +} + +fn get_genre_list(vec: Vec<Genre>) -> String { + let vec = vec + .iter() + .map(|genre| genre.genre.as_ref().unwrap().clone()) + .collect::<Vec<_>>(); + let vec = vec.into_iter().take(5).collect::<Vec<_>>(); + vec.join("/") +} + +pub fn get_completed(statuses: Vec<Statuses>) -> i32 { + let anime_statuses = statuses; + let mut anime_completed = 0; + for i in anime_statuses { + if i.status == *"COMPLETED" { + anime_completed = i.count; + } + } + anime_completed +} + +fn get_anime_desc(anime: Anime, localised: UserLocalised, user_id: i32) -> String { + localised + .anime + .replace("$url$", get_user_anime_url(user_id).as_str()) + .replace("$count$", anime.count.unwrap_or(0).to_string().as_str()) + .replace( + "$complete$", + get_completed(anime.statuses.clone()).to_string().as_str(), + ) + .replace( + "$duration$", + get_anime_time_watch(anime.minutes_watched.unwrap_or(0), localised.clone()).as_str(), + ) + .replace( + "$score$", + anime.mean_score.unwrap_or(0f64).to_string().as_str(), + ) + .replace( + "$sd$", + anime + .standard_deviation + .unwrap_or(0f64) + .to_string() + .as_str(), + ) + .replace("$tag_list$", get_tag_list(anime.tags.clone()).as_str()) + .replace( + "$genre_list$", + get_genre_list(anime.genres.clone()).as_str(), + ) +} + +fn get_anime_time_watch(i: i32, localised1: UserLocalised) -> String { + let mut min = i; + let mut hour = 0; + let mut days = 0; + let mut week = 0; + + if min >= 60 { + hour = min / 60; + min %= 60; + } + + if hour >= 24 { + days = hour / 24; + hour %= 24; + } + + if days >= 7 { + week = days / 7; + days %= 7; + } + + let mut tw = String::new(); + + if week == 1 { + tw.push_str(format!("{} {}", localised1.week, week).as_str()) + } else if week > 1 { + tw.push_str(format!("{} {}", localised1.weeks, week).as_str()) + } + if days == 1 { + tw.push_str(format!("{} {}", localised1.day, days).as_str()) + } else if days > 1 { + tw.push_str(format!("{} {}", localised1.days, days).as_str()) + } + if hour == 1 { + tw.push_str(format!("{} {}", localised1.hour, tw).as_str()) + } else if hour > 1 { + tw.push_str(format!("{} {}", localised1.hours, tw).as_str()) + } + if min == 1 { + tw.push_str(format!("{} {}", localised1.minute, min).as_str()) + } else if min > 1 { + tw.push_str(format!("{} {}", localised1.minutes, min).as_str()) + } + + tw +} + +pub fn get_color(user: User) -> Colour { + let mut _color = COLOR.clone(); + match user + .options + .profile_color + .clone() + .unwrap_or_else(|| "#FF00FF".to_string()) + .as_str() + { + "blue" => _color = Colour::BLUE, + "purple" => _color = Colour::PURPLE, + "pink" => _color = Colour::MEIBE_PINK, + "orange" => _color = Colour::ORANGE, + "red" => _color = Colour::RED, + "green" => _color = Colour::DARK_GREEN, + "gray" => _color = Colour::LIGHT_GREY, + _ => { + _color = { + let hex_code = "#0D966D"; + let color_code = u32::from_str_radix(&hex_code[1..], 16).unwrap(); + Colour::new(color_code) + } + } + } + _color +} diff --git a/src/api/main.rs b/src/api/main.rs deleted file mode 100644 index b9e58907..00000000 --- a/src/api/main.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serenity::client::bridge::gateway::ShardManager; -use serenity::utils::shard_id; -use tokio::sync::Mutex; - -use crate::cmd::general_module::pool::get_pool; - -#[derive(serde::Serialize)] -struct Ping { - id: u32, - ping: String, -} - -async fn handle(req: tide::Request<()>) -> tide::Result<String> { - let shard_id = req.param("shardid").unwrap_or("0"); - let database_url = "./data.db"; - let pool = get_pool(database_url).await; - let row: (Option<String>, Option<String>) = - sqlx::query_as("SELECT anilist_username, user_id FROM registered_user WHERE user_id = ?") - .bind(shard_id) - .fetch_one(&pool) - .await - .unwrap_or((None, None)); - - let ping = Ping { - id: shard_id, - ping: ping, - }; - Ok("Hello world".to_string()) -} - -pub async fn create_server() -> tide::Result<()> { - let mut app = tide::new(); - app.at("/api/ping/:shardid").get(handle); - - app.listen("0.0.0.0:5783").await?; - Ok(()) -} diff --git a/src/api/mod.rs b/src/api/mod.rs deleted file mode 100644 index 2a043412..00000000 --- a/src/api/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod main; diff --git a/src/available_lang.rs b/src/available_lang.rs deleted file mode 100644 index ed0ad8c4..00000000 --- a/src/available_lang.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct AvailableLang { - pub lang: String, -} - -type AvailableLangList = HashMap<String, AvailableLang>; - -impl AvailableLang { - /// Reads and returns a list of available languages from a JSON file. - /// - /// This function opens a file named `available_lang.json` in the `lang_file` directory. - /// After opening the file, it reads the JSON content into a `String`. - /// Finally, it tries to deserialize the JSON content into an `AvailableLangList` object. - /// - /// # Errors - /// - /// If any error occurs while performing these operations, such as: - /// - /// * Error while opening the file. - /// * Error while reading the file. - /// * Error while parsing the JSON content. - /// - /// This function will return a static string describing the error. - /// - /// # Returns - /// - /// A `Result` which is: - /// - /// * `Ok(AvailableLangList)` if all operations completed successfully. - /// * `Err(&'static str)` if any error occurred. - /// - /// # Example - /// - /// ``` - /// use crate::your_module::get_available_lang; - /// - /// match get_available_lang() { - /// Ok(lang_list) => println!("Available languages: {:?}", lang_list), - /// Err(err) => println!("Error: {}", err), - /// } - /// ``` - pub fn get_available_lang() -> Result<AvailableLangList, &'static str> { - let mut file = match File::open("./lang_file/available_lang.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - - let mut json = String::new(); - - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/cmd/ai_module/image.rs b/src/cmd/ai_module/image.rs deleted file mode 100644 index 9775888d..00000000 --- a/src/cmd/ai_module/image.rs +++ /dev/null @@ -1,266 +0,0 @@ -use log::error; -use std::path::Path; -use std::{env, fs}; - -use crate::constant::COLOR; -use crate::function::error_management::error_base_url::error_no_base_url_edit; -use crate::function::error_management::error_creating_header::error_creating_header_edit; -use crate::function::error_management::error_getting_option::error_no_option; -use crate::function::error_management::error_instance_admin::error_instance_admin_models_edit; -use crate::function::error_management::error_parsing_json::error_parsing_json_edit; -use crate::function::error_management::error_request::error_making_request_edit; -use crate::function::error_management::error_resolving_value::error_resolving_value_followup; -use crate::function::error_management::error_response::{ - error_getting_bytes_response_edit, error_getting_response_from_url_edit, - error_writing_file_response_edit, -}; -use crate::function::error_management::error_token::error_no_token_edit; -use crate::function::error_management::error_url::error_no_url_edit; -use crate::function::general::differed_response::differed_response; -use crate::function::general::get_nsfw_channel::get_nsfw; -use crate::function::general::in_progress::in_progress_embed; -use crate::structure::embed::ai::struct_lang_image::ImageLocalisedText; -use crate::structure::register::ai::struct_image_register::ImageRegister; -use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE}; -use serde_json::{json, Value}; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; -use uuid::Uuid; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - if !get_nsfw(command, ctx).await { - return; - } - let option = match options.get(0) { - Some(data) => data, - None => { - error_no_option(ctx, command).await; - return; - } - }; - let option = match option.resolved.as_ref() { - Some(data) => data, - None => { - error_no_option(ctx, command).await; - return; - } - }; - if let CommandDataOptionValue::String(description) = option { - let uuid_name = Uuid::new_v4(); - let filename = format!("{}.png", uuid_name); - let filename_str = filename.as_str(); - - let localised_text = match ImageLocalisedText::get_image_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - differed_response(ctx, command).await; - - let message = match in_progress_embed(ctx, command).await { - Ok(Some(message_option)) => message_option, - Ok(None) => { - error_resolving_value_followup(ctx, command).await; - return; - } - Err(error) => { - error!("Error: {}", error); - return; - } - }; - - let my_path = "./.env"; - let path = Path::new(my_path); - let _ = dotenv::from_path(path); - let prompt = description; - let api_key = match env::var("AI_API_TOKEN") { - Ok(x) => x, - Err(why) => { - error!("{}", why); - error_no_token_edit(ctx, command, message).await; - return; - } - }; - let api_base_url = match env::var("AI_API_BASE_URL") { - Ok(x) => x, - Err(why) => { - error!("{}", why); - error_no_base_url_edit(ctx, command, message).await; - return; - } - }; - let data; - if let Ok(image_generation_mode) = env::var("IMAGE_GENERATION_MODELS_ON") { - let is_ok = image_generation_mode.to_lowercase() == "true"; - if is_ok { - let model = match env::var("IMAGE_GENERATION_MODELS") { - Ok(data) => data, - Err(why) => { - error!("{}", why); - error_instance_admin_models_edit(ctx, command, message).await; - return; - } - }; - data = json!({ - "prompt": prompt, - "n": 1, - "size": "1024x1024", - "model": model, - "response_format": "url" - }) - } else { - data = json!({ - "prompt": prompt, - "n": 1, - "size": "1024x1024", - "response_format": "url" - }) - } - } else { - data = json!({ - "prompt": prompt, - "n": 1, - "size": "1024x1024", - "response_format": "url" - }) - } - let api_url = format!("{}images/generations", api_base_url); - let client = reqwest::Client::new(); - - let mut headers = HeaderMap::new(); - headers.insert( - AUTHORIZATION, - match HeaderValue::from_str(&format!("Bearer {}", api_key)) { - Ok(data) => data, - Err(why) => { - error!("{}", why); - error_creating_header_edit(ctx, command, message).await; - return; - } - }, - ); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - - let res: Value = match client - .post(api_url) - .headers(headers) - .json(&data) - .send() - .await - { - Ok(data) => match data.json().await { - Ok(data) => data, - Err(why) => { - println!("{}", why); - error_parsing_json_edit(ctx, message, command).await; - return; - } - }, - Err(why) => { - error!("{}", why); - error_making_request_edit(ctx, command, message).await; - return; - } - }; - - let mut url_string = ""; - if let Some(data) = res.get("data") { - if let Some(object) = data.get(0) { - if let Some(url) = object.get("url") { - url_string = match url.as_str() { - Some(url) => url, - None => { - error_no_url_edit(ctx, command, message).await; - return; - } - } - } - } - } - - let mut real_message = message.clone(); - let response = match reqwest::get(url_string).await { - Ok(data) => data, - Err(why) => { - error!("{}", why); - error_getting_response_from_url_edit(ctx, command, message).await; - return; - } - }; - let bytes = match response.bytes().await { - Ok(data) => data, - Err(why) => { - error!("{}", why); - error_getting_bytes_response_edit(ctx, command, message).await; - return; - } - }; - match fs::write(filename.clone(), &bytes) { - Ok(_) => {} - Err(why) => { - error!("{}", why); - error_writing_file_response_edit(ctx, command, message).await; - return; - } - } - - let path = Path::new(filename_str); - - if let Err(why) = real_message - .edit(&ctx.http, |m| { - m.attachment(path).embed(|e| { - e.title(&localised_text.title) - .image(format!("attachment://{}", filename)) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - }) - .await - { - let _ = fs::remove_file(filename_str); - error!("Cannot respond to slash command: {}", why); - } - let _ = fs::remove_file(filename_str); - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let images = match ImageRegister::get_image_register_localised() { - Ok(images) => images, - Err(e) => { - error!("error when creating images command: {}", e); - return command; - } - }; - let command = command - .name("image") - .description("generate an image") - .create_option(|option| { - let option = option - .name("description") - .description("Description of the image you want to generate.") - .kind(CommandOptionType::String) - .required(true); - for image in images.values() { - option - .name_localized(&image.code, &image.option1) - .description_localized(&image.code, &image.option1_desc); - } - option - }); - for image in images.values() { - command - .name_localized(&image.code, &image.name) - .description_localized(&image.code, &image.desc); - } - command -} diff --git a/src/cmd/ai_module/transcript.rs b/src/cmd/ai_module/transcript.rs deleted file mode 100644 index 1c593a1d..00000000 --- a/src/cmd/ai_module/transcript.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::fs::File; -use std::io::copy; -use std::path::Path; -use std::{env, fs}; - -use crate::constant::COLOR; -use crate::function::error_management::error_base_url::error_no_base_url_edit; -use crate::function::error_management::error_file::{error_file_extension, error_file_type}; -use crate::function::error_management::error_request::error_making_request_edit; -use crate::function::error_management::error_resolving_value::error_resolving_value_followup; -use crate::function::error_management::error_token::error_no_token_edit; -use crate::function::general::differed_response::differed_response_with_file_deletion; -use crate::function::general::in_progress::in_progress_embed; -use crate::structure::embed::ai::struct_lang_transcript::TranscriptLocalisedText; -use crate::structure::register::ai::struct_transcript_register::RegisterLocalisedTranscript; -use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; -use reqwest::{multipart, Url}; -use serde_json::Value; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::command::CommandOptionType::Attachment; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; -use uuid::Uuid; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let attachement_option; - if options.get(0).expect("Expected attachement option").name == "video" { - attachement_option = options - .get(0) - .expect("Expected attachement option") - .resolved - .as_ref() - .expect("Expected attachement object"); - } else if options.get(1).expect("Expected attachement option").name == "video" { - attachement_option = options - .get(1) - .expect("Expected attachement option") - .resolved - .as_ref() - .expect("Expected attachement object"); - } else { - attachement_option = options - .get(2) - .expect("Expected attachement option") - .resolved - .as_ref() - .expect("Expected attachement object"); - } - let mut prompt: String = "Do a transcript by first detecting the langage and then transcribing it in the detected langage".to_string(); - let mut lang: String = "en".to_string(); - for option in options { - if option.name == "lang" { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::String(lang_option) = resolved { - lang = lang_option.clone() - } else { - lang = "En".to_string(); - } - } - if option.name == "prompt" { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::String(prompt_option) = resolved { - prompt = prompt_option.clone() - } else { - prompt = "Do a transcript by first detecting the langage and then transcribing it in the detected langage".to_string(); - } - } - } - if let CommandDataOptionValue::Attachment(attachement) = attachement_option { - let localised_text = - match TranscriptLocalisedText::get_transcript_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let content_type = attachement.content_type.clone().unwrap(); - let content = attachement.proxy_url.clone(); - - if !content_type.starts_with("audio/") && !content_type.starts_with("video/") { - error_file_type(ctx, command).await; - return; - } - - let allowed_extensions = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"]; - let parsed_url = Url::parse(content.as_str()).expect("Failed to parse URL"); - let path_segments = parsed_url - .path_segments() - .expect("Failed to retrieve path segments"); - let last_segment = path_segments.last().expect("URL has no path segments"); - - let file_extension = last_segment - .rsplit('.') - .next() - .expect("No file extension found") - .to_lowercase(); - - if !allowed_extensions.contains(&&*file_extension) { - error_file_extension(ctx, command).await; - return; - } - - let response = reqwest::get(content).await.expect("download"); - let uuid_name = Uuid::new_v4(); - let fname = Path::new("./").join(format!("{}.{}", uuid_name, file_extension)); - let file_name = format!("/{}.{}", uuid_name, file_extension); - let mut file = File::create(fname.clone()).expect("file name"); - let resp_byte = response.bytes().await.unwrap(); - copy(&mut resp_byte.as_ref(), &mut file).unwrap(); - let file_to_delete = fname.clone(); - - differed_response_with_file_deletion(ctx, command, file_to_delete.clone()).await; - - let message = match in_progress_embed(ctx, command).await { - Ok(Some(message_option)) => message_option, - Ok(None) => { - error_resolving_value_followup(ctx, command).await; - return; - } - Err(error) => { - println!("Error: {}", error); - return; - } - }; - - let my_path = "./.env"; - let path = Path::new(my_path); - let _ = dotenv::from_path(path); - let api_key = match env::var("AI_API_TOKEN") { - Ok(x) => x, - Err(_) => { - error_no_token_edit(ctx, command, message.clone()).await; - return; - } - }; - let api_base_url = match env::var("AI_API_BASE_URL") { - Ok(x) => x, - Err(_) => { - error_no_base_url_edit(ctx, command, message.clone()).await; - return; - } - }; - let api_url = format!("{}audio/transcriptions", api_base_url); - let client = reqwest::Client::new(); - let mut headers = HeaderMap::new(); - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {}", api_key)).unwrap(), - ); - - let file = fs::read(fname).unwrap(); - let part = multipart::Part::bytes(file) - .file_name(file_name) - .mime_str(content_type.as_str()) - .unwrap(); - let prompt = prompt; - let form = multipart::Form::new() - .part("file", part) - .text("model", "whisper-1") - .text("prompt", prompt) - .text("language", lang) - .text("response_format", "json"); - - let response_result = client - .post(api_url) - .headers(headers) - .multipart(form) - .send() - .await; - let response = match response_result { - Ok(res) => res, - Err(err) => { - eprintln!("Error sending the requests: {}", err); - let _ = fs::remove_file(&file_to_delete); - error_making_request_edit(ctx, command, message).await; - return; - } - }; - let res_result: Result<Value, reqwest::Error> = response.json().await; - - let res = match res_result { - Ok(value) => value, - Err(err) => { - eprintln!("Error parsing response as JSON: {}", err); - let _ = fs::remove_file(&file_to_delete); - error_making_request_edit(ctx, command, message.clone()).await; - return; - } - }; - - let _ = fs::remove_file(&file_to_delete); - - let text = res["text"].as_str().unwrap_or(""); - let mut real_message = message.clone(); - - if let Err(why) = real_message - .edit(&ctx.http, |m| { - m.embed(|e| { - e.title(&localised_text.title) - .description(text.to_string()) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } - let _ = fs::remove_file(&file_to_delete); - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let transcripts = RegisterLocalisedTranscript::get_transcript_register_localised().unwrap(); - let command = command - .name("transcript") - .description("generate a transcript") - .create_option(|option| { - let option = option - .name("video") - .description("File of the video you want the transcript of 25mb max.") - .kind(Attachment) - .required(true); - for transcript in transcripts.values() { - option - .name_localized(&transcript.code, &transcript.option1) - .description_localized(&transcript.code, &transcript.option1_desc); - } - option - }) - .create_option(|option| { - let option = option - .name("prompt") - .description( - "Use optional text to guide style or continue audio. Match audio language.", - ) - .kind(CommandOptionType::String) - .required(false); - for transcript in transcripts.values() { - option - .name_localized(&transcript.code, &transcript.option2) - .description_localized(&transcript.code, &transcript.option2_desc); - } - option - }) - .create_option(|option| { - let option = option - .name("lang") - .description("Input language in ISO-639-1 format improves accuracy and latency.") - .kind(CommandOptionType::String) - .required(false); - for transcript in transcripts.values() { - option - .name_localized(&transcript.code, &transcript.option3) - .description_localized(&transcript.code, &transcript.option3_desc); - } - option - }); - for transcript in transcripts.values() { - command - .name_localized(&transcript.code, &transcript.name) - .description_localized(&transcript.code, &transcript.desc); - } - command -} diff --git a/src/cmd/ai_module/translation.rs b/src/cmd/ai_module/translation.rs deleted file mode 100644 index 83f509cd..00000000 --- a/src/cmd/ai_module/translation.rs +++ /dev/null @@ -1,284 +0,0 @@ -use std::fs::File; -use std::io::copy; -use std::path::Path; -use std::{env, fs}; - -use crate::constant::COLOR; -use crate::function::error_management::error_file::{error_file_extension, error_file_type}; -use crate::function::error_management::error_parsing_json::error_parsing_json_edit; -use crate::function::error_management::error_request::error_making_request_edit; -use crate::function::error_management::error_resolving_value::error_resolving_value_followup; -use crate::function::general::differed_response::differed_response_with_file_deletion; -use crate::function::general::in_progress::in_progress_embed; -use crate::structure::embed::ai::struct_lang_translation::TranslationLocalisedText; -use crate::structure::register::ai::struct_translation_register::RegisterLocalisedTranslation; -use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE}; -use reqwest::{multipart, Url}; -use serde_json::{json, Value}; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::channel::Message; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::command::CommandOptionType::Attachment; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; -use uuid::Uuid; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let mut lang: String = "en".to_string(); - let attachement_option = if options.get(0).expect("Expected attachement option").name == "video" - { - options - .get(0) - .expect("Expected attachement option") - .resolved - .as_ref() - .expect("Expected attachement object") - } else { - options - .get(1) - .expect("Expected attachement option") - .resolved - .as_ref() - .expect("Expected attachement object") - }; - - for option in options { - if option.name == "lang" { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::String(lang_option) = resolved { - lang = lang_option.clone() - } else { - lang = "En".to_string(); - } - } - } - - if let CommandDataOptionValue::Attachment(attachement) = attachement_option { - let localised_text = - match TranslationLocalisedText::get_translation_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let content_type = attachement.content_type.clone().unwrap(); - let content = attachement.proxy_url.clone(); - - if !content_type.starts_with("audio/") && !content_type.starts_with("video/") { - error_file_type(ctx, command).await; - return; - } - - let allowed_extensions = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"]; - let parsed_url = Url::parse(&content).expect("Failed to parse URL"); - let path_segments = parsed_url - .path_segments() - .expect("Failed to retrieve path segments"); - let last_segment = path_segments.last().expect("URL has no path segments"); - - let file_extension = last_segment - .rsplit('.') - .next() - .expect("No file extension found") - .to_lowercase(); - - if !allowed_extensions.contains(&&*file_extension) { - error_file_extension(ctx, command).await; - return; - } - - let response = reqwest::get(content).await.expect("download"); - - let uuid_name = Uuid::new_v4(); - let fname = Path::new("./").join(format!("{}.{}", uuid_name, file_extension)); - let file_name = format!("/{}.{}", uuid_name, file_extension); - let mut file = File::create(fname.clone()).expect("file name"); - let resp_byte = response.bytes().await.unwrap(); - copy(&mut resp_byte.as_ref(), &mut file).unwrap(); - let file_to_delete = fname.clone(); - - differed_response_with_file_deletion(ctx, command, file_to_delete.clone()).await; - - let message: Message = match in_progress_embed(ctx, command).await { - Ok(Some(message_option)) => message_option, - Ok(None) => { - error_resolving_value_followup(ctx, command).await; - return; - } - Err(error) => { - println!("Error: {}", error); - return; - } - }; - - let my_path = "./.env"; - let path = Path::new(my_path); - let _ = dotenv::from_path(path); - let api_key = env::var("AI_API_TOKEN").expect("token"); - let api_base_url = env::var("AI_API_BASE_URL").expect("base url"); - let api_url = format!("{}audio/translations", api_base_url); - let client = reqwest::Client::new(); - let mut headers = HeaderMap::new(); - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {}", api_key)).unwrap(), - ); - - let file = fs::read(fname).unwrap(); - let part = multipart::Part::bytes(file) - .file_name(file_name) - .mime_str(&content_type) - .unwrap(); - let form = multipart::Form::new() - .part("file", part) - .text("model", "whisper-1") - .text("response_format", "json"); - - let response_result = client - .post(api_url) - .headers(headers) - .multipart(form) - .send() - .await; - let response = match response_result { - Ok(res) => res, - Err(err) => { - let _ = fs::remove_file(&file_to_delete); - eprintln!("Error sending the requests: {}", err); - error_making_request_edit(ctx, command, message).await; - return; - } - }; - println!("{:?}", response); - let res_result: Result<Value, reqwest::Error> = response.json().await; - - let res = match res_result { - Ok(value) => value, - Err(err) => { - let _ = fs::remove_file(&file_to_delete); - eprintln!("Error parsing response as JSON: {}", err); - error_parsing_json_edit(ctx, message, command).await; - return; - } - }; - - let text = res["text"].as_str().unwrap_or(""); - let _ = fs::remove_file(&file_to_delete); - if lang != "en" { - let text = translation(lang, text.to_string(), api_key, api_base_url).await; - translation_embed(ctx, text, message, localised_text.clone()).await; - } else { - translation_embed(ctx, text.to_string(), message, localised_text.clone()).await; - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let translations = RegisterLocalisedTranslation::get_translation_register_localised().unwrap(); - let command = command - .name("translation") - .description("generate a translation") - .create_option(|option| { - let option = option - .name("video") - .description("File of the video you want the translation of 25mb max.") - .kind(Attachment) - .required(true); - for translation in translations.values() { - option - .name_localized(&translation.code, &translation.option1) - .description_localized(&translation.code, &translation.option1_desc); - } - option - }) - .create_option(|option| { - let option = option - .name("lang") - .description("Lang in ISO-639-1 format.") - .kind(CommandOptionType::String) - .required(false); - for translation in translations.values() { - option - .name_localized(&translation.code, &translation.option2) - .description_localized(&translation.code, &translation.option2_desc); - } - option - }); - for translation in translations.values() { - command - .name_localized(&translation.code, &translation.name) - .description_localized(&translation.code, &translation.desc); - } - command -} - -pub async fn translation( - lang: String, - text: String, - api_key: String, - api_base_url: String, -) -> String { - let prompt_gpt = format!(" - i will give you a text and a ISO-639-1 code and you will translate it in the corresponding langage - iso code: {} - text: - {} - ", lang, text); - - let api_url = format!("{}chat/completions", api_base_url); - let client = reqwest::Client::new(); - let mut headers = HeaderMap::new(); - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {}", api_key)).unwrap(), - ); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - - let data = json!({ - "model": "gpt-3.5-turbo-16k", - "messages": [{"role": "system", "content": "You are a expert in translating and only do that."},{"role": "user", "content": prompt_gpt}] - }); - - let res: Value = client - .post(api_url) - .headers(headers) - .json(&data) - .send() - .await - .unwrap() - .json() - .await - .unwrap(); - let content = res["choices"][0]["message"]["content"].to_string(); - let no_quote = content.replace('"', ""); - - no_quote.replace("\\n", " \\n ") -} - -pub async fn translation_embed( - ctx: &Context, - text: String, - message: Message, - localised_text: TranslationLocalisedText, -) { - let mut real_message = message.clone(); - if let Err(why) = real_message - .edit(&ctx.http, |m| { - m.embed(|e| { - e.title(&localised_text.title) - .description(text.to_string()) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } -} diff --git a/src/cmd/anilist_module/add_activity.rs b/src/cmd/anilist_module/add_activity.rs deleted file mode 100644 index e17b8262..00000000 --- a/src/cmd/anilist_module/add_activity.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::io::Cursor; - -use crate::constant::COLOR; -use crate::function::error_management::error_no::error_no_anime_specified; -use crate::function::error_management::no_lang_error::error_no_langage_guild_id; -use crate::function::general::differed_response::differed_response; -use crate::function::general::trim::trim_webhook; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::structure::anilist::struct_minimal_anime::MinimalAnimeWrapper; -use crate::structure::embed::anilist::struct_lang_add_activity::AddActivityLocalisedText; -use crate::structure::register::anilist::struct_add_activity_register::RegisterLocalisedAddActivity; -use base64::{engine::general_purpose, Engine as _}; -use image::imageops::FilterType; -use image::{guess_format, GenericImageView, ImageFormat}; -use reqwest::get; -use serde_json::json; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::command::CommandOptionType; -use serenity::model::prelude::application_command::{ - ApplicationCommandInteraction, CommandDataOption, CommandDataOptionValue, -}; -use serenity::model::{Permissions, Timestamp}; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - differed_response(ctx, command).await; - - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS activity_data ( - anime_id TEXT, - timestamp TEXT, - server_id TEXT, - webhook TEXT, - episode TEXT, - name TEXT, - delays INTEGER DEFAULT 0, - PRIMARY KEY (anime_id, server_id) - )", - ) - .execute(&pool) - .await - .unwrap(); - - let mut value = "".to_string(); - let mut delays = 0; - for option in options { - if option.name == "anime_name" { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::String(value_option) = resolved { - value = value_option.clone() - } else { - error_no_anime_specified(ctx, command).await; - return; - } - } - if option.name == "delays" { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::Integer(delays_option) = resolved { - delays = *delays_option - } else { - delays = 0; - } - } - } - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return; - } - }; - - let localised_text = - match AddActivityLocalisedText::get_add_activity_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let data = if value.parse::<i32>().is_ok() { - match MinimalAnimeWrapper::new_minimal_anime_by_id( - localised_text.clone(), - value.parse().unwrap(), - ) - .await - { - Ok(minimal_anime) => minimal_anime, - Err(_) => { - error_no_anime_specified(ctx, command).await; - return; - } - } - } else { - match MinimalAnimeWrapper::new_minimal_anime_by_search( - localised_text.clone(), - value.to_string(), - ) - .await - { - Ok(minimal_anime) => minimal_anime, - Err(_) => { - error_no_anime_specified(ctx, command).await; - return; - } - } - }; - let anime_id = data.get_id(); - - let mut anime_name = data.get_name(); - let channel_id = command.channel_id.0; - if check_if_activity_exist(anime_id, guild_id.clone()).await { - if let Err(why) = command - .create_followup_message(&ctx.http, |f| { - f.embed(|m| { - m.title(&localised_text.title1) - .url(format!("https://anilist.co/anime/{}", data.get_id())) - .timestamp(Timestamp::now()) - .color(COLOR) - .description(format!( - "{} {}", - &localised_text.already_added, - data.get_name() - )) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } - } else { - if anime_name.len() >= 50 { - anime_name = trim_webhook(anime_name.clone(), 50 - anime_name.len() as i32) - } - let bytes = get(data.get_image()).await.unwrap().bytes().await.unwrap(); - let mut img = image::load(Cursor::new(&bytes), guess_format(&bytes).unwrap()).unwrap(); - let (width, height) = img.dimensions(); - let square_size = width.min(height); - let crop_x = (width - square_size) / 2; - let crop_y = (height - square_size) / 2; - - let img = img - .crop(crop_x, crop_y, square_size, square_size) - .resize_exact(128, 128, FilterType::Nearest); - let mut buf = Cursor::new(Vec::new()); - img.write_to(&mut buf, ImageFormat::Jpeg) - .expect("Failed to encode image"); - let base64 = general_purpose::STANDARD.encode(buf.into_inner()); - let image = format!("data:image/jpeg;base64,{}", base64); - let map = json!({ - "avatar": image, - "name": anime_name - }); - let webhook = ctx - .http - .create_webhook(channel_id, &map, None) - .await - .unwrap() - .url() - .unwrap(); - - sqlx::query( - "INSERT OR REPLACE INTO activity_data (anime_id, timestamp, server_id, webhook, episode, name, delays) VALUES (?, ?, ?, ?, ?, ?, ?)", - ) - .bind(anime_id) - .bind(data.get_timestamp()) - .bind(guild_id) - .bind(webhook) - .bind(data.get_episode()) - .bind(data.get_name()) - .bind(data.get_name()) - .bind(delays) - .execute(&pool) - .await - .unwrap(); - if let Err(why) = command - .create_followup_message(&ctx.http, |f| { - f.embed(|m| { - m.title(&localised_text.title2) - .url(format!("https://anilist.co/anime/{}", data.get_id())) - .timestamp(Timestamp::now()) - .color(COLOR) - .description(format!("{} {}", &localised_text.adding, data.get_name())) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let activities = RegisterLocalisedAddActivity::get_add_activity_register_localised().unwrap(); - let command = command - .name("add_activity") - .description("Add an anime activity") - .create_option(|option| { - let option = option - .name("anime_name") - .description("Name of the anime you want to add as an activity") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for activity in activities.values() { - option - .name_localized(&activity.code, &activity.option1) - .description_localized(&activity.code, &activity.option1_desc); - } - option - }) - .create_option(|option| { - let option = option - .name("delays") - .description("A delays in second") - .kind(CommandOptionType::Integer) - .required(false); - for activity in activities.values() { - option - .name_localized(&activity.code, &activity.option2) - .description_localized(&activity.code, &activity.option2_desc); - } - option - }) - .default_member_permissions(Permissions::ADMINISTRATOR); - for activity in activities.values() { - command - .name_localized(&activity.code, &activity.name) - .description_localized(&activity.code, &activity.desc); - } - command -} - -pub async fn check_if_activity_exist(anime_id: i32, server_id: String) -> bool { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - let row: (Option<String>, Option<String>, Option<String>, Option<String>) = sqlx::query_as( - "SELECT anime_id, timestamp, server_id, webhook FROM activity_data WHERE anime_id = ? AND server_id = ?", - ) - .bind(anime_id) - .bind(server_id) - .fetch_one(&pool) - .await - .unwrap_or((None, None, None, None)); - !(row.0.is_none() && row.1.is_none() && row.2.is_none() && row.3.is_none()) -} diff --git a/src/cmd/anilist_module/anime.rs b/src/cmd/anilist_module/anime.rs deleted file mode 100644 index d64bd09f..00000000 --- a/src/cmd/anilist_module/anime.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::error_not_nsfw::error_not_nsfw; -use crate::function::general::get_nsfw_channel::get_nsfw; -use crate::structure::anilist::media::struct_media::MediaWrapper; -use crate::structure::embed::anilist::struct_lang_anime::AnimeLocalisedText; -use crate::structure::register::anilist::struct_anime_register::RegisterLocalisedAnime; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - // Get the content of the first option. - let option = options - .get(0) - .expect("Expected name option") - .resolved - .as_ref() - .expect("Expected name object"); - // Check if the option variable contain the correct value. - if let CommandDataOptionValue::String(value) = option { - let localised_text = match AnimeLocalisedText::get_anime_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let data: MediaWrapper = if value.parse::<i32>().is_ok() { - match MediaWrapper::new_anime_by_id(value.parse().unwrap(), ctx, command).await { - Ok(media_wrapper) => media_wrapper, - Err(_) => { - return; - } - } - } else { - match MediaWrapper::new_anime_by_search(value, ctx, command).await { - Ok(media_wrapper) => media_wrapper, - Err(_) => { - return; - } - } - }; - - if data.get_nsfw() && !get_nsfw(command, ctx).await { - error_not_nsfw(ctx, command).await; - return; - } - - let banner_image = data.get_banner(); - let desc = data.get_desc(); - let thumbnail = data.get_thumbnail(); - let site_url = data.get_url(); - let name = data.get_name(); - - let info = data.get_anime_info(localised_text.clone()); - let genre = data.get_genres(); - let tag = data.get_tags(); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(name) - .url(site_url) - .timestamp(Timestamp::now()) - .description(desc) - .thumbnail(thumbnail) - .image(banner_image) - .field(&localised_text.desc_title, info, false) - .fields(vec![ - (&localised_text.fields_name_1, genre, true), - (&localised_text.fields_name_2, tag, true), - ]) - .color(COLOR) - }) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let animes = RegisterLocalisedAnime::get_anime_register_localised().unwrap(); - let command = command - .name("anime") - .description("Info of an anime") - .create_option(|option| { - let option = option - .name("anime_name") - .description("Name of the anime you want to check") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for anime in animes.values() { - option - .name_localized(&anime.code, &anime.option1) - .description_localized(&anime.code, &anime.option1_desc); - } - option - }); - for anime in animes.values() { - command - .name_localized(&anime.code, &anime.name) - .description_localized(&anime.code, &anime.desc); - } - command -} diff --git a/src/cmd/anilist_module/character.rs b/src/cmd/anilist_module/character.rs deleted file mode 100644 index af4e177a..00000000 --- a/src/cmd/anilist_module/character.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::structure::anilist::character::struct_autocomplete_character::CharacterPageWrapper; -use crate::structure::anilist::character::struct_character::CharacterWrapper; -use crate::structure::embed::anilist::struct_lang_character::CharacterLocalisedText; -use crate::structure::register::anilist::struct_character_register::RegisterLocalisedCharacter; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let option = options - .get(0) - .expect("Expected username option") - .resolved - .as_ref() - .expect("Expected username object"); - if let CommandDataOptionValue::String(value) = option { - let localised_text = - match CharacterLocalisedText::get_character_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let data: CharacterWrapper = if value.parse::<i32>().is_ok() { - match CharacterWrapper::new_character_by_id( - value.parse().unwrap(), - localised_text.clone(), - ) - .await - { - Ok(character_wrapper) => character_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - match CharacterWrapper::new_character_by_search(value, localised_text.clone()).await { - Ok(character_wrapper) => character_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - }; - let name = data.get_name(); - let desc = data.get_desc(localised_text.clone()); - - let image = data.get_image(); - let url = data.get_url(); - - let info = data.get_info(localised_text.clone()); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(name) - .url(url) - .timestamp(Timestamp::now()) - .color(COLOR) - .description(desc) - .thumbnail(image) - .field(&localised_text.info, info, true) - }) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let characters = RegisterLocalisedCharacter::get_character_register_localised().unwrap(); - let command = command - .name("character") - .description("Get information on a character") - .create_option(|option| { - let option = option - .name("name") - .description("The name of the character") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for character in characters.values() { - option - .name_localized(&character.code, &character.name) - .description_localized(&character.code, &character.desc); - } - option - }); - for character in characters.values() { - command - .name_localized(&character.code, &character.name) - .description_localized(&character.code, &character.desc); - } - command -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - let search = &command.data.options.first().unwrap().value; - if let Some(search) = search { - let data = CharacterPageWrapper::new_autocomplete_character(search, 8).await; - let choices = data.get_choices(); - // doesn't matter if it errors - _ = command - .create_autocomplete_response(ctx.http, |response| { - response.set_choices(choices.clone()) - }) - .await; - } -} diff --git a/src/cmd/anilist_module/compare.rs b/src/cmd/anilist_module/compare.rs deleted file mode 100644 index e4f4045b..00000000 --- a/src/cmd/anilist_module/compare.rs +++ /dev/null @@ -1,449 +0,0 @@ -use std::cmp::Ordering; -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::cmd::general_module::lang_struct::CompareLocalisedText; -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use crate::structure::anilist::user::struct_autocomplete_user::UserPageWrapper; -use crate::structure::anilist::user::struct_user::UserWrapper; -use crate::structure::register::anilist::struct_compare_register::RegisterLocalisedCompare; -use serde_json::json; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::command::CommandOptionType; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let option = options - .get(0) - .expect("Expected username option") - .resolved - .as_ref() - .expect("Expected username object"); - let option2 = options - .get(1) - .expect("Expected username option") - .resolved - .as_ref() - .expect("Expected username object"); - if let CommandDataOptionValue::String(username1) = option { - if let CommandDataOptionValue::String(username2) = option2 { - embed(ctx, command, username1, username2).await - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let compares = RegisterLocalisedCompare::get_compare_register_localised().unwrap(); - let command = command - .name("compare") - .description("compare stats of two user") - .create_option(|option| { - let option = option - .name("username") - .description("Username of the 1st anilist user to compare") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for manga in compares.values() { - option - .name_localized(&manga.code, &manga.option1) - .description_localized(&manga.code, &manga.option1_desc); - } - option - }) - .create_option(|option| { - let option = option - .name("username2") - .description("Username of the 2nd anilist user to compare") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for manga in compares.values() { - option - .name_localized(&manga.code, &manga.option2) - .description_localized(&manga.code, &manga.option2_desc); - } - option - }); - for manga in compares.values() { - command - .name_localized(&manga.code, &manga.name) - .description_localized(&manga.code, &manga.desc); - } - command -} - -pub async fn embed( - ctx: &Context, - command: &ApplicationCommandInteraction, - value: &String, - value2: &String, -) { - let mut file = match File::open("../../../lang_file/embed/anilist/compare.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return; - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => error_cant_read_langage_file(ctx, command).await, - } - - let json_data: HashMap<String, CompareLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return; - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return; - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - let data = if value.parse::<i32>().is_ok() { - match UserWrapper::new_user_by_id(value.parse().unwrap()).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - match UserWrapper::new_user_by_search(value).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - }; - - let data2 = if value2.parse::<i32>().is_ok() { - match UserWrapper::new_user_by_id(value2.parse().unwrap()).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - match UserWrapper::new_user_by_search(value2).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - }; - - let anime_count_text = match data.get_anime_count().cmp(&data2.get_anime_count()) { - Ordering::Equal => { - format!( - "{}{}{}{}", - data.get_username(), - &localised_text.connector_user_same_anime, - data2.get_username(), - &localised_text.same_anime - ) - } - Ordering::Less => { - format!( - "{}{}{}", - data2.get_username(), - &localised_text.more_anime, - data.get_username() - ) - } - Ordering::Greater => { - format!( - "{}{}{}", - data.get_username(), - &localised_text.more_anime, - data.get_username() - ) - } - }; - - let anime_watch_time = match data.get_anime_minute().cmp(&data2.get_anime_minute()) { - Ordering::Equal => { - format!( - "{}{}{}{}", - data.get_username(), - &localised_text.connector_user_same_time, - data2.get_username(), - &localised_text.time_anime_watch - ) - } - Ordering::Less => { - format!( - "{}{}{}", - data2.get_username(), - &localised_text.time_anime_watch, - data.get_username() - ) - } - Ordering::Greater => { - format!( - "{}{}{}", - data.get_username(), - &localised_text.time_anime_watch, - data2.get_username() - ) - } - }; - - let manga_count_text = match data.get_manga_count().cmp(&data2.get_manga_count()) { - Ordering::Equal => { - format!( - "{}{}{}{}", - data.get_username(), - &localised_text.connector_user_same_manga, - data2.get_username(), - &localised_text.same_manga - ) - } - Ordering::Greater => { - format!( - "{}{}{}", - data.get_username(), - &localised_text.more_manga, - data2.get_username() - ) - } - Ordering::Less => { - format!( - "{}{}{}", - data2.get_username(), - &localised_text.more_manga, - data.get_username() - ) - } - }; - - let manga_chapter_count = match data.get_manga_completed().cmp(&data2.get_manga_completed()) - { - Ordering::Equal => { - format!( - "{}{}{}{}", - data.get_username(), - &localised_text.connector_user_same_chapter, - data2.get_username(), - &localised_text.same_chapter - ) - } - Ordering::Greater => { - format!( - "{}{}{}", - data.get_username(), - &localised_text.more_chapter, - data2.get_username() - ) - } - Ordering::Less => { - format!( - "{}{}{}", - data2.get_username(), - &localised_text.more_chapter, - data.get_username() - ) - } - }; - - let pref_anime_genre1 = data.get_one_anime_genre(0); - let pref_anime_genre2 = data2.get_one_anime_genre(1); - let pref_anime_genre_text = if pref_anime_genre1 == pref_anime_genre2 { - format!( - "{}{}{}{}{}", - data.get_username(), - &localised_text.genre_same_connector_anime, - data2.get_username(), - &localised_text.genre_same_prefer_anime, - pref_anime_genre1 - ) - } else { - format!( - "{}{}{}{}{}{}{}", - data.get_username(), - &localised_text.diff_pref_genre_1_anime, - pref_anime_genre1, - &localised_text.diff_pref_genre_while_anime, - data2.get_username(), - &localised_text.diff_pref_genre_2_anime, - pref_anime_genre2 - ) - }; - - let pref_anime_tag1 = data.get_one_anime_tag(0); - let pref_anime_tag2 = data2.get_one_anime_tag(0); - let pref_anime_tag_text = if pref_anime_tag1 == pref_anime_tag2 { - format!( - "{}{}{}{}{}", - data.get_username(), - &localised_text.same_tag_connector_anime, - data2.get_username(), - &localised_text.same_tag_prefer_anime, - pref_anime_tag1 - ) - } else { - format!( - "{}{}{}{}{}{}{}", - data.get_username(), - &localised_text.diff_pref_tag_1_anime, - pref_anime_tag1, - &localised_text.diff_pref_tag_while_anime, - data2.get_username(), - &localised_text.diff_pref_tag_2_anime, - pref_anime_tag2 - ) - }; - - let pref_manga_genre1 = data.get_one_manga_genre(0); - let pref_manga_genre2 = data2.get_one_manga_genre(0); - let pref_manga_genre_text = if pref_manga_genre1 == pref_manga_genre2 { - format!( - "{}{}{}{}{}", - data.get_username(), - &localised_text.genre_same_connector_manga, - data2.get_username(), - &localised_text.genre_same_prefer_manga, - pref_manga_genre1 - ) - } else { - format!( - "{}{}{}{}{}{}{}", - data.get_username(), - &localised_text.diff_pref_genre_1_manga, - pref_manga_genre1, - &localised_text.diff_pref_genre_while_manga, - data2.get_username(), - &localised_text.diff_pref_genre_2_manga, - pref_manga_genre2 - ) - }; - - let pref_manga_tag1 = data.get_one_manga_tag(0); - let pref_manga_tag2 = data2.get_one_manga_tag(0); - let pref_manga_tag_text = if pref_manga_tag1 == pref_manga_tag2 { - format!( - "{}{}{}{}{}", - data.get_username(), - &localised_text.same_tag_connector_manga, - data2.get_username(), - &localised_text.same_tag_prefer_manga, - pref_manga_tag1 - ) - } else { - format!( - "{}{}{}{}{}{}{}", - data.get_username(), - &localised_text.diff_pref_tag_1_manga, - pref_manga_tag1, - &localised_text.diff_pref_tag_while_manga, - data2.get_username(), - &localised_text.diff_pref_tag_2_manga, - pref_manga_tag2 - ) - }; - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title("Comparison") - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .field( - "", - format!( - "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", - &localised_text.sub_title_anime, - anime_count_text, - &localised_text.watch_time, - anime_watch_time, - &localised_text.pref_genre_anime, - pref_anime_genre_text, - &localised_text.pref_tag_anime, - pref_anime_tag_text, - &localised_text.sub_title_manga, - manga_count_text, - &localised_text.chapter_read, - manga_chapter_count, - &localised_text.pref_genre_manga, - pref_manga_genre_text, - &localised_text.pref_tag_manga, - pref_manga_tag_text - ), - false, - ) - .color(COLOR) - }) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } - } else { - no_langage_error(ctx, command).await; - } -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - // Create an empty vector to store the choices - let mut choices = Vec::new(); - - // Get the first autocomplete option and add to choices - if let Some(option1) = command.data.options.get(0) { - if let Some(value1) = &option1.value { - let data1 = UserPageWrapper::new_autocomplete_user(value1, 8).await; - choices.extend(data1.get_choice()); - } - } - - // Get the second autocomplete option and add to choices - if let Some(option2) = command.data.options.get(1) { - if let Some(value2) = &option2.value { - let data2 = UserPageWrapper::new_autocomplete_user(value2, 8).await; - choices.extend(data2.get_choice()); - } - } - - // Create a single autocomplete response with the collected choices - let choices_json = json!(choices); - _ = command - .create_autocomplete_response(ctx.http.clone(), |response| { - response.set_choices(choices_json) - }) - .await; -} diff --git a/src/cmd/anilist_module/get_activity.rs b/src/cmd/anilist_module/get_activity.rs deleted file mode 100644 index 93fa1fb7..00000000 --- a/src/cmd/anilist_module/get_activity.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::function::general::differed_response::differed_response; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) -> String { - differed_response(ctx, command).await; - - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - - "good".to_string() -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("get_activity") - .description("List all anime activity") -} diff --git a/src/cmd/anilist_module/get_register_user.rs b/src/cmd/anilist_module/get_register_user.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/cmd/anilist_module/get_register_user.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/cmd/anilist_module/level.rs b/src/cmd/anilist_module/level.rs deleted file mode 100644 index e44a45ca..00000000 --- a/src/cmd/anilist_module/level.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::function::error_management::common::custom_error; -use crate::structure::anilist::level::struct_level::LevelSystem; -use crate::structure::anilist::user::struct_user::{Statuses, UserWrapper}; -use crate::structure::embed::anilist::struct_lang_level::LevelLocalisedText; -use crate::structure::register::anilist::struct_level_register::RegisterLocalisedLevel; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let option = options - .get(0) - .expect("Expected username option") - .resolved - .as_ref() - .expect("Expected username object"); - if let CommandDataOptionValue::String(value) = option { - let localised_text = match LevelLocalisedText::get_level_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let data = if value.parse::<i32>().is_ok() { - match UserWrapper::new_user_by_id(value.parse().unwrap()).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - match UserWrapper::new_user_by_search(value).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - }; - let profile_picture = data.data.user.avatar.large.clone().unwrap_or( "https://imgs.search.brave.com/CYnhSvdQcm9aZe3wG84YY0B19zT2wlAuAkiAGu0mcLc/rs:fit:640:400:1/g:ce/aHR0cDovL3d3dy5m/cmVtb250Z3VyZHdh/cmEub3JnL3dwLWNv/bnRlbnQvdXBsb2Fk/cy8yMDIwLzA2L25v/LWltYWdlLWljb24t/Mi5wbmc".to_string()); - let user = data.data.user.name.clone().unwrap_or("N/A".to_string()); - let anime = data.data.user.statistics.anime.clone(); - let manga = data.data.user.statistics.manga.clone(); - let (anime_completed, anime_watching) = get_total(anime.statuses.clone()); - let (manga_completed, manga_reading) = get_total(manga.statuses.clone()); - - let chap = manga.chapters_read.unwrap_or(0) as f64; - let min = anime.minutes_watched.unwrap_or(0) as f64; - let input = (anime_completed * 2.5 + anime_watching * 1.0) - + (manga_completed * 2.5 + manga_reading * 1.0) - + chap * 5.0 - + (min / 5.0); - - let user_level; - let user_progression; - if let Some((level, level_progress, level_progress_total)) = LevelSystem::get_level(input) { - user_level = level; - user_progression = format!("{:.3}/{:.3}", level_progress, level_progress_total) - } else { - user_level = 0; - user_progression = "0/0".to_string(); - } - - let color = data.get_color(); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(user) - .timestamp(Timestamp::now()) - .thumbnail(profile_picture) - .fields(vec![( - "".to_string(), - format!( - "{}{}{}{}{}{}{}", - &localised_text.level, - user_level, - &localised_text.xp, - input, - &localised_text.progression_1, - user_progression, - &localised_text.progression_2 - ), - false, - )]) - .color(color) - }) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let levels = RegisterLocalisedLevel::get_level_register_localised().unwrap(); - let command = command - .name("level") - .description("Weeb level of a user") - .create_option(|option| { - let option = option - .name("username") - .description("Username of the anilist user you want to know the level of") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for level in levels.values() { - option - .name_localized(&level.code, &level.option1) - .description_localized(&level.code, &level.option1_desc); - } - option - }); - for level in levels.values() { - command - .name_localized(&level.code, &level.name) - .description_localized(&level.code, &level.desc); - } - command -} - -pub fn get_total(media: Vec<Statuses>) -> (f64, f64) { - let mut watching = 0.0; - let mut completed = 0.0; - for i in media { - if i.status == *"COMPLETED" { - completed = i.count as f64; - } else if i.status == *"CURRENT" { - watching = i.count as f64 - } - } - (watching, completed) -} diff --git a/src/cmd/anilist_module/ln.rs b/src/cmd/anilist_module/ln.rs deleted file mode 100644 index e1173266..00000000 --- a/src/cmd/anilist_module/ln.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::function::anilist::command_media::embed; -use crate::structure::anilist::media::struct_autocomplete_media::MediaPageWrapper; -use crate::structure::register::anilist::struct_ln_register::RegisterLocalisedLN; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - embed(options, ctx, command, "NOVEL").await; -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let lns = RegisterLocalisedLN::get_ln_register_localised().unwrap(); - let command = command - .name("ln") - .description("Info of a light novel") - .create_option(|option| { - let option = option - .name("ln_name") - .description("Name of the light novel you want to check") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for ln in lns.values() { - option - .name_localized(&ln.code, &ln.option1) - .description_localized(&ln.code, &ln.option1_desc); - } - option - }); - for ln in lns.values() { - command - .name_localized(&ln.code, &ln.name) - .description_localized(&ln.code, &ln.desc); - } - command -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - let search = &command.data.options.first().unwrap().value; - if let Some(search) = search { - let data = MediaPageWrapper::new_autocomplete_ln(search, 8, "MANGA", "NOVEL").await; - let choices = data.get_choices(); - // doesn't matter if it errors - _ = command - .create_autocomplete_response(ctx.http, |response| { - response.set_choices(choices.clone()) - }) - .await; - } -} diff --git a/src/cmd/anilist_module/manga.rs b/src/cmd/anilist_module/manga.rs deleted file mode 100644 index 9d70c10d..00000000 --- a/src/cmd/anilist_module/manga.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::function::anilist::command_media::embed; -use crate::structure::anilist::media::struct_autocomplete_media::MediaPageWrapper; -use crate::structure::register::anilist::struct_manga_register::RegisterLocalisedManga; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - embed(options, ctx, command, "MANGA").await; -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let mangas = RegisterLocalisedManga::get_manga_register_localised().unwrap(); - let command = command - .name("manga") - .description("Info of a manga") - .create_option(|option| { - let option = option - .name("manga_name") - .description("Name of the manga you want to check") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for manga in mangas.values() { - option - .name_localized(&manga.code, &manga.option1) - .description_localized(&manga.code, &manga.option1_desc); - } - option - }); - for manga in mangas.values() { - command - .name_localized(&manga.code, &manga.name) - .description_localized(&manga.code, &manga.desc); - } - command -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - let search = &command.data.options.first().unwrap().value; - if let Some(search) = search { - let data = MediaPageWrapper::new_autocomplete_manga(search, 8, "MANGA", "NOVEL").await; - let choices = data.get_choices(); - // doesn't matter if it errors - _ = command - .create_autocomplete_response(ctx.http, |response| { - response.set_choices(choices.clone()) - }) - .await; - } -} diff --git a/src/cmd/anilist_module/random.rs b/src/cmd/anilist_module/random.rs deleted file mode 100644 index 4769141b..00000000 --- a/src/cmd/anilist_module/random.rs +++ /dev/null @@ -1,289 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::cmd::general_module::lang_struct::RandomLocalisedText; -use crate::constant::COLOR; -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::differed_response::differed_response; -use crate::function::general::get_guild_langage::get_guild_langage; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::structure::anilist::random::struct_random::PageWrapper; -use crate::structure::anilist::random::struct_site_statistic_anime::SiteStatisticsAnimeWrapper; -use crate::structure::anilist::random::struct_site_statistic_manga::SiteStatisticsMangaWrapper; -use chrono::Utc; -use rand::prelude::*; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; -use sqlx::{Pool, Sqlite}; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let database_url = "./cache.db"; - let pool = get_sqlite_pool(database_url).await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS cache_stats ( - key TEXT PRIMARY KEY, - response TEXT NOT NULL, - last_updated INTEGER NOT NULL, - last_page INTEGER NOT NULL - )", - ) - .execute(&pool) - .await - .unwrap(); - - let option = options - .get(0) - .expect("Expected username option") - .resolved - .as_ref() - .expect("Expected username object"); - - if let CommandDataOptionValue::String(random_type) = option { - differed_response(ctx, command).await; - - let row: (Option<String>, Option<i64>, Option<i64>) = sqlx::query_as( - "SELECT response, last_updated, last_page FROM cache_stats WHERE key = ?", - ) - .bind(random_type) - .fetch_one(&pool) - .await - .unwrap_or((None, None, None)); - - let (response, last_updated, last_page): (Option<String>, Option<i64>, Option<i64>) = row; - - let page_number = last_page.unwrap_or(1567); // This is as today date the last page, i will update it sometime. - - let previous_page = page_number - 1; - let cached_response = response.unwrap_or("Nothing".to_string()); - - if let Some(updated) = last_updated { - let duration_since_updated = Utc::now().timestamp() - updated; - if duration_since_updated < 24 * 60 * 60 { - embed(page_number, random_type.to_string(), ctx, command).await; - } else { - update_cache( - page_number, - random_type, - ctx, - command, - previous_page, - cached_response, - pool, - ) - .await - } - } else { - update_cache( - page_number, - random_type, - ctx, - command, - previous_page, - cached_response, - pool, - ) - .await - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("random") - .description("Get a random manga or anime") - .create_option(|option| { - option - .name("type") - .description("Type of the media you want manga or anime. manga include ln atm.") - .kind(CommandOptionType::String) - .required(true) - .add_string_choice("manga", "manga") - .add_string_choice("anime", "anime") - }) -} - -pub async fn embed( - last_page: i64, - random_type: String, - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let number = thread_rng().gen_range(1..=last_page); - if random_type == "manga" { - let data = PageWrapper::new_manga_page(number).await; - - let url = data.get_manga_url(); - - follow_up_message(ctx, command, data, url).await - } else if random_type == "anime" { - let data = PageWrapper::new_anime_page(number).await; - - let url = data.get_anime_url(); - - follow_up_message(ctx, command, data, url).await - } else { - let mut file = match File::open("./lang_file/embed/anilist/random.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return; - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => error_cant_read_langage_file(ctx, command).await, - } - - let json_data: HashMap<String, RandomLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return; - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return; - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - if let Err(why) = command - .create_followup_message(&ctx.http, |f| { - f.embed(|m| { - m.title(&localised_text.error_title) - .description(&localised_text.error_message) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } - } - } -} - -pub async fn follow_up_message( - ctx: &Context, - command: &ApplicationCommandInteraction, - data: PageWrapper, - url: String, -) { - let mut file = - File::open("../../../lang_file/embed/anilist/random.json").expect("Failed to open file"); - let mut json = String::new(); - file.read_to_string(&mut json).expect("Failed to read file"); - - let title_user = data.get_user_pref_title(); - let title = data.get_native_title(); - let genre = data.get_genre(); - let tag = data.get_tags(); - let format = data.get_format(); - let description = data.get_description(); - let cover_image = data.get_cover_image(); - - let json_data: HashMap<String, RandomLocalisedText> = - serde_json::from_str(&json).expect("Failed to parse JSON"); - - let guild_id = command.guild_id.unwrap().0.to_string().clone(); - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - if let Err(why) = command - .create_followup_message(&ctx.http, |f| { - f.embed(|m| { - m.title(format!("{}/{}", title_user, title)) - .description(format!( - "{}{}{}{}{}{}{}{}", - &localised_text.genre, - genre, - &localised_text.tag, - tag, - &localised_text.format, - format, - &localised_text.desc, - description - )) - .timestamp(Timestamp::now()) - .color(COLOR) - .thumbnail(cover_image) - .url(url) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } - } -} - -pub async fn update_cache( - mut page_number: i64, - random_type: &String, - ctx: &Context, - command: &ApplicationCommandInteraction, - mut previous_page: i64, - mut cached_response: String, - pool: Pool<Sqlite>, -) { - let now = Utc::now().timestamp(); - - if random_type.as_str() == "manga" { - loop { - let (data, res) = SiteStatisticsMangaWrapper::new_manga(page_number).await; - let has_next_page = data.has_next_page(); - - if !has_next_page { - break; - } - cached_response = res.to_string(); - previous_page = page_number; - - page_number += 1; - } - } else if random_type.as_str() == "anime" { - loop { - let (data, res) = SiteStatisticsAnimeWrapper::new_anime(page_number).await; - let has_next_page = data.has_next_page(); - - if !has_next_page { - break; - } - cached_response = res.to_string(); - previous_page = page_number; - - page_number += 1; - } - } - - sqlx::query("INSERT OR REPLACE INTO cache_stats (key, response, last_updated, last_page) VALUES (?, ?, ?, ?)") - .bind(random_type) - .bind(&cached_response) - .bind(now) - .bind(previous_page) - .execute(&pool) - .await.unwrap(); - embed(previous_page, random_type.to_string(), ctx, command).await; -} diff --git a/src/cmd/anilist_module/register.rs b/src/cmd/anilist_module/register.rs deleted file mode 100644 index 4e3a107b..00000000 --- a/src/cmd/anilist_module/register.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::constant::COLOR; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::structure::embed::anilist::struct_lang_register::RegisterLocalisedText; -use crate::structure::register::anilist::struct_register_register::RegisterLocalisedRegister; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS registered_user ( - user_id TEXT PRIMARY KEY, - anilist_username TEXT NOT NULL - )", - ) - .execute(&pool) - .await - .unwrap(); - let option = options - .get(0) - .expect("Expected username option") - .resolved - .as_ref() - .expect("Expected username object"); - - if let CommandDataOptionValue::String(_) = option { - let user_id = &command.user.id.to_string(); - let username = &command.user.name; - let user_pfp_ref = &command.user.avatar.as_ref().unwrap(); - let profile_picture; - if let Some(first) = user_pfp_ref.split('_').next() { - if first == "a" { - profile_picture = format!( - "https://cdn.discordapp.com/avatars/{}/{}.gif?size=1024", - user_id, user_pfp_ref - ); - } else { - profile_picture = format!( - "https://cdn.discordapp.com/avatars/{}/{}.webp?size=1024", - user_id, user_pfp_ref - ); - } - } else { - profile_picture = format!( - "https://cdn.discordapp.com/avatars//{}/{}.webp?size=1024", - user_id, user_pfp_ref - ); - } - sqlx::query( - "INSERT OR REPLACE INTO registered_user (user_id, anilist_username) VALUES (?, ?)", - ) - .bind(user_id) - .bind(username) - .execute(&pool) - .await - .unwrap(); - - let localised_text = match RegisterLocalisedText::get_register_localised(ctx, command).await - { - Ok(data) => data, - Err(_) => return, - }; - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(username) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .thumbnail(profile_picture) - .color(COLOR) - .description(format!( - "{}{}({}){}{}{}", - &localised_text.part_1, - username, - user_id, - &localised_text.part_2, - username, - &localised_text.part_3 - )) - }) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let registers = RegisterLocalisedRegister::get_register_register_localised().unwrap(); - let command = command - .name("register") - .description("Register your anilist username for ease of use.") - .create_option(|option| { - let option = option - .name("username") - .description("Your anilist user name.") - .kind(CommandOptionType::String) - .required(true); - for register in registers.values() { - option - .name_localized(®ister.code, ®ister.option1) - .description_localized(®ister.code, ®ister.option1_desc); - } - option - }); - for register in registers.values() { - command - .name_localized(®ister.code, ®ister.name) - .description_localized(®ister.code, ®ister.desc); - } - command -} diff --git a/src/cmd/anilist_module/register_waifu.rs b/src/cmd/anilist_module/register_waifu.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/cmd/anilist_module/register_waifu.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/cmd/anilist_module/search.rs b/src/cmd/anilist_module/search.rs deleted file mode 100644 index 2f62c2ed..00000000 --- a/src/cmd/anilist_module/search.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::cmd::anilist_module::{anime, character, ln, manga, staff, studio, user}; -use crate::function::error_management::error_not_implemented::error_not_implemented; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - // Get the content of the first option. - let option = options - .get(1) - .expect("Expected type option") - .resolved - .as_ref() - .expect("Expected type object"); - // Check if the option variable contain the correct value. - if let CommandDataOptionValue::String(search_type) = option { - let search_types = search_type.as_ref(); - match search_types { - "anime" => anime::run(&command.data.options, ctx, command).await, - "character" => character::run(&command.data.options, ctx, command).await, - "ln" => ln::run(&command.data.options, ctx, command).await, - "manga" => manga::run(&command.data.options, ctx, command).await, - "staff" => staff::run(&command.data.options, ctx, command).await, - "user" => user::run(&command.data.options, ctx, command).await, - "studio" => studio::run(&command.data.options, ctx, command).await, - _ => error_not_implemented(ctx, command).await, - }; - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("search") - .description("Info of an anime") - .create_option(|option| { - option - .name("name") - .description("The name of the anime/user/ln/etc...") - .kind(CommandOptionType::String) - .required(true) - }) - .create_option(|option| { - option - .name("search_type") - .description("The type of the search you want.") - .kind(CommandOptionType::String) - .add_string_choice("anime", "anime") - .add_string_choice("character", "character") - .add_string_choice("ln", "ln") - .add_string_choice("manga", "manga") - .add_string_choice("staff", "staff") - .add_string_choice("user", "user") - .add_string_choice("studio", "studio") - .required(true) - }) -} diff --git a/src/cmd/anilist_module/send_activity.rs b/src/cmd/anilist_module/send_activity.rs deleted file mode 100644 index cdf6a1b3..00000000 --- a/src/cmd/anilist_module/send_activity.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::env; -use std::thread::sleep; -use std::time::Duration; - -use crate::constant::COLOR; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::structure::anilist::struct_minimal_anime::MinimalAnimeWrapper; -use crate::structure::embed::anilist::struct_lang_send_activity::SendActivityLocalisedText; -use chrono::Utc; -use serenity::http::Http; -use serenity::model::channel::Embed; -use serenity::model::prelude::Webhook; -use sqlx::FromRow; - -#[derive(Debug, FromRow, Clone)] -pub struct ActivityData { - anime_id: Option<String>, - timestamp: Option<String>, - server_id: Option<String>, - webhook: Option<String>, - episode: Option<String>, - name: Option<String>, - delays: Option<i32>, -} - -pub async fn manage_activity() { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - sqlx::query( - "CREATE TABLE IF NOT EXISTS activity_data ( - anime_id TEXT, - timestamp TEXT, - server_id TEXT, - webhook TEXT, - episode TEXT, - name TEXT, - delays INTEGER DEFAULT 0, - PRIMARY KEY (anime_id, server_id) - )", - ) - .execute(&pool) - .await - .unwrap(); - loop { - tokio::spawn(async move { - send_activity().await; - }); - sleep(Duration::from_secs(1)); - } -} - -pub async fn send_activity() { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - let now = Utc::now().timestamp().to_string(); - let rows: Vec<ActivityData> = sqlx::query_as( - "SELECT anime_id, timestamp, server_id, webhook, episode, name, delays FROM activity_data WHERE timestamp = ?", - ) - .bind(now.clone()) - .fetch_all(&pool) - .await - .unwrap(); - for row in rows { - if Utc::now().timestamp().to_string() != row.timestamp.clone().unwrap() { - } else { - let row2 = row.clone(); - let guild_id = row.server_id.clone(); - if row.delays.unwrap() != 0 { - tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs((row2.delays.unwrap()) as u64)).await; - send_specific_activity(row, guild_id.unwrap(), row2).await - }); - } else { - send_specific_activity(row, guild_id.unwrap(), row2).await - } - } - } -} - -pub async fn update_info(row: ActivityData, guild_id: String) { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - sleep(Duration::from_secs(30 * 60)); - let data = - MinimalAnimeWrapper::new_minimal_anime_by_id_no_error(row.anime_id.clone().unwrap()).await; - sqlx::query( - "INSERT OR REPLACE INTO activity_data (anime_id, timestamp, server_id, webhook, episode, name, delays) VALUES (?, ?, ?, ?, ?, ?, ?)", - ) - .bind(row.anime_id.unwrap()) - .bind(data.get_timestamp()) - .bind(guild_id) - .bind(row.webhook.unwrap()) - .bind(data.get_episode()) - .bind(data.get_name()) - .bind(data.get_name()) - .bind(row.delays.unwrap()) - .execute(&pool) - .await - .unwrap(); -} - -pub async fn send_specific_activity(row: ActivityData, guild_id: String, row2: ActivityData) { - let localised_text = SendActivityLocalisedText::get_send_activity_localised(guild_id.clone()) - .await - .unwrap(); - let my_path = "./.env"; - let path = std::path::Path::new(my_path); - let _ = dotenv::from_path(path); - let token = env::var("DISCORD_TOKEN").expect("discord token"); - let http = Http::new(token.as_str()); - let webhook = Webhook::from_url(&http, row.webhook.clone().unwrap().as_ref()) - .await - .unwrap(); - let embed = Embed::fake(|e| { - e.title(&localised_text.title) - .url(format!( - "https://anilist.co/anime/{}", - row.anime_id.unwrap() - )) - .description(format!( - "{}{} {}{} {}", - &localised_text.ep, - row.episode.unwrap(), - &localised_text.of, - row.name.unwrap(), - localised_text.end, - )) - .color(COLOR) - }); - webhook - .execute(&http, false, |w| w.embeds(vec![embed])) - .await - .unwrap(); - tokio::spawn(async move { update_info(row2, guild_id).await }); -} diff --git a/src/cmd/anilist_module/staff.rs b/src/cmd/anilist_module/staff.rs deleted file mode 100644 index 4d1eea12..00000000 --- a/src/cmd/anilist_module/staff.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::structure::anilist::staff::struct_autocomplete_staff::StaffPageWrapper; -use crate::structure::anilist::staff::struct_staff::StaffWrapper; -use crate::structure::embed::anilist::struct_lang_staff::StaffLocalisedText; -use crate::structure::register::anilist::struct_staff_register::RegisterLocalisedStaff; -use serde_json::json; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let option = options - .get(0) - .expect("Expected name option") - .resolved - .as_ref() - .expect("Expected name object"); - if let CommandDataOptionValue::String(value) = option { - let data = if value.parse::<i32>().is_ok() { - match StaffWrapper::new_staff_by_id(value.parse().unwrap()).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - match StaffWrapper::new_staff_by_search(value).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - }; - - let staff_url = data.get_url(); - - let staff_name = data.get_name(); - - let image = data.get_image(); - - let result_role: String = data.format_role(); - - let result_va: String = data.format_va(); - - let localised_text = match StaffLocalisedText::get_staff_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let desc = data.get_desc(&localised_text); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(staff_name) - .timestamp(Timestamp::now()) - .color(COLOR) - .fields(vec![ - (&localised_text.desc_title, desc, false), - (&localised_text.media, result_role, true), - (&localised_text.va, result_va, true), - ]) - .url(staff_url) - .image(image) - }) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let staffs = RegisterLocalisedStaff::get_staff_register_localised().unwrap(); - let command = command - .name("staff") - .description("Get info of a staff") - .create_option(|option| { - let option = option - .name("staff_name") - .description("Name of the staff you want info about.") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for staff in staffs.values() { - option - .name_localized(&staff.code, &staff.option1) - .description_localized(&staff.code, &staff.option1_desc); - } - option - }); - for staff in staffs.values() { - command - .name_localized(&staff.code, &staff.name) - .description_localized(&staff.code, &staff.desc); - } - command -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - let search = &command.data.options.first().unwrap().value; - if let Some(search) = search { - let data = StaffPageWrapper::new_autocomplete_staff(search, 8).await; - - let choices = data.get_choice(); - // doesn't matter if it errors - let choices_json = json!(choices); - _ = command - .create_autocomplete_response(ctx.http.clone(), |response| { - response.set_choices(choices_json) - }) - .await; - } -} diff --git a/src/cmd/anilist_module/studio.rs b/src/cmd/anilist_module/studio.rs deleted file mode 100644 index 95867af5..00000000 --- a/src/cmd/anilist_module/studio.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::structure::anilist::studio::struct_autocomplete_studio::StudioPageWrapper; -use crate::structure::anilist::studio::struct_studio::StudioWrapper; -use crate::structure::embed::anilist::struct_lang_studio::StudioLocalisedText; -use crate::structure::register::anilist::struct_studio_register::RegisterLocalisedStudio; -use serde_json::json; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::prelude::InteractionResponseType; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let option = options - .get(0) - .expect("Expected name option") - .resolved - .as_ref() - .expect("Expected name object"); - if let CommandDataOptionValue::String(value) = option { - let data = if value.parse::<i32>().is_ok() { - match StudioWrapper::new_studio_by_id(value.parse().unwrap()).await { - Ok(studio_wrapper) => studio_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - match StudioWrapper::new_studio_by_search(value).await { - Ok(studio_wrapper) => studio_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - }; - let localised_text = match StudioLocalisedText::get_studio_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let name = data.get_studio_name(); - let url = data.get_site_url(); - let desc = data.get_desc(localised_text.clone()); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(name) - .url(url) - .timestamp(Timestamp::now()) - .color(COLOR) - .description(desc) - }) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let studios = RegisterLocalisedStudio::get_studio_register_localised().unwrap(); - let command = command - .name("studio") - .description("Get info on an studio") - .create_option(|option| { - let option = option - .name("studio") - .description("The name of the studio.") - .kind(CommandOptionType::String) - .required(true) - .set_autocomplete(true); - for studio in studios.values() { - option - .name_localized(&studio.code, &studio.option1) - .description_localized(&studio.code, &studio.option1_desc); - } - option - }); - for studio in studios.values() { - command - .name_localized(&studio.code, &studio.name) - .description_localized(&studio.code, &studio.desc); - } - command -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - let search = &command.data.options.first().unwrap().value; - if let Some(search) = search { - let data = StudioPageWrapper::new_autocomplete_staff(search, 8).await; - let choices = data.get_choice(); - // doesn't matter if it errors - let choices_json = json!(choices); - _ = command - .create_autocomplete_response(ctx.http.clone(), |response| { - response.set_choices(choices_json) - }) - .await; - } -} diff --git a/src/cmd/anilist_module/user.rs b/src/cmd/anilist_module/user.rs deleted file mode 100644 index 693725c8..00000000 --- a/src/cmd/anilist_module/user.rs +++ /dev/null @@ -1,202 +0,0 @@ -use crate::function::error_management::common::custom_error; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::structure::anilist::user::struct_user::UserWrapper; -use crate::structure::embed::anilist::struct_lang_user::UserLocalisedText; -use crate::structure::register::anilist::struct_user_register::RegisterLocalisedUser; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn run( - _options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - if let Some(option) = _options.get(0) { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::String(user) = resolved { - embed(_options, ctx, command, user).await - } else { - custom_error(ctx, command, "error_management").await - } - } else { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - let user_id = &command.user.id.to_string(); - let row: (Option<String>, Option<String>) = sqlx::query_as( - "SELECT anilist_username, user_id FROM registered_user WHERE user_id = ?", - ) - .bind(user_id) - .fetch_one(&pool) - .await - .unwrap_or((None, None)); - let (user, _): (Option<String>, Option<String>) = row; - embed( - _options, - ctx, - command, - &user.unwrap_or("N/A".parse().unwrap()), - ) - .await - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let users = RegisterLocalisedUser::get_user_register_localised().unwrap(); - let command = command - .name("user") - .description("Info of an anilist user") - .create_option(|option| { - let option = option - .name("username") - .description("Username of the anilist user you want to check") - .kind(CommandOptionType::String) - .required(false) - .set_autocomplete(true); - for user in users.values() { - option - .name_localized(&user.code, &user.option1) - .description_localized(&user.code, &user.option1_desc); - } - option - }); - for user in users.values() { - command - .name_localized(&user.code, &user.name) - .description_localized(&user.code, &user.desc); - } - command -} - -pub async fn embed( - _options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, - value: &String, -) { - let data = if value.parse::<i32>().is_ok() { - match UserWrapper::new_user_by_id(value.parse().unwrap()).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - match UserWrapper::new_user_by_search(value).await { - Ok(user_wrapper) => user_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - }; - - let localised_text = match UserLocalisedText::get_user_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let user_url = data.get_user_url(); - - let color = data.get_color(); - - let chap = data.get_manga_chapter(); - let manga_genre = data.get_manga_genre(); - let manga_count = data.get_manga_count(); - let manga_score = data.get_manga_score(); - let manga_standard_deviation = data.get_manga_standard_deviation(); - let manga_tag_name = data.get_manga_tag(); - let manga_completed = data.get_manga_completed(); - - let time_watched = data.time_anime_watched(localised_text.clone()); - - let anime_count = data.get_anime_count(); - let anime_score = data.get_anime_score(); - let anime_standard_deviation = data.get_anime_standard_deviation(); - let anime_tag_name = data.get_anime_tag(); - let anime_genre = data.get_anime_genre(); - let anime_completed = data.get_anime_completed(); - - let manga_url = data.get_user_manga_url(); - let anime_url = data.get_user_anime_url(); - - let user = data.get_username(); - let profile_picture = data.get_pfp(); - let banner = data.get_banner(); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(user) - .url(&user_url) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .thumbnail(profile_picture) - .image(banner) - .fields(vec![ - ( - "".to_string(), - format!( - "**[{}]({})**{}{}{}{}{}{}{}{:.2}{}{:.2}{}{}{}{}", - &localised_text.manga_title, - manga_url, - &localised_text.manga_count, - manga_count, - &localised_text.manga_completed, - manga_completed, - &localised_text.manga_chapter_read, - chap, - &localised_text.manga_mean_score, - manga_score, - &localised_text.manga_standard_deviation, - manga_standard_deviation, - &localised_text.manga_pref_tag, - manga_tag_name, - &localised_text.manga_pref_genre, - manga_genre - ), - false, - ), - ( - "".to_string(), - format!( - "**[{}]({})**{}{}{}{}{}{}{}{:.2}{}{:.2}{}{}{}{}", - &localised_text.anime_title, - anime_url, - &localised_text.anime_count, - anime_count, - &localised_text.anime_completed, - anime_completed, - &localised_text.anime_time_watch, - time_watched, - &localised_text.anime_mean_score, - anime_score, - &localised_text.anime_standard_deviation, - anime_standard_deviation, - &localised_text.anime_pref_tag, - anime_tag_name, - &localised_text.anime_pref_genre, - anime_genre - ), - false, - ), - ]) - .color(color) - }) - }) - }) - .await - { - println!("{}: {}", localised_text.error_slash_command, why); - } -} diff --git a/src/cmd/anilist_module/waifu.rs b/src/cmd/anilist_module/waifu.rs deleted file mode 100644 index 5a0d63e9..00000000 --- a/src/cmd/anilist_module/waifu.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::structure::anilist::character::struct_character::CharacterWrapper; -use crate::structure::embed::anilist::struct_lang_character::CharacterLocalisedText; -use crate::structure::register::anilist::struct_waifu_register::RegisterLocalisedWaifu; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::command::CommandOptionType; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::model::prelude::InteractionResponseType; -use serenity::model::Timestamp; - -pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match CharacterLocalisedText::get_character_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let data = match CharacterWrapper::new_character_by_id(156323, localised_text.clone()).await { - Ok(character_wrapper) => character_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - }; - - let name = data.get_name(); - let url = data.get_url(); - let desc = data.get_desc(localised_text.clone()); - let image = data.get_image(); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(name) - .url(url) - .timestamp(Timestamp::now()) - .color(COLOR) - .description(desc) - .thumbnail(image) - }) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let waifus = RegisterLocalisedWaifu::get_waifu_register_localised().unwrap(); - let command = command - .name("waifu") - .description("Give you the best waifu.") - .create_option(|option| { - option - .name("username") - .description("Username of the discord user you want the waifu of") - .kind(CommandOptionType::User) - .required(false); - for waifu in waifus.values() { - option - .name_localized(&waifu.code, &waifu.name) - .description_localized(&waifu.code, &waifu.desc); - } - option - }); - for waifu in waifus.values() { - command - .name_localized(&waifu.code, &waifu.name) - .description_localized(&waifu.code, &waifu.desc); - } - command -} diff --git a/src/cmd/general_module/avatar.rs b/src/cmd/general_module/avatar.rs deleted file mode 100644 index 6d768acb..00000000 --- a/src/cmd/general_module/avatar.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::function::error_management::error_avatar::error_no_avatar; -use crate::structure::embed::general::struct_lang_avatar::AvatarLocalisedText; -use crate::structure::register::general::struct_avatar_register::RegisterLocalisedAvatar; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::http::Http; -use serenity::model::application::command::CommandOptionType; -use serenity::model::prelude::application_command::{ - ApplicationCommandInteraction, CommandDataOption, CommandDataOptionValue, -}; -use serenity::model::prelude::{InteractionResponseType, User}; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - if let Some(option) = options.get(0) { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::User(user, ..) = resolved { - avatar_with_user(ctx, command, user).await - } else { - avatar_without_user(ctx, command).await - } - } else { - avatar_without_user(ctx, command).await - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let profiles = RegisterLocalisedAvatar::get_avatar_register_localised().unwrap(); - let command = command - .name("avatar") - .description("Show the avatar of a user") - .create_option(|option| { - let option = option - .name("user") - .description("The user you wan the avatar of") - .kind(CommandOptionType::User) - .required(false); - for profile in profiles.values() { - option - .name_localized(&profile.code, &profile.option1) - .description_localized(&profile.code, &profile.option1_desc); - } - option - }); - for profile in profiles.values() { - command - .name_localized(&profile.code, &profile.name) - .description_localized(&profile.code, &profile.description); - } - command -} - -async fn avatar_without_user(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match AvatarLocalisedText::get_avatar_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let user = command.user.id.0; - let real_user = Http::get_user(&ctx.http, user).await; - let result = if let Ok(user) = real_user { - user - } else { - custom_error(ctx, command, &localised_text.error_no_user).await; - return; - }; - - avatar_with_user(ctx, command, &result).await -} - -async fn avatar_with_user(ctx: &Context, command: &ApplicationCommandInteraction, user: &User) { - let localised_text = match AvatarLocalisedText::get_avatar_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - - let avatar_url = match user.avatar_url() { - Some(url) => url, - None => { - error_no_avatar(ctx, command).await; - return; - } - }; - - send_embed( - avatar_url, - ctx, - command, - localised_text.clone(), - user.name.clone(), - ) - .await -} - -pub async fn send_embed( - avatar_url: String, - ctx: &Context, - command: &ApplicationCommandInteraction, - localised_text: AvatarLocalisedText, - username: String, -) { - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(format!("{}{}", &localised_text.title, username)) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .image(avatar_url) - }) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } -} diff --git a/src/cmd/general_module/banner.rs b/src/cmd/general_module/banner.rs deleted file mode 100644 index 2cffa4eb..00000000 --- a/src/cmd/general_module/banner.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::structure::embed::general::struct_lang_banner::BannerLocalisedText; -use crate::structure::register::general::struct_banner_register::RegisterLocalisedBanner; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::http::client::Http; -use serenity::model::application::command::CommandOptionType; -use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::application_command::{CommandDataOption, CommandDataOptionValue}; -use serenity::model::user::User; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - return if let Some(option) = options.get(0) { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::User(user, ..) = resolved { - banner_with_user(ctx, command, user).await; - } else { - banner_without_user(ctx, command).await; - } - } else { - banner_without_user(ctx, command).await; - }; -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let banners = RegisterLocalisedBanner::get_banner_register_localised().unwrap(); - let command = command - .name("banner") - .description("Get the banner") - .create_option(|option| { - let option = option - .name("user") - .description("The user you wan the banner of") - .kind(CommandOptionType::User) - .required(false); - for banner in banners.values() { - option - .name_localized(&banner.code, &banner.option1) - .description_localized(&banner.code, &banner.option1_desc); - } - option - }); - for banner in banners.values() { - command - .name_localized(&banner.code, &banner.name) - .description_localized(&banner.code, &banner.description); - } - command -} - -pub async fn no_banner(ctx: &Context, command: &ApplicationCommandInteraction, username: String) { - let localised_text = match BannerLocalisedText::get_banner_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(format!("{} {}", &localised_text.title, username)) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .description(&localised_text.description) - }) - }) - }) - .await - { - println!("{}: {}", &localised_text.error_slash_command, why); - } -} - -pub async fn banner_without_user(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match BannerLocalisedText::get_banner_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let user = command.user.id.0; - let real_user = Http::get_user(&ctx.http, user).await; - let result = if let Ok(user) = real_user { - user - } else { - custom_error(ctx, command, &localised_text.error_no_user).await; - return; - }; - let banner_url = &result.banner_url(); - let banner = if let Some(string) = banner_url { - string - } else { - no_banner(ctx, command, command.user.name.clone()).await; - return; - }; - - send_embed(ctx, command, localised_text.clone(), banner, result).await; -} - -pub async fn banner_with_user( - ctx: &Context, - command: &ApplicationCommandInteraction, - user_data: &User, -) { - let localised_text = match BannerLocalisedText::get_banner_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let user = user_data.id.0; - let real_user = Http::get_user(&ctx.http, user).await; - let result = if let Ok(user) = real_user { - user - } else { - custom_error(ctx, command, &localised_text.error_no_user).await; - return; - }; - let banner_url = &result.banner_url(); - let banner = if let Some(string) = banner_url { - string - } else { - no_banner(ctx, command, user_data.name.clone()).await; - return; - }; - - send_embed(ctx, command, localised_text.clone(), banner, result).await; -} - -pub async fn send_embed( - ctx: &Context, - command: &ApplicationCommandInteraction, - localised_text: BannerLocalisedText, - banner: &String, - result: User, -) { - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(format!("{}{}", &localised_text.title, result.name)) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .image(banner) - }) - }) - }) - .await - { - println!("{}: {}", &localised_text.error_slash_command, why); - } -} diff --git a/src/cmd/general_module/credit.rs b/src/cmd/general_module/credit.rs deleted file mode 100644 index 2d113c45..00000000 --- a/src/cmd/general_module/credit.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::constant::COLOR; -use crate::structure::embed::general::struct_lang_credit::CreditLocalisedText; -use crate::structure::register::general::struct_credit_register::RegisterLocalisedCredit; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::Timestamp; - -pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { - let credit_localised = match CreditLocalisedText::get_credit_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let mut desc: String = "".to_string(); - for x in credit_localised.list { - desc += x.text.as_str() - } - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(&credit_localised.title) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .description(desc) - }) - }) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let credits = RegisterLocalisedCredit::get_credit_register_localised().unwrap(); - let command = command.name("credit").description("List of credit"); - for credit in credits.values() { - command - .name_localized(&credit.code, &credit.name) - .description_localized(&credit.code, &credit.desc); - } - command -} diff --git a/src/cmd/general_module/info.rs b/src/cmd/general_module/info.rs deleted file mode 100644 index 3afa4ffe..00000000 --- a/src/cmd/general_module/info.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::constant::COLOR; -use crate::structure::embed::general::struct_lang_info::InfoLocalisedText; -use crate::structure::register::general::struct_info_register::RegisterLocalisedInfo; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::component::ButtonStyle; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::interaction::application_command::ApplicationCommandInteraction; -use serenity::model::Timestamp; - -pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match InfoLocalisedText::get_info_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| message.embed( - |m| { - m.title(&localised_text.title) - .description(&localised_text.description) - .footer(|f| f.text(&localised_text.footer)) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - .components(|components| { - components.create_action_row(|row| { - row.create_button(|button| { - button.label(&localised_text.button_see_on_github) - .url("https://github.com/ValgulNecron/DIscordAnilistBotRS") - .style(ButtonStyle::Link) - }) - .create_button(|button| { - button.label(&localised_text.button_official_website) - .url("https://kasuki.valgul.moe/") - .style(ButtonStyle::Link) - }) - }) - .create_action_row(|button| { - button.create_button(|button| { - button.label(&localised_text.button_official_discord) - .url("https://discord.gg/dWGU6mkw7J") - .style(ButtonStyle::Link) - }) - .create_button(|button| { - button.label(&localised_text.button_add_the_bot) - .url("https://discord.com/api/oauth2/authorize?client_id=923286536445894697&permissions=533113194560&scope=bot") - .style(ButtonStyle::Link) - }) - }) - }) - ) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let infos = RegisterLocalisedInfo::get_info_register_localised().unwrap(); - let command = command - .name("info") - .description("Get information on the bot"); - for info in infos.values() { - command - .name_localized(&info.code, &info.name) - .description_localized(&info.code, &info.desc); - } - command -} diff --git a/src/cmd/general_module/lang.rs b/src/cmd/general_module/lang.rs deleted file mode 100644 index 596ecf4a..00000000 --- a/src/cmd/general_module/lang.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::available_lang::AvailableLang; -use crate::function::error_management::no_lang_error::error_no_langage_guild_id; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::structure::embed::general::struct_lang_lang::LangLocalisedText; -use crate::structure::register::general::struct_lang_register::LangRegister; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::command::CommandOptionType; -use serenity::model::application::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, CommandDataOptionValue, -}; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::{Permissions, Timestamp}; -use serenity::utils::Colour; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS guild_lang ( - guild TEXT PRIMARY KEY, - lang TEXT NOT NULL - )", - ) - .execute(&pool) - .await - .unwrap(); - - let option = options - .get(0) - .expect("Expected lang option") - .resolved - .as_ref() - .expect("Expected lang object"); - let color = Colour::FABLED_PINK; - - if let CommandDataOptionValue::String(lang) = option { - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return; - } - }; - let localised_text = match LangLocalisedText::get_ping_localised(color, ctx, command).await - { - Ok(data) => data, - Err(_) => return, - }; - sqlx::query("INSERT OR REPLACE INTO guild_lang (guild, lang) VALUES (?, ?)") - .bind(guild_id) - .bind(lang) - .execute(&pool) - .await - .unwrap(); - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(&localised_text.title) - .description(format!("{}{}", &localised_text.description, lang)) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(color) - }) - }) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let available_langages = AvailableLang::get_available_lang().unwrap(); - let langages = LangRegister::get_profile_register_localised().unwrap(); - command - .name("lang") - .description("Change the lang of the bot response") - .default_member_permissions(Permissions::ADMINISTRATOR) - .create_option(|option| { - option - .name("lang") - .description("The lang you want to set the response to.") - .kind(CommandOptionType::String) - .required(true); - for langages in available_langages.values() { - option.add_string_choice(&langages.lang, &langages.lang); - } - for lang in langages.values() { - option - .name_localized(&lang.code, &lang.option1) - .description_localized(&lang.code, &lang.option1_desc); - } - option - }); - for lang in langages.values() { - command - .name_localized(&lang.code, &lang.option1) - .description_localized(&lang.code, &lang.option1_desc); - } - command -} diff --git a/src/cmd/general_module/lang_struct.rs b/src/cmd/general_module/lang_struct.rs deleted file mode 100644 index 942fa635..00000000 --- a/src/cmd/general_module/lang_struct.rs +++ /dev/null @@ -1,60 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RandomLocalisedText { - pub error_title: String, - pub error_message: String, - pub genre: String, - pub tag: String, - pub format: String, - pub desc: String, - pub error_slash_command: String, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct CompareLocalisedText { - pub more_anime: String, - pub connector_user_same_anime: String, - pub same_anime: String, - pub time_anime_watch: String, - pub connector_user_same_time: String, - pub same_time: String, - pub more_manga: String, - pub connector_user_same_manga: String, - pub same_manga: String, - pub more_chapter: String, - pub connector_user_same_chapter: String, - pub same_chapter: String, - pub genre_same_connector_anime: String, - pub genre_same_prefer_anime: String, - pub diff_pref_genre_1_anime: String, - pub diff_pref_genre_while_anime: String, - pub diff_pref_genre_2_anime: String, - pub same_tag_connector_anime: String, - pub same_tag_prefer_anime: String, - pub diff_pref_tag_1_anime: String, - pub diff_pref_tag_while_anime: String, - pub diff_pref_tag_2_anime: String, - pub diff_pref_tag_anime: String, - pub genre_same_connector_manga: String, - pub genre_same_prefer_manga: String, - pub diff_pref_genre_1_manga: String, - pub diff_pref_genre_while_manga: String, - pub diff_pref_genre_2_manga: String, - pub diff_pref_genre_manga: String, - pub same_tag_connector_manga: String, - pub same_tag_prefer_manga: String, - pub diff_pref_tag_1_manga: String, - pub diff_pref_tag_while_manga: String, - pub diff_pref_tag_2_manga: String, - pub title: String, - pub sub_title_anime: String, - pub watch_time: String, - pub pref_genre_anime: String, - pub pref_tag_anime: String, - pub sub_title_manga: String, - pub chapter_read: String, - pub pref_genre_manga: String, - pub pref_tag_manga: String, - pub error_slash_command: String, -} diff --git a/src/cmd/general_module/module_activation.rs b/src/cmd/general_module/module_activation.rs deleted file mode 100644 index 138e641c..00000000 --- a/src/cmd/general_module/module_activation.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::error_module::error_no_module; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::structure::embed::general::struct_lang_module_activation::ModuleLocalisedText; -use crate::structure::register::general::struct_modules_register::RegisterLocalisedModule; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::prelude::application_command::{CommandDataOption, CommandDataOptionValue}; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::ApplicationCommandInteraction; -use serenity::model::prelude::InteractionResponseType; -use serenity::model::{Permissions, Timestamp}; -use sqlx::{Pool, Sqlite}; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS module_activation ( - guild_id TEXT PRIMARY KEY, - ai_module INTEGER, - anilist_module INTEGER - )", - ) - .execute(&pool) - .await - .unwrap(); - - let localised_text = match ModuleLocalisedText::get_module_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let mut module = "".to_string(); - let mut state = false; - for option in options { - if option.name == "module_name" { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::String(module_option) = resolved { - module = module_option.clone() - } else { - module = "".to_string(); - } - } - if option.name == "state" { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::Boolean(state_option) = resolved { - state = *state_option - } else { - state = false - } - } - } - - let guild_id = command.guild_id.unwrap().0.to_string().clone(); - - match module.as_str() { - "ANIME" => { - let row = make_sql_request(guild_id.clone(), &pool).await; - let (_, ai_module, _): (Option<String>, Option<bool>, Option<bool>) = row; - - let ai_value = match ai_module { - Some(true) => 1, - Some(false) => 0, - None => 1, - }; - sqlx::query( - "INSERT OR REPLACE INTO module_activation (guild_id, anilist_module, ai_module) VALUES (?, ?, ?)", - ) - .bind(&guild_id) - .bind(state) - .bind(ai_value) - .execute(&pool) - .await - .unwrap(); - - let text = if state { - &localised_text.on - } else { - &localised_text.off - }; - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title("ANILIST") - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .description(text) - }) - }) - }) - .await - { - println!("Error creating slash: {}", why); - } - } - "AI" => { - let row = make_sql_request(guild_id.clone(), &pool).await; - let (_, _, anilist_module): (Option<String>, Option<bool>, Option<bool>) = row; - - let anilist_value = match anilist_module { - Some(true) => 1, - Some(false) => 0, - None => 1, - }; - - sqlx::query( - "INSERT OR REPLACE INTO module_activation (guild_id, ai_module, anilist_module) VALUES (?, ?, ?)", - ) - .bind(&guild_id) - .bind(state) - .bind(anilist_value) - .execute(&pool) - .await - .unwrap(); - - let text = if state { - &localised_text.on - } else { - &localised_text.off - }; - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title("AI") - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .description(text) - }) - }) - }) - .await - { - println!("Error creating slash: {}", why); - } - } - _ => error_no_module(ctx, command).await, - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let modules = RegisterLocalisedModule::get_module_register_localised().unwrap(); - let command = command - .name("module") - .description("Turn on and off module.") - .default_member_permissions(Permissions::ADMINISTRATOR) - .create_option(|option| { - let option = option - .name("module_name") - .description("The name of the module you want to turn on or off") - .kind(CommandOptionType::String) - .add_string_choice("AI", "AI") - .add_string_choice("ANIME", "ANIME") - .required(true); - for module in modules.values() { - option - .name_localized(&module.code, &module.option1) - .description_localized(&module.code, &module.option1_desc); - } - option - }) - .create_option(|option| { - let option = option - .name("state") - .description("ON or OFF") - .kind(CommandOptionType::Boolean) - .required(true); - for module in modules.values() { - option - .name_localized(&module.code, &module.option2) - .description_localized(&module.code, &module.option2_desc); - } - option - }); - for module in modules.values() { - command - .name_localized(&module.code, &module.name) - .description_localized(&module.code, &module.desc); - } - command -} - -pub async fn check_activation_status(module: &str, guild_id: String) -> bool { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - - let row: (Option<String>, Option<bool>, Option<bool>) = sqlx::query_as( - "SELECT guild_id, ai_module, anilist_module FROM module_activation WHERE guild_id = ?", - ) - .bind(&guild_id) - .fetch_one(&pool) - .await - .unwrap_or((None, None, None)); - - let (_, ai_module, anilist_module): (Option<String>, Option<bool>, Option<bool>) = row; - match module { - "ANILIST" => anilist_module.unwrap_or(true), - "AI" => ai_module.unwrap_or(true), - _ => false, - } -} - -pub async fn make_sql_request( - guild_id: String, - pool: &Pool<Sqlite>, -) -> (Option<String>, Option<bool>, Option<bool>) { - let row: (Option<String>, Option<bool>, Option<bool>) = sqlx::query_as( - "SELECT guild_id, ai_module, anilist_module FROM module_activation WHERE guild = ?", - ) - .bind(&guild_id) - .fetch_one(pool) - .await - .unwrap_or((None, None, None)); - row -} diff --git a/src/cmd/general_module/ping.rs b/src/cmd/general_module/ping.rs deleted file mode 100644 index 714f9d58..00000000 --- a/src/cmd/general_module/ping.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::constant::COLOR; -use crate::structure::embed::general::struct_lang_ping::PingLocalisedText; -use crate::structure::register::general::struct_ping_register::RegisterLocalisedPing; -use serenity::builder::CreateApplicationCommand; -use serenity::client::bridge::gateway::ShardId; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::Timestamp; - -use crate::structure::struct_shard_manager::ShardManagerContainer; - -pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { - let data_read = ctx.data.read().await; - let shard_manager = match data_read.get::<ShardManagerContainer>() { - Some(data) => data, - None => return, - }; - - let manager = shard_manager.lock().await; - let runners = manager.runners.lock().await; - - let runner = match runners.get(&ShardId(ctx.shard_id)) { - Some(data) => data, - None => return, - }; - - let latency = match runner.latency { - Some(duration) => format!("{:.2}ms", duration.as_millis()), - None => "?ms".to_string(), - }; - - let localised_text = match PingLocalisedText::get_ping_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(&localised_text.title) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .description(format!( - "{}{}{}{}{}", - &localised_text.description_part_1, - &localised_text.description_part_2, - ctx.shard_id, - &localised_text.description_part_3, - latency - )) - }) - }) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let pings = RegisterLocalisedPing::get_ping_register_localised().unwrap(); - let command = command.name("ping").description("A ping command"); - for ping in pings.values() { - command - .name_localized(&ping.code, &ping.name) - .description_localized(&ping.code, &ping.desc); - } - command -} diff --git a/src/cmd/general_module/profile.rs b/src/cmd/general_module/profile.rs deleted file mode 100644 index 7be4577c..00000000 --- a/src/cmd/general_module/profile.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::constant::COLOR; -use crate::structure::embed::general::struct_lang_profile::ProfileLocalisedText; -use crate::structure::register::general::struct_profile_register::RegisterLocalisedProfile; -use serenity::builder::CreateApplicationCommand; -use serenity::client::Context; -use serenity::model::application::command::CommandOptionType; -use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::application_command::{CommandDataOption, CommandDataOptionValue}; -use serenity::model::user::User; -use serenity::model::Timestamp; - -pub async fn run( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - if let Some(option) = options.get(0) { - let resolved = option.resolved.as_ref().unwrap(); - if let CommandDataOptionValue::User(user, ..) = resolved { - send_embed(ctx, command, user.clone()).await; - return; - } - } - let user = &command.user; - send_embed(ctx, command, user.clone()).await -} - -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - let profiles = RegisterLocalisedProfile::get_profile_register_localised().unwrap(); - let command = command - .name("profile") - .description("Show the profile of a user") - .create_option(|option| { - let option = option - .name("user") - .description("The user you wan the profile of") - .kind(CommandOptionType::User) - .required(false); - for profile in profiles.values() { - option - .name_localized(&profile.code, &profile.option1) - .description_localized(&profile.code, &profile.option1_desc); - } - option - }); - for profile in profiles.values() { - command - .name_localized(&profile.code, &profile.name) - .description_localized(&profile.code, &profile.desc); - } - command -} - -async fn description( - user: User, - command: &ApplicationCommandInteraction, - localised_text: ProfileLocalisedText, -) -> String { - let is_bot = &user.bot; - let public_flag = &user.public_flags.unwrap(); - let user_id = &user.id; - let created_at = &user.created_at(); - let member = &command.member.clone().unwrap(); - let joined_at = member - .joined_at - .unwrap_or(Timestamp::from_unix_timestamp(0i64).unwrap()); - format!( - "\n {}{} \n {}{} \n {}{:?} \n {}{} \n {}{}", - &localised_text.user_id, - user_id, - &localised_text.is_bot, - is_bot, - &localised_text.public_flag, - public_flag, - &localised_text.created_at, - created_at, - &localised_text.joined_at, - joined_at - ) -} - -async fn send_embed(ctx: &Context, command: &ApplicationCommandInteraction, user: User) { - let localised_text = match ProfileLocalisedText::get_profile_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let avatar_url = match user.avatar_url() { - Some(a) => a, - None => "exemple.com".to_string(), - }; - let desc = description(user.clone(), command, localised_text.clone()).await; - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(format!("{}{}", &localised_text.title, user.name)) - // Add a timestamp for the current time - // This also accepts a rfc3339 Timestamp - .timestamp(Timestamp::now()) - .color(COLOR) - .thumbnail(avatar_url) - .description(desc) - }) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } -} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs deleted file mode 100644 index 51ff6724..00000000 --- a/src/cmd/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod ai_module; -pub mod anilist_module; -//pub mod games_module; -pub mod general_module; -//pub mod moderation_module; diff --git a/src/cmd/moderation_module/mod.rs b/src/cmd/moderation_module/mod.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/cmd/moderation_module/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/command_autocomplete/anilist/anime.rs b/src/command_autocomplete/anilist/anime.rs new file mode 100644 index 00000000..4f6dffe4 --- /dev/null +++ b/src/command_autocomplete/anilist/anime.rs @@ -0,0 +1,14 @@ +use serenity::all::{CommandInteraction, Context}; + +use crate::anilist_struct::autocomplete::media::{send_auto_complete, MediaPageWrapper}; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search = String::new(); + for option in &command.data.options { + if option.name.as_str() != "type" { + search = option.value.as_str().unwrap().to_string() + } + } + let anime = MediaPageWrapper::new_autocomplete_anime(&search.to_string()).await; + send_auto_complete(ctx, command, anime).await; +} diff --git a/src/command_autocomplete/anilist/character.rs b/src/command_autocomplete/anilist/character.rs new file mode 100644 index 00000000..0aa3b425 --- /dev/null +++ b/src/command_autocomplete/anilist/character.rs @@ -0,0 +1,32 @@ +use serenity::all::{ + AutocompleteChoice, CommandInteraction, Context, CreateAutocompleteResponse, + CreateInteractionResponse, +}; + +use crate::anilist_struct::autocomplete::character::CharacterPageWrapper; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search = String::new(); + for option in &command.data.options { + if option.name.as_str() != "type" { + search = option.value.as_str().unwrap().to_string() + } + } + let data = CharacterPageWrapper::new_autocomplete_character(&search.to_string()).await; + let mut choices = Vec::new(); + let character = data.data.page.characters.unwrap().clone(); + + for user in character { + let data = user.unwrap(); + let name = data.name.unwrap(); + let full = name.full.clone(); + let user_pref = name.user_preferred.clone(); + let name = user_pref.unwrap_or(full); + choices.push(AutocompleteChoice::new(name, data.id.to_string())) + } + + let data = CreateAutocompleteResponse::new().set_choices(choices); + let builder = CreateInteractionResponse::Autocomplete(data); + + let _ = command.create_response(ctx.http, builder).await; +} diff --git a/src/command_autocomplete/anilist/compare.rs b/src/command_autocomplete/anilist/compare.rs new file mode 100644 index 00000000..2179f2a1 --- /dev/null +++ b/src/command_autocomplete/anilist/compare.rs @@ -0,0 +1,31 @@ +use serenity::all::{ + AutocompleteChoice, CommandInteraction, Context, CreateAutocompleteResponse, + CreateInteractionResponse, +}; +use tracing::log::trace; + +use crate::anilist_struct::autocomplete::user::UserPageWrapper; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut choice = Vec::new(); + trace!("{:?}", &command.data.options); + for option in &command.data.options { + let search = option.value.as_str().unwrap(); + trace!("{:?}", search); + let data = UserPageWrapper::new_autocomplete_user(&search.to_string()).await; + let mut choices = Vec::new(); + let users = data.data.page.users.unwrap().clone(); + + for user in users { + let data = user.unwrap(); + let user = data.name; + choices.push(AutocompleteChoice::new(user, data.id.to_string())) + } + choice.extend(choices); + } + + let data = CreateAutocompleteResponse::new().set_choices(choice); + let builder = CreateInteractionResponse::Autocomplete(data); + + let _ = command.create_response(ctx.http, builder).await; +} diff --git a/src/command_autocomplete/anilist/ln.rs b/src/command_autocomplete/anilist/ln.rs new file mode 100644 index 00000000..b7863f5e --- /dev/null +++ b/src/command_autocomplete/anilist/ln.rs @@ -0,0 +1,14 @@ +use serenity::all::{CommandInteraction, Context}; + +use crate::anilist_struct::autocomplete::media::{send_auto_complete, MediaPageWrapper}; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search = String::new(); + for option in &command.data.options { + if option.name.as_str() != "type" { + search = option.value.as_str().unwrap().to_string() + } + } + let manga = MediaPageWrapper::new_autocomplete_ln(&search.to_string()).await; + send_auto_complete(ctx, command, manga).await; +} diff --git a/src/command_autocomplete/anilist/manga.rs b/src/command_autocomplete/anilist/manga.rs new file mode 100644 index 00000000..ea3f16dc --- /dev/null +++ b/src/command_autocomplete/anilist/manga.rs @@ -0,0 +1,14 @@ +use serenity::all::{CommandInteraction, Context}; + +use crate::anilist_struct::autocomplete::media::{send_auto_complete, MediaPageWrapper}; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search = String::new(); + for option in &command.data.options { + if option.name.as_str() != "type" { + search = option.value.as_str().unwrap().to_string() + } + } + let manga = MediaPageWrapper::new_autocomplete_manga(&search.to_string()).await; + send_auto_complete(ctx, command, manga).await; +} diff --git a/src/command_autocomplete/anilist/mod.rs b/src/command_autocomplete/anilist/mod.rs new file mode 100644 index 00000000..f3b2ae7e --- /dev/null +++ b/src/command_autocomplete/anilist/mod.rs @@ -0,0 +1,9 @@ +pub mod anime; +pub mod character; +pub mod compare; +pub mod ln; +pub mod manga; +pub mod search; +pub mod staff; +pub mod studio; +pub mod user; diff --git a/src/command_autocomplete/anilist/search.rs b/src/command_autocomplete/anilist/search.rs new file mode 100644 index 00000000..8ee406d7 --- /dev/null +++ b/src/command_autocomplete/anilist/search.rs @@ -0,0 +1,22 @@ +use serenity::all::{CommandInteraction, Context}; + +use crate::command_autocomplete::anilist::{anime, character, ln, manga, staff, studio, user}; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search_type = String::new(); + for option in &command.data.options { + if option.name.as_str() == "type" { + search_type = option.value.as_str().unwrap().to_string() + } + } + match search_type.as_str() { + "anime" => anime::autocomplete(ctx, command).await, + "ln" => ln::autocomplete(ctx, command).await, + "manga" => manga::autocomplete(ctx, command).await, + "user" => user::autocomplete(ctx, command).await, + "character" => character::autocomplete(ctx, command).await, + "staff" => staff::autocomplete(ctx, command).await, + "studio" => studio::autocomplete(ctx, command).await, + _ => {} + } +} diff --git a/src/command_autocomplete/anilist/staff.rs b/src/command_autocomplete/anilist/staff.rs new file mode 100644 index 00000000..2f3a6908 --- /dev/null +++ b/src/command_autocomplete/anilist/staff.rs @@ -0,0 +1,33 @@ +use serenity::all::{ + AutocompleteChoice, CommandInteraction, Context, CreateAutocompleteResponse, + CreateInteractionResponse, +}; + +use crate::anilist_struct::autocomplete::staff::StaffPageWrapper; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search = String::new(); + for option in &command.data.options { + if option.name.as_str() != "type" { + search = option.value.as_str().unwrap().to_string() + } + } + let data = StaffPageWrapper::new_autocomplete_staff(&search.to_string()).await; + let mut choices = Vec::new(); + let staffs = data.data.page.staff.clone().unwrap(); + + for staff in staffs { + let data = staff.unwrap(); + let user = data + .name + .user_preferred + .clone() + .unwrap_or(data.name.full.clone()); + choices.push(AutocompleteChoice::new(user, data.id.to_string())) + } + + let data = CreateAutocompleteResponse::new().set_choices(choices); + let builder = CreateInteractionResponse::Autocomplete(data); + + let _ = command.create_response(ctx.http, builder).await; +} diff --git a/src/command_autocomplete/anilist/studio.rs b/src/command_autocomplete/anilist/studio.rs new file mode 100644 index 00000000..cde2047f --- /dev/null +++ b/src/command_autocomplete/anilist/studio.rs @@ -0,0 +1,30 @@ +use serenity::all::{ + AutocompleteChoice, CommandInteraction, Context, CreateAutocompleteResponse, + CreateInteractionResponse, +}; + +use crate::anilist_struct::autocomplete::studio::StudioPageWrapper; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search = String::new(); + for option in &command.data.options { + if option.name.as_str() != "type" { + search = option.value.as_str().unwrap().to_string() + } + } + let data = StudioPageWrapper::new_autocomplete_staff(&search.to_string()).await; + let studios = data.data.page.studios.clone().unwrap(); + + let mut choices = Vec::new(); + + for studio in studios { + let data = studio.unwrap(); + let user = data.name; + choices.push(AutocompleteChoice::new(user, data.id.to_string())) + } + + let data = CreateAutocompleteResponse::new().set_choices(choices); + let builder = CreateInteractionResponse::Autocomplete(data); + + let _ = command.create_response(ctx.http, builder).await; +} diff --git a/src/command_autocomplete/anilist/user.rs b/src/command_autocomplete/anilist/user.rs new file mode 100644 index 00000000..7ced1c12 --- /dev/null +++ b/src/command_autocomplete/anilist/user.rs @@ -0,0 +1,29 @@ +use serenity::all::{ + AutocompleteChoice, CommandInteraction, Context, CreateAutocompleteResponse, + CreateInteractionResponse, +}; + +use crate::anilist_struct::autocomplete::user::UserPageWrapper; + +pub async fn autocomplete(ctx: Context, command: CommandInteraction) { + let mut search = String::new(); + for option in &command.data.options { + if option.name.as_str() != "type" { + search = option.value.as_str().unwrap().to_string() + } + } + let data = UserPageWrapper::new_autocomplete_user(&search.to_string()).await; + let mut choices = Vec::new(); + let users = data.data.page.users.unwrap().clone(); + + for user in users { + let data = user.unwrap(); + let user = data.name; + choices.push(AutocompleteChoice::new(user, data.id.to_string())) + } + + let data = CreateAutocompleteResponse::new().set_choices(choices); + let builder = CreateInteractionResponse::Autocomplete(data); + + let _ = command.create_response(ctx.http, builder).await; +} diff --git a/src/command_autocomplete/autocomplete_dispatch.rs b/src/command_autocomplete/autocomplete_dispatch.rs new file mode 100644 index 00000000..ec924c6e --- /dev/null +++ b/src/command_autocomplete/autocomplete_dispatch.rs @@ -0,0 +1,23 @@ +use serenity::all::{CommandInteraction, Context}; + +use crate::command_autocomplete::anilist::{ + anime, character, compare, ln, manga, search, staff, studio, user, +}; + +pub async fn autocomplete_dispatching(ctx: Context, command: CommandInteraction) { + match command.data.name.as_str() { + "anime" => anime::autocomplete(ctx, command).await, + "add_activity" => anime::autocomplete(ctx, command).await, + "ln" => ln::autocomplete(ctx, command).await, + "manga" => manga::autocomplete(ctx, command).await, + "user" => user::autocomplete(ctx, command).await, + "character" => character::autocomplete(ctx, command).await, + "compare" => compare::autocomplete(ctx, command).await, + "register" => user::autocomplete(ctx, command).await, + "staff" => staff::autocomplete(ctx, command).await, + "studio" => studio::autocomplete(ctx, command).await, + "search" => search::autocomplete(ctx, command).await, + "seiyuu" => staff::autocomplete(ctx, command).await, + _ => {} + } +} diff --git a/src/command_autocomplete/mod.rs b/src/command_autocomplete/mod.rs new file mode 100644 index 00000000..836e01d6 --- /dev/null +++ b/src/command_autocomplete/mod.rs @@ -0,0 +1,2 @@ +pub mod anilist; +pub mod autocomplete_dispatch; diff --git a/src/command_register/command_registration.rs b/src/command_register/command_registration.rs new file mode 100644 index 00000000..ba0be155 --- /dev/null +++ b/src/command_register/command_registration.rs @@ -0,0 +1,122 @@ +use std::env; +use std::sync::Arc; + +use serenity::all::{Command, CreateCommand, CreateCommandOption, Http, Permissions}; +use tracing::{error, trace}; + +use crate::command_register::command_structure::{get_commands, CommandData}; + +pub async fn creates_commands(http: &Arc<Http>, is_ok: bool) { + if is_ok { + delete_command(http).await; + } + let commands = match get_commands("./json/command") { + Err(e) => { + error!("{:?}", e); + return; + } + Ok(c) => c, + }; + for command in commands { + create_command(&command, http).await; + } +} + +async fn create_command(command: &CommandData, http: &Arc<Http>) { + let mut permission = Permissions::SEND_MESSAGES; + if command.perm { + let mut perm_bit: u64 = 0; + let perm_list = command.default_permissions.clone().unwrap(); + for permi in perm_list { + let permission: Permissions = permi.permission.into(); + perm_bit += permission.bits() + } + permission = Permissions::from_bits(perm_bit).unwrap() + } + + let mut nsfw = command.nsfw.clone(); + + if command.name.as_str() == "image" { + let honor_nsfw = env::var("IMAGE_GENERATION_MODELS_ON").unwrap_or(String::from("fale")); + let is_ok = honor_nsfw.to_lowercase() == "true"; + nsfw = is_ok + } + + let mut build = CreateCommand::new(&command.name) + .description(&command.desc) + .dm_permission(command.dm_command) + .nsfw(nsfw) + .default_member_permissions(permission); + match &command.localised { + Some(localiseds) => { + for localised in localiseds { + build = build + .name_localized(&localised.code, &localised.name) + .description_localized(&localised.code, &localised.desc) + } + } + None => {} + } + + if command.arg_num > (0u32) { + let options = create_option(command).await; + for option in options { + build = build.add_option(option); + } + } + trace!("{:?}", build); + match Command::create_global_command(http, build).await { + Ok(res) => res, + Err(e) => { + error!("{} for command {}", e, command.name); + return; + } + }; +} + +async fn create_option(command: &CommandData) -> Vec<CreateCommandOption> { + let mut options_builds = Vec::new(); + for option in command.args.as_ref().unwrap() { + let command_type = option.command_type.clone().into(); + let mut options_build = CreateCommandOption::new(command_type, &option.name, &option.desc) + .required(option.required); + match &option.choices { + Some(choices) => { + for choice in choices { + options_build = options_build + .add_string_choice(&choice.option_choice, &choice.option_choice); + } + } + None => {} + } + match &option.localised_args { + Some(localiseds) => { + for localised in localiseds { + options_build = options_build + .name_localized(&localised.code, &localised.name) + .description_localized(&localised.code, &localised.desc) + } + } + None => {} + } + options_build = options_build.set_autocomplete(option.autocomplete); + + options_builds.push(options_build) + } + + options_builds +} + +async fn delete_command(http: &Arc<Http>) { + let cmds = Command::get_global_commands(http).await.unwrap(); + for cmd in cmds { + trace!("Removing {:?}", cmd.name); + match Command::delete_global_command(http, cmd.id).await { + Ok(res) => res, + Err(e) => { + error!("{} for command {}", e, cmd.name); + return; + } + }; + } +} diff --git a/src/command_register/command_structure.rs b/src/command_register/command_structure.rs new file mode 100644 index 00000000..7d73acca --- /dev/null +++ b/src/command_register/command_structure.rs @@ -0,0 +1,240 @@ +use std::fs; +use std::io::Error; + +use serde::Deserialize; +use serde::Serialize; +use serde_json; +use serenity::all::{CommandOptionType, Permissions}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Arg { + pub name: String, + pub desc: String, + pub required: bool, + pub autocomplete: bool, + #[serde(with = "RemoteCommandOptionType")] + pub command_type: RemoteCommandOptionType, + pub choices: Option<Vec<ArgChoice>>, + pub localised_args: Option<Vec<LocalisedArg>>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ArgChoice { + pub option_choice: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct LocalisedArg { + pub code: String, + pub name: String, + pub desc: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Localised { + pub code: String, + pub name: String, + pub desc: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CommandData { + pub name: String, + pub desc: String, + pub arg_num: u32, + pub args: Option<Vec<Arg>>, + pub perm: bool, + pub default_permissions: Option<Vec<DefaultPermission>>, + pub localised: Option<Vec<Localised>>, + pub dm_command: bool, + pub nsfw: bool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DefaultPermission { + #[serde(with = "RemotePermissionType")] + pub permission: RemotePermissionType, +} + +pub fn get_commands(directory: &str) -> Result<Vec<CommandData>, Error> { + let mut commands = Vec::new(); + + for entry in fs::read_dir(directory)? { + let entry = entry?; + let path = entry.path(); + + if path.is_file() && path.extension().unwrap_or_default() == "json" { + let json_str = fs::read_to_string(path)?; + let command: CommandData = serde_json::from_str(&json_str)?; + commands.push(command); + } + } + + Ok(commands) +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub enum RemoteCommandOptionType { + SubCommand, + SubCommandGroup, + String, + Integer, + Boolean, + User, + Channel, + Role, + Mentionable, + Number, + Attachment, + Unknown(u8), +} + +impl From<RemoteCommandOptionType> for CommandOptionType { + fn from(remote: RemoteCommandOptionType) -> Self { + match remote { + RemoteCommandOptionType::SubCommand => CommandOptionType::SubCommand, + RemoteCommandOptionType::SubCommandGroup => CommandOptionType::SubCommandGroup, + RemoteCommandOptionType::String => CommandOptionType::String, + RemoteCommandOptionType::Integer => CommandOptionType::Integer, + RemoteCommandOptionType::Boolean => CommandOptionType::Boolean, + RemoteCommandOptionType::User => CommandOptionType::User, + RemoteCommandOptionType::Channel => CommandOptionType::Channel, + RemoteCommandOptionType::Role => CommandOptionType::Role, + RemoteCommandOptionType::Mentionable => CommandOptionType::Mentionable, + RemoteCommandOptionType::Number => CommandOptionType::Number, + RemoteCommandOptionType::Attachment => CommandOptionType::Attachment, + RemoteCommandOptionType::Unknown(value) => CommandOptionType::Unknown(value), + } + } +} + +impl From<CommandOptionType> for RemoteCommandOptionType { + fn from(original: CommandOptionType) -> Self { + match original { + CommandOptionType::SubCommand => RemoteCommandOptionType::SubCommand, + CommandOptionType::SubCommandGroup => RemoteCommandOptionType::SubCommandGroup, + CommandOptionType::String => RemoteCommandOptionType::String, + CommandOptionType::Integer => RemoteCommandOptionType::Integer, + CommandOptionType::Boolean => RemoteCommandOptionType::Boolean, + CommandOptionType::User => RemoteCommandOptionType::User, + CommandOptionType::Channel => RemoteCommandOptionType::Channel, + CommandOptionType::Role => RemoteCommandOptionType::Role, + CommandOptionType::Mentionable => RemoteCommandOptionType::Mentionable, + CommandOptionType::Number => RemoteCommandOptionType::Number, + CommandOptionType::Attachment => RemoteCommandOptionType::Attachment, + CommandOptionType::Unknown(value) => RemoteCommandOptionType::Unknown(value), + _ => RemoteCommandOptionType::String, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub enum RemotePermissionType { + CreateInstantInvite, + KickMembers, + BanMembers, + Administrator, + ManageChannels, + ManageGuild, + AddReactions, + ViewAuditLog, + PrioritySpeaker, + Stream, + ViewChannel, + SendMessages, + SendTtsMessages, + ManageMessages, + EmbedLinks, + AttachFiles, + ReadMessageHistory, + MentionEveryone, + UseExternalEmojis, + ViewGuildInsights, + Connect, + Speak, + MuteMembers, + DeafenMembers, + MoveMembers, + UseVad, + ChangeNickname, + ManageNicknames, + ManageRoles, + ManageWebhooks, + ManageGuildExpressions, + UseApplicationCommands, + RequestToSpeak, + ManageEvents, + ManageThreads, + CreatePublicThreads, + CreatePrivateThreads, + UseExternalStickers, + SendMessagesInThreads, + UseEmbeddedActivities, + ModerateMembers, + ViewCreatorMonetizationAnalytics, + UseSoundboard, + CreateGuildExpressions, + CreateEvents, + UseExternalSounds, + SendVoiceMessages, + SetVoiceChannelStatus, + Unknown, +} + +impl From<RemotePermissionType> for Permissions { + fn from(remote: RemotePermissionType) -> Self { + match remote { + RemotePermissionType::CreateInstantInvite => Permissions::CREATE_INSTANT_INVITE, + RemotePermissionType::KickMembers => Permissions::KICK_MEMBERS, + RemotePermissionType::BanMembers => Permissions::BAN_MEMBERS, + RemotePermissionType::Administrator => Permissions::ADMINISTRATOR, + RemotePermissionType::ManageChannels => Permissions::MANAGE_CHANNELS, + RemotePermissionType::ManageGuild => Permissions::MANAGE_GUILD, + RemotePermissionType::AddReactions => Permissions::ADD_REACTIONS, + RemotePermissionType::ViewAuditLog => Permissions::VIEW_AUDIT_LOG, + RemotePermissionType::PrioritySpeaker => Permissions::PRIORITY_SPEAKER, + RemotePermissionType::Stream => Permissions::STREAM, + RemotePermissionType::ViewChannel => Permissions::VIEW_CHANNEL, + RemotePermissionType::SendMessages => Permissions::SEND_MESSAGES, + RemotePermissionType::SendTtsMessages => Permissions::SEND_TTS_MESSAGES, + RemotePermissionType::ManageMessages => Permissions::MANAGE_MESSAGES, + RemotePermissionType::EmbedLinks => Permissions::EMBED_LINKS, + RemotePermissionType::AttachFiles => Permissions::ATTACH_FILES, + RemotePermissionType::ReadMessageHistory => Permissions::READ_MESSAGE_HISTORY, + RemotePermissionType::MentionEveryone => Permissions::MENTION_EVERYONE, + RemotePermissionType::UseExternalEmojis => Permissions::USE_EXTERNAL_EMOJIS, + RemotePermissionType::ViewGuildInsights => Permissions::VIEW_GUILD_INSIGHTS, + RemotePermissionType::Connect => Permissions::CONNECT, + RemotePermissionType::Speak => Permissions::SPEAK, + RemotePermissionType::MuteMembers => Permissions::MUTE_MEMBERS, + RemotePermissionType::DeafenMembers => Permissions::DEAFEN_MEMBERS, + RemotePermissionType::MoveMembers => Permissions::MOVE_MEMBERS, + RemotePermissionType::UseVad => Permissions::USE_VAD, + RemotePermissionType::ChangeNickname => Permissions::CHANGE_NICKNAME, + RemotePermissionType::ManageNicknames => Permissions::MANAGE_NICKNAMES, + RemotePermissionType::ManageRoles => Permissions::MANAGE_ROLES, + RemotePermissionType::ManageWebhooks => Permissions::MANAGE_WEBHOOKS, + RemotePermissionType::ManageGuildExpressions => Permissions::MANAGE_GUILD_EXPRESSIONS, + RemotePermissionType::UseApplicationCommands => Permissions::USE_APPLICATION_COMMANDS, + RemotePermissionType::RequestToSpeak => Permissions::REQUEST_TO_SPEAK, + RemotePermissionType::ManageEvents => Permissions::MANAGE_EVENTS, + RemotePermissionType::ManageThreads => Permissions::MANAGE_THREADS, + RemotePermissionType::CreatePublicThreads => Permissions::CREATE_PUBLIC_THREADS, + RemotePermissionType::CreatePrivateThreads => Permissions::CREATE_PRIVATE_THREADS, + RemotePermissionType::UseExternalStickers => Permissions::USE_EXTERNAL_STICKERS, + RemotePermissionType::SendMessagesInThreads => Permissions::SEND_MESSAGES_IN_THREADS, + RemotePermissionType::UseEmbeddedActivities => Permissions::USE_EMBEDDED_ACTIVITIES, + RemotePermissionType::ModerateMembers => Permissions::MODERATE_MEMBERS, + RemotePermissionType::ViewCreatorMonetizationAnalytics => { + Permissions::VIEW_CREATOR_MONETIZATION_ANALYTICS + } + RemotePermissionType::UseSoundboard => Permissions::USE_SOUNDBOARD, + RemotePermissionType::CreateGuildExpressions => Permissions::CREATE_GUILD_EXPRESSIONS, + RemotePermissionType::CreateEvents => Permissions::CREATE_EVENTS, + RemotePermissionType::UseExternalSounds => Permissions::USE_EXTERNAL_SOUNDS, + RemotePermissionType::SendVoiceMessages => Permissions::SEND_VOICE_MESSAGES, + RemotePermissionType::SetVoiceChannelStatus => Permissions::SET_VOICE_CHANNEL_STATUS, + RemotePermissionType::Unknown => Permissions::empty(), + } + } +} diff --git a/src/command_register/mod.rs b/src/command_register/mod.rs new file mode 100644 index 00000000..1dc675f3 --- /dev/null +++ b/src/command_register/mod.rs @@ -0,0 +1,2 @@ +pub mod command_registration; +pub mod command_structure; diff --git a/src/command_run/ai/image.rs b/src/command_run/ai/image.rs new file mode 100644 index 00000000..2b77a343 --- /dev/null +++ b/src/command_run/ai/image.rs @@ -0,0 +1,176 @@ +use std::{env, fs}; + +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE}; +use serde_json::{json, Value}; +use serenity::all::CreateInteractionResponse::Defer; +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateAttachment, + CreateEmbed, CreateInteractionResponseFollowup, CreateInteractionResponseMessage, Timestamp, +}; +use uuid::Uuid; + +use crate::constant::{ + COLOR, COMMAND_SENDING_ERROR, DIFFERED_COMMAND_SENDING_ERROR, DIFFERED_OPTION_ERROR, + OPTION_ERROR, +}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + DifferedFailedToGetBytes, DifferedFailedUrlError, DifferedHeaderError, DifferedImageModelError, + DifferedResponseError, DifferedTokenError, DifferedWritingFile, NoCommandOption, +}; +use crate::lang_struct::ai::image::load_localization_image; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let lang = options.get(0).ok_or(OPTION_ERROR.clone())?; + let lang = lang.value.clone(); + + let desc = match lang { + CommandDataOptionValue::String(lang) => lang, + _ => { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + }; + + let image_localised = load_localization_image(guild_id).await?; + + let builder_message = Defer(CreateInteractionResponseMessage::new()); + + command + .create_response(&ctx.http, builder_message) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone())?; + + let uuid_name = Uuid::new_v4(); + let filename = format!("{}.png", uuid_name); + let filename_str = filename.as_str(); + + let prompt = desc; + let api_key = match env::var("AI_API_TOKEN") { + Ok(x) => x, + Err(_) => { + return Err(DifferedTokenError(String::from( + "There was an error while getting the token.", + ))); + } + }; + + let api_base_url = match env::var("AI_API_BASE_URL") { + Ok(x) => x, + Err(_) => "https://api.openai.com/v1/".to_string(), + }; + + let mut data = json!({ + "prompt": prompt, + "n": 1, + "size": "1024x1024", + "response_format": "url" + }); + if let Ok(image_generation_mode) = env::var("IMAGE_GENERATION_MODELS_ON") { + let is_ok = image_generation_mode.to_lowercase() == "true"; + if is_ok { + let model = match env::var("IMAGE_GENERATION_MODELS") { + Ok(data) => data, + Err(_) => { + return Err(DifferedImageModelError(String::from( + "Please specify the models you want to use", + ))); + } + }; + data = json!({ + "prompt": prompt, + "n": 1, + "size": "1024x1024", + "model": model, + "response_format": "url" + }) + } + } + + let api_url = format!("{}images/generations", api_base_url); + let client = reqwest::Client::new(); + + let mut headers = HeaderMap::new(); + headers.insert( + AUTHORIZATION, + match HeaderValue::from_str(&format!("Bearer {}", api_key)) { + Ok(data) => data, + Err(_) => { + return Err(DifferedHeaderError(String::from( + "Failed to create the header", + ))); + } + }, + ); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let res: Value = client + .post(api_url) + .headers(headers) + .json(&data) + .send() + .await + .map_err(|_| { + DifferedResponseError(String::from("Failed to get the response from the server.")) + })? + .json() + .await + .map_err(|_| { + DifferedResponseError(String::from("Failed to get the response from the server.")) + })?; + + let url_string = res + .get("data") + .ok_or(DIFFERED_OPTION_ERROR.clone())? + .get(0) + .ok_or(DIFFERED_OPTION_ERROR.clone())? + .get("url") + .ok_or(DIFFERED_OPTION_ERROR.clone())? + .as_str() + .ok_or(DifferedFailedUrlError(String::from( + "Failed to get the response url.", + )))?; + + let response = reqwest::get(url_string) + .await + .map_err(|_| DifferedResponseError(String::from("Failed to get data from url.")))?; + let bytes = response.bytes().await.map_err(|_| { + DifferedFailedToGetBytes(String::from("Failed to get bytes data from response.")) + })?; + + fs::write(&filename, &bytes) + .map_err(|_| DifferedWritingFile(String::from("Failed to write the file bytes.")))?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .image(format!("attachment://{}", &filename)) + .title(image_localised.title); + + let attachement = CreateAttachment::path(&filename) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + let builder_message = CreateInteractionResponseFollowup::new() + .embed(builder_embed) + .files(vec![attachement]); + + command + .create_followup(&ctx.http, builder_message) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + let _ = fs::remove_file(filename_str); + + Ok(()) +} diff --git a/src/cmd/ai_module/mod.rs b/src/command_run/ai/mod.rs similarity index 100% rename from src/cmd/ai_module/mod.rs rename to src/command_run/ai/mod.rs diff --git a/src/command_run/ai/transcript.rs b/src/command_run/ai/transcript.rs new file mode 100644 index 00000000..30c50383 --- /dev/null +++ b/src/command_run/ai/transcript.rs @@ -0,0 +1,197 @@ +use std::fs::File; +use std::io::copy; +use std::path::Path; +use std::{env, fs}; + +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; +use reqwest::{multipart, Url}; +use serde_json::Value; +use serenity::all::CreateInteractionResponse::Defer; +use serenity::all::{ + Attachment, CommandInteraction, Context, CreateEmbed, CreateInteractionResponseFollowup, + CreateInteractionResponseMessage, ResolvedOption, ResolvedValue, Timestamp, +}; +use tracing::log::trace; +use uuid::Uuid; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, DIFFERED_COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + DifferedCopyBytesError, DifferedFileExtensionError, DifferedFileTypeError, + DifferedGettingBytesError, DifferedResponseError, DifferedTokenError, NoCommandOption, +}; +use crate::lang_struct::ai::transcript::load_localization_transcript; + +pub async fn run( + options: &[ResolvedOption<'_>], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut prompt: String = String::new(); + let mut lang: String = String::new(); + let mut attachement: Option<Attachment> = None; + for option in options.iter().clone() { + if option.name == "lang" { + let resolved = option.value.clone(); + if let ResolvedValue::String(lang_option) = resolved { + lang = String::from(lang_option) + } + } + if option.name == "prompt" { + let resolved = option.value.clone(); + if let ResolvedValue::String(prompt_option) = resolved { + prompt = String::from(prompt_option) + } + } + if option.name == "video" { + if let ResolvedOption { + value: ResolvedValue::Attachment(attachment_option), + .. + } = option + { + let simple = *attachment_option; + let attach_option = simple.clone(); + attachement = Some(attach_option) + } else { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + } + } + + let attachement = match attachement { + Some(att) => att, + None => { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + }; + + let content_type = attachement + .content_type + .clone() + .ok_or(OPTION_ERROR.clone())?; + let content = attachement.proxy_url.clone(); + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let transcript_localised = load_localization_transcript(guild_id).await?; + + if !content_type.starts_with("audio/") && !content_type.starts_with("video/") { + return Err(DifferedFileTypeError(String::from("Bad file type."))); + } + + let builder_message = Defer(CreateInteractionResponseMessage::new()); + + command + .create_response(&ctx.http, builder_message) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone())?; + + let allowed_extensions = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"]; + let parsed_url = Url::parse(content.as_str()).expect("Failed to parse URL"); + let path_segments = parsed_url + .path_segments() + .expect("Failed to retrieve path segments"); + let last_segment = path_segments.last().expect("URL has no path segments"); + + let file_extension = last_segment + .rsplit('.') + .next() + .expect("No file extension found") + .to_lowercase(); + + if !allowed_extensions.contains(&&*file_extension) { + return Err(DifferedFileExtensionError(String::from( + "Bad file extension", + ))); + } + + let response = reqwest::get(content).await.expect("download"); + let uuid_name = Uuid::new_v4(); + let fname = Path::new("./").join(format!("{}.{}", uuid_name, file_extension)); + let file_name = format!("/{}.{}", uuid_name, file_extension); + let mut file = File::create(fname.clone()).expect("file name"); + let resp_byte = response.bytes().await.map_err(|_| { + DifferedGettingBytesError(String::from("Failed to get the bytes from the response.")) + })?; + copy(&mut resp_byte.as_ref(), &mut file) + .map_err(|_| DifferedCopyBytesError(String::from("Failed to copy bytes data.")))?; + let file_to_delete = fname.clone(); + + let my_path = "./.env"; + let path = Path::new(my_path); + let _ = dotenv::from_path(path); + let api_key = match env::var("AI_API_TOKEN") { + Ok(x) => x, + Err(_) => { + return Err(DifferedTokenError(String::from( + "There was an error while getting the token.", + ))); + } + }; + let api_base_url = match env::var("AI_API_BASE_URL") { + Ok(x) => x, + Err(_) => "https://api.openai.com/v1/".to_string(), + }; + let api_url = format!("{}audio/transcriptions", api_base_url); + let client = reqwest::Client::new(); + let mut headers = HeaderMap::new(); + headers.insert( + AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {}", api_key)).unwrap(), + ); + + let file = fs::read(fname).unwrap(); + let part = multipart::Part::bytes(file) + .file_name(file_name) + .mime_str(content_type.as_str()) + .unwrap(); + let prompt = prompt; + let form = multipart::Form::new() + .part("file", part) + .text("model", "whisper-1") + .text("prompt", prompt) + .text("language", lang) + .text("response_format", "json"); + + let response_result = client + .post(api_url) + .headers(headers) + .multipart(form) + .send() + .await; + let response = response_result.map_err(|_| { + DifferedResponseError(String::from("Failed to get the response from the server.")) + })?; + let res_result: Result<Value, reqwest::Error> = response.json().await; + + let res = res_result.map_err(|_| { + DifferedResponseError(String::from("Failed to get the response from the server.")) + })?; + + let _ = fs::remove_file(&file_to_delete); + trace!("{}", res); + let text = res["text"].as_str().unwrap_or(""); + trace!("{}", text); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .title(transcript_localised.title) + .description(text); + + let builder_message = CreateInteractionResponseFollowup::new().embed(builder_embed); + + command + .create_followup(&ctx.http, builder_message) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + Ok(()) +} diff --git a/src/command_run/ai/translation.rs b/src/command_run/ai/translation.rs new file mode 100644 index 00000000..c1525880 --- /dev/null +++ b/src/command_run/ai/translation.rs @@ -0,0 +1,237 @@ +use std::fs::File; +use std::io::copy; +use std::path::Path; +use std::{env, fs}; + +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE}; +use reqwest::{multipart, Url}; +use serde_json::{json, Value}; +use serenity::all::CreateInteractionResponse::Defer; +use serenity::all::{ + Attachment, CommandInteraction, Context, CreateEmbed, CreateInteractionResponseFollowup, + CreateInteractionResponseMessage, ResolvedOption, ResolvedValue, Timestamp, +}; +use tracing::log::trace; +use uuid::Uuid; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, DIFFERED_COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + DifferedCopyBytesError, DifferedFileExtensionError, DifferedFileTypeError, + DifferedGettingBytesError, DifferedResponseError, DifferedTokenError, NoCommandOption, +}; +use crate::lang_struct::ai::translation::load_localization_translation; + +pub async fn run( + options: &[ResolvedOption<'_>], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut lang: String = String::new(); + let mut attachement: Option<Attachment> = None; + for option in options.iter().clone() { + if option.name == "lang_struct" { + let resolved = option.value.clone(); + if let ResolvedValue::String(lang_option) = resolved { + lang = String::from(lang_option) + } + } + if option.name == "video" { + if let ResolvedOption { + value: ResolvedValue::Attachment(attachment_option), + .. + } = option + { + let simple = *attachment_option; + let attach_option = simple.clone(); + attachement = Some(attach_option) + } else { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + } + } + + let attachement = match attachement { + Some(att) => att, + None => { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + }; + + let content_type = attachement + .content_type + .clone() + .ok_or(OPTION_ERROR.clone())?; + let content = attachement.proxy_url.clone(); + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let translation_localised = load_localization_translation(guild_id).await?; + + if !content_type.starts_with("audio/") && !content_type.starts_with("video/") { + return Err(DifferedFileTypeError(String::from("Bad file type."))); + } + + let builder_message = Defer(CreateInteractionResponseMessage::new()); + + command + .create_response(&ctx.http, builder_message) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone())?; + + let allowed_extensions = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"]; + let parsed_url = Url::parse(content.as_str()).expect("Failed to parse URL"); + let path_segments = parsed_url + .path_segments() + .expect("Failed to retrieve path segments"); + let last_segment = path_segments.last().expect("URL has no path segments"); + + let file_extension = last_segment + .rsplit('.') + .next() + .expect("No file extension found") + .to_lowercase(); + + if !allowed_extensions.contains(&&*file_extension) { + return Err(DifferedFileExtensionError(String::from( + "Bad file extension", + ))); + } + + let response = reqwest::get(content).await.expect("download"); + let uuid_name = Uuid::new_v4(); + let fname = Path::new("./").join(format!("{}.{}", uuid_name, file_extension)); + let file_name = format!("/{}.{}", uuid_name, file_extension); + let mut file = File::create(fname.clone()).expect("file name"); + let resp_byte = response.bytes().await.map_err(|_| { + DifferedGettingBytesError(String::from("Failed to get the bytes from the response.")) + })?; + copy(&mut resp_byte.as_ref(), &mut file) + .map_err(|_| DifferedCopyBytesError(String::from("Failed to copy bytes data.")))?; + let file_to_delete = fname.clone(); + + let my_path = "./.env"; + let path = Path::new(my_path); + let _ = dotenv::from_path(path); + let api_key = match env::var("AI_API_TOKEN") { + Ok(x) => x, + Err(_) => { + return Err(DifferedTokenError(String::from( + "There was an error while getting the token.", + ))); + } + }; + let api_base_url = match env::var("AI_API_BASE_URL") { + Ok(x) => x, + Err(_) => "https://api.openai.com/v1/".to_string(), + }; + let api_url = format!("{}audio/transcriptions", api_base_url); + let client = reqwest::Client::new(); + let mut headers = HeaderMap::new(); + headers.insert( + AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {}", api_key)).unwrap(), + ); + + let file = fs::read(fname).unwrap(); + let part = multipart::Part::bytes(file) + .file_name(file_name) + .mime_str(content_type.as_str()) + .unwrap(); + let form = multipart::Form::new() + .part("file", part) + .text("model", "whisper-1") + .text("language", lang.clone()) + .text("response_format", "json"); + + let response_result = client + .post(api_url) + .headers(headers) + .multipart(form) + .send() + .await; + let response = response_result.map_err(|_| { + DifferedResponseError(String::from("Failed to get the response from the server.")) + })?; + let res_result: Result<Value, reqwest::Error> = response.json().await; + + let res = res_result.map_err(|_| { + DifferedResponseError(String::from("Failed to get the response from the server.")) + })?; + + let _ = fs::remove_file(&file_to_delete); + trace!("{}", res); + let text = res["text"].as_str().unwrap_or(""); + trace!("{}", text); + + let text = if lang != "en" { + translation(lang, text.to_string(), api_key, api_base_url).await? + } else { + String::from(text) + }; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .title(translation_localised.title) + .description(text); + + let builder_message = CreateInteractionResponseFollowup::new().embed(builder_embed); + + command + .create_followup(&ctx.http, builder_message) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + Ok(()) +} + +pub async fn translation( + lang: String, + text: String, + api_key: String, + api_base_url: String, +) -> Result<String, AppError> { + let prompt_gpt = format!(" + i will give you a text and a ISO-639-1 code and you will translate it in the corresponding langage + iso code: {} + text: + {} + ", lang, text); + + let api_url = format!("{}chat/completions", api_base_url); + let client = reqwest::Client::new(); + let mut headers = HeaderMap::new(); + headers.insert( + AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {}", api_key)).unwrap(), + ); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let data = json!({ + "model": "gpt-3.5-turbo-16k", + "messages": [{"role": "system", "content": "You are a expert in translating and only do that."},{"role": "user", "content": prompt_gpt}] + }); + + let res: Value = client + .post(api_url) + .headers(headers) + .json(&data) + .send() + .await + .map_err(|_| DifferedResponseError(String::from("error translation")))? + .json() + .await + .map_err(|_| DifferedResponseError(String::from("error translation")))?; + let content = res["choices"][0]["message"]["content"].to_string(); + let no_quote = content.replace('"', ""); + + Ok(no_quote.replace("\\n", " \\n ")) +} diff --git a/src/command_run/anilist/add_activity.rs b/src/command_run/anilist/add_activity.rs new file mode 100644 index 00000000..2a4a5cbf --- /dev/null +++ b/src/command_run/anilist/add_activity.rs @@ -0,0 +1,213 @@ +use std::io::Cursor; + +use base64::{engine::general_purpose, Engine as _}; +use image::imageops::FilterType; +use image::{guess_format, GenericImageView, ImageFormat}; +use reqwest::get; +use serde_json::json; +use serenity::all::CreateInteractionResponse::Defer; +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponseFollowup, CreateInteractionResponseMessage, Timestamp, +}; + +use crate::anilist_struct::run::minimal_anime::{MinimalAnimeWrapper, Title}; +use crate::common::trimer::trim_webhook; +use crate::constant::{ + COLOR, COMMAND_SENDING_ERROR, DIFFERED_COMMAND_SENDING_ERROR, DIFFERED_OPTION_ERROR, +}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{CreatingWebhookDifferedError, DifferedNotAiringError}; +use crate::lang_struct::anilist::add_activity::load_localization_add_activity; +use crate::sqls::general::data::{get_one_activity, set_data_activity}; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut delay = 0; + let mut anime = String::new(); + for option in options { + if option.name == "delay" { + let resolved = &option.value; + if let CommandDataOptionValue::Integer(delay_option) = resolved { + delay = delay_option.clone() + } else { + delay = 0; + } + } + if option.name == "anime_name" { + let resolved = &option.value; + if let CommandDataOptionValue::String(anime_option) = resolved { + anime = anime_option.clone() + } else { + anime = String::new() + } + } + } + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let builder_message = Defer(CreateInteractionResponseMessage::new()); + + command + .create_response(&ctx.http, builder_message) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone())?; + + let add_activity_localised = load_localization_add_activity(guild_id.clone()).await?; + + let data = if anime.parse::<i32>().is_ok() { + MinimalAnimeWrapper::new_minimal_anime_by_id(anime.parse().unwrap()).await? + } else { + MinimalAnimeWrapper::new_minimal_anime_by_search(anime.to_string()).await? + }; + let media = data.data.media.clone(); + let anime_id = media.id.clone(); + let title = data.data.media.title.ok_or(DIFFERED_OPTION_ERROR.clone())?; + let mut anime_name = get_name(title); + let channel_id = command.channel_id; + if check_if_activity_exist(anime_id, guild_id.clone()).await { + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .title(&add_activity_localised.fail) + .url(format!("https://anilist.co/anime/{}", media.id)) + .description( + &add_activity_localised + .fail_desc + .replace("$anime$", anime_name.as_str()), + ); + + let builder_message = CreateInteractionResponseFollowup::new().embed(builder_embed); + + command + .create_followup(&ctx.http, builder_message) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + Ok(()) + } else { + if anime_name.len() >= 50 { + anime_name = trim_webhook(anime_name.clone(), 50 - anime_name.len() as i32) + } + + let bytes = get(media.cover_image.unwrap().extra_large. + unwrap_or( + "https://imgs.search.brave.com/CYnhSvdQcm9aZe3wG84YY0B19zT2wlAuAkiAGu0mcLc/rs:fit:640:400:1/g:ce/aHR0cDovL3d3dy5m/cmVtb250Z3VyZHdh/cmEub3JnL3dwLWNv/bnRlbnQvdXBsb2Fk/cy8yMDIwLzA2L25v/LWltYWdlLWljb24t/Mi5wbmc" + .to_string() + ) + ).await.unwrap().bytes().await.unwrap(); + let mut img = image::load(Cursor::new(&bytes), guess_format(&bytes).unwrap()).unwrap(); + let (width, height) = img.dimensions(); + let square_size = width.min(height); + let crop_x = (width - square_size) / 2; + let crop_y = (height - square_size) / 2; + + let img = img + .crop(crop_x, crop_y, square_size, square_size) + .resize_exact(128, 128, FilterType::Nearest); + let mut buf = Cursor::new(Vec::new()); + img.write_to(&mut buf, ImageFormat::Jpeg) + .expect("Failed to encode image"); + let base64 = general_purpose::STANDARD.encode(buf.into_inner()); + let image = format!("data:image/jpeg;base64,{}", base64); + let map = json!({ + "avatar": image, + "name": anime_name + }); + + let next_airing = match media.next_airing_episode.clone() { + Some(na) => na, + None => return Err(DifferedNotAiringError(String::from("Not airing"))), + }; + + let webhook = ctx + .http + .create_webhook(channel_id, &map, None) + .await + .map_err(|_| { + CreatingWebhookDifferedError(String::from("Error when creating the webhook.")) + })? + .url() + .map_err(|_| { + CreatingWebhookDifferedError(String::from("Error when getting the webhook url.")) + })?; + + set_data_activity( + anime_id, + next_airing.airing_at.unwrap_or(0), + guild_id, + webhook, + next_airing.episode.unwrap_or(0), + anime_name.clone(), + delay, + ) + .await?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .title(&add_activity_localised.success) + .url(format!("https://anilist.co/anime/{}", media.id)) + .description( + &add_activity_localised + .success_desc + .replace("$anime$", anime_name.as_str()), + ); + + let builder_message = CreateInteractionResponseFollowup::new().embed(builder_embed); + + command + .create_followup(&ctx.http, builder_message) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + Ok(()) + } +} + +async fn check_if_activity_exist(anime_id: i32, server_id: String) -> bool { + let row: ( + Option<String>, + Option<String>, + Option<String>, + Option<String>, + ) = get_one_activity(anime_id, server_id) + .await + .unwrap_or((None, None, None, None)); + !(row.0.is_none() && row.1.is_none() && row.2.is_none() && row.3.is_none()) +} + +fn get_name(title: Title) -> String { + let en = title.english.clone(); + let rj = title.romaji.clone(); + let en = en.unwrap_or(String::from("")); + let rj = rj.unwrap_or(String::from("")); + let mut title = String::new(); + let mut total = 0; + match en.as_str() { + "" => {} + _ => { + total += 1; + title.push_str(en.as_str()) + } + } + match rj.as_str() { + "\"\"" => {} + _ => { + if total == 1 { + title.push_str(" / "); + title.push_str(rj.as_str()) + } else { + title.push_str(rj.as_str()) + } + } + } + + title +} diff --git a/src/command_run/anilist/anime.rs b/src/command_run/anilist/anime.rs new file mode 100644 index 00000000..73a17994 --- /dev/null +++ b/src/command_run/anilist/anime.rs @@ -0,0 +1,26 @@ +use serenity::all::{CommandDataOption, CommandInteraction, Context}; + +use crate::anilist_struct::run::media::{send_embed, MediaWrapper}; +use crate::error_enum::AppError; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut value = String::new(); + for option_data in options { + if option_data.name.as_str() != "type" { + let option_value = option_data.value.as_str().clone().unwrap(); + value = option_value.to_string().clone() + } + } + + let data: MediaWrapper = if value.parse::<i32>().is_ok() { + MediaWrapper::new_anime_by_id(value.parse().unwrap()).await? + } else { + MediaWrapper::new_anime_by_search(&value).await? + }; + + send_embed(ctx, command, data).await +} diff --git a/src/command_run/anilist/character.rs b/src/command_run/anilist/character.rs new file mode 100644 index 00000000..57c4eb49 --- /dev/null +++ b/src/command_run/anilist/character.rs @@ -0,0 +1,26 @@ +use serenity::all::{CommandDataOption, CommandInteraction, Context}; + +use crate::anilist_struct::run::character::{send_embed, CharacterWrapper}; +use crate::error_enum::AppError; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut value = String::new(); + for option_data in options { + if option_data.name.as_str() != "type" { + let option_value = option_data.value.as_str().clone().unwrap(); + value = option_value.to_string().clone() + } + } + + let data: CharacterWrapper = if value.parse::<i32>().is_ok() { + CharacterWrapper::new_character_by_id(value.parse().unwrap()).await? + } else { + CharacterWrapper::new_character_by_search(&value).await? + }; + + send_embed(ctx, command, data).await +} diff --git a/src/command_run/anilist/compare.rs b/src/command_run/anilist/compare.rs new file mode 100644 index 00000000..06fc6ff0 --- /dev/null +++ b/src/command_run/anilist/compare.rs @@ -0,0 +1,321 @@ +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, +}; + +use crate::anilist_struct::run::user::UserWrapper; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NoCommandOption; +use crate::lang_struct::anilist::compare::load_localization_compare; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let option = &options.get(0).ok_or(OPTION_ERROR.clone())?.value; + + let value = match option { + CommandDataOptionValue::String(lang) => lang, + _ => { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + }; + + let option2 = &options.get(1).ok_or(OPTION_ERROR.clone())?.value; + + let value2 = match option2 { + CommandDataOptionValue::String(lang) => lang, + _ => { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + }; + + let data: UserWrapper = if value.parse::<i32>().is_ok() { + UserWrapper::new_user_by_id(value.parse().unwrap()).await? + } else { + UserWrapper::new_user_by_search(value).await? + }; + + let data2: UserWrapper = if value2.parse::<i32>().is_ok() { + UserWrapper::new_user_by_id(value2.parse().unwrap()).await? + } else { + UserWrapper::new_user_by_search(value2).await? + }; + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let compare_localised = load_localization_compare(guild_id).await?; + + let user = data.data.user.clone(); + let user2 = data2.data.user.clone(); + let username = user.name.clone().unwrap_or(String::new()); + let username2 = user2.name.clone().unwrap_or(String::new()); + + let mut desc = String::new(); + + if &user.statistics.anime.count.unwrap_or(0) > &user2.statistics.anime.count.unwrap_or(0) { + desc.push_str( + compare_localised + .more_anime + .replace("$greater$", username.as_str()) + .replace("$lesser$", username2.as_str()) + .as_str(), + ) + } else if user.statistics.anime.count.unwrap_or(0) < user2.statistics.anime.count.unwrap_or(0) { + desc.push_str( + compare_localised + .more_anime + .replace("$greater$", username2.as_str()) + .replace("$lesser$", username.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_anime + .replace("$2$", username2.as_str()) + .replace("$1$", username.as_str()) + .as_str(), + ) + } + + if user.statistics.anime.minutes_watched.unwrap_or(0) + > user2.statistics.anime.minutes_watched.unwrap_or(0) + { + desc.push_str( + compare_localised + .more_watch_time + .replace("$greater$", username.as_str()) + .replace("$lesser$", username2.as_str()) + .as_str(), + ) + } else if &user.statistics.anime.minutes_watched.unwrap_or(0) + < &user2.statistics.anime.minutes_watched.unwrap_or(0) + { + desc.push_str( + compare_localised + .more_watch_time + .replace("$greater$", username2.as_str()) + .replace("$lesser$", username.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_watch_time + .replace("$2$", username2.as_str()) + .replace("$1$", username.as_str()) + .as_str(), + ) + } + + let tag = user.statistics.anime.tags[0] + .tag + .name + .clone() + .unwrap_or(String::new()); + let tag2 = user2.statistics.anime.tags[0] + .tag + .name + .clone() + .unwrap_or(String::new()); + + let diff = tag != tag2; + + if diff { + desc.push_str( + compare_localised + .tag_anime + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", tag.as_str()) + .replace("$2a$", tag2.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_tag_anime + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", tag.as_str()) + .as_str(), + ) + } + + let genre = user.statistics.anime.genres[0] + .genre + .clone() + .unwrap_or(String::new()); + let genre2 = user2.statistics.anime.genres[0] + .genre + .clone() + .unwrap_or(String::new()); + + let diff = genre != genre2; + + if diff { + desc.push_str( + compare_localised + .genre_anime + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", genre.as_str()) + .replace("$2a$", genre2.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_tag_anime + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", genre.as_str()) + .as_str(), + ) + } + + if user.statistics.manga.count.unwrap_or(0) > user2.statistics.manga.count.unwrap_or(0) { + desc.push_str( + compare_localised + .more_manga + .replace("$greater$", username.as_str()) + .replace("$lesser$", username2.as_str()) + .as_str(), + ) + } else if user.statistics.manga.count.unwrap_or(0) < user2.statistics.manga.count.unwrap_or(0) { + desc.push_str( + compare_localised + .more_manga + .replace("$greater$", username2.as_str()) + .replace("$lesser$", username.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_manga + .replace("$2$", username2.as_str()) + .replace("$1$", username.as_str()) + .as_str(), + ) + } + + if user.statistics.manga.chapters_read.unwrap_or(0) + > user2.statistics.manga.chapters_read.unwrap_or(0) + { + desc.push_str( + compare_localised + .more_manga_chapter + .replace("$greater$", username.as_str()) + .replace("$lesser$", username2.as_str()) + .as_str(), + ) + } else if user.statistics.manga.chapters_read.unwrap_or(0) + < user2.statistics.manga.chapters_read.unwrap_or(0) + { + desc.push_str( + compare_localised + .more_manga_chapter + .replace("$greater$", username2.as_str()) + .replace("$lesser$", username.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_manga_chapter + .replace("$2$", username2.as_str()) + .replace("$1$", username.as_str()) + .as_str(), + ) + } + + let tag = user.statistics.manga.tags[0] + .tag + .name + .clone() + .unwrap_or(String::new()); + let tag2 = user2.statistics.manga.tags[0] + .tag + .name + .clone() + .unwrap_or(String::new()); + + let diff = tag != tag2; + + if diff { + desc.push_str( + compare_localised + .tag_manga + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", tag.as_str()) + .replace("$2a$", tag2.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_tag_manga + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", tag.as_str()) + .as_str(), + ) + } + + let genre = user.statistics.manga.genres[0] + .genre + .clone() + .unwrap_or(String::new()); + let genre2 = user2.statistics.manga.genres[0] + .genre + .clone() + .unwrap_or(String::new()); + + let diff = genre != genre2; + + if diff { + desc.push_str( + compare_localised + .genre_manga + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", genre.as_str()) + .replace("$2a$", genre2.as_str()) + .as_str(), + ) + } else { + desc.push_str( + compare_localised + .same_tag_manga + .replace("$1$", username.as_str()) + .replace("$2$", username2.as_str()) + .replace("$1a$", genre.as_str()) + .as_str(), + ) + } + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(desc); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/anilist/level.rs b/src/command_run/anilist/level.rs new file mode 100644 index 00000000..a4f1651b --- /dev/null +++ b/src/command_run/anilist/level.rs @@ -0,0 +1,168 @@ +use serenity::all::{ + CommandDataOption, CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; +use tracing::trace; + +use crate::anilist_struct::run::user::{ + get_banner, get_color, get_completed, get_user_url, UserWrapper, +}; +use crate::constant::{COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::anilist::level::load_localization_level; +use crate::sqls::general::data::get_registered_user; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + trace!("{:?}", options); + for option in options { + if option.name.as_str() != "type" { + match option.value.as_str() { + Some(a) => { + let value = &a.to_string(); + + let data: UserWrapper = if value.parse::<i32>().is_ok() { + UserWrapper::new_user_by_id(value.parse().unwrap()).await? + } else { + UserWrapper::new_user_by_search(value).await? + }; + + return send_embed(ctx, command, data).await; + } + + None => {} + } + } + } + let user_id = &command.user.id.to_string(); + let row: (Option<String>, Option<String>) = get_registered_user(user_id).await?; + trace!("{:?}", row); + let (user, _): (Option<String>, Option<String>) = row; + let user = user.ok_or(OPTION_ERROR.clone())?; + let data = UserWrapper::new_user_by_id((&user).parse::<i32>().unwrap()).await?; + return send_embed(ctx, command, data).await; +} + +pub async fn send_embed( + ctx: &Context, + command: &CommandInteraction, + data: UserWrapper, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let level_localised = load_localization_level(guild_id).await?; + + let user = data.data.user.clone(); + + let manga = user.statistics.manga.clone(); + let anime = user.statistics.anime.clone(); + + let manga_completed = get_completed(manga.statuses.clone()); + let anime_completed = get_completed(anime.statuses.clone()); + let chap_read = manga.chapters_read.clone().unwrap_or(0); + let tw = anime.minutes_watched.clone().unwrap_or(0); + + let xp = + (2.0 * (manga_completed + anime_completed) as f64) + chap_read as f64 + (tw as f64 * 0.1); + + let username = user.name.clone().unwrap(); + + let (level, actual, next_xp): (u32, f64, f64) = get_level(xp); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(get_color(user.clone())) + .title(&user.name.unwrap_or(String::new())) + .url(get_user_url(&user.id.clone().unwrap_or(0))) + .image(get_banner(&user.id.clone().unwrap_or(0))) + .thumbnail(&user.avatar.large.clone().unwrap()) + .description( + level_localised + .desc + .replace("$username$", username.as_str()) + .replace("$level$", level.to_string().as_str()) + .replace("$xp$", xp.to_string().as_str()) + .replace("$actual$", actual.to_string().as_str()) + .replace("$next$", next_xp.to_string().as_str()), + ); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} + +pub const LEVELS: [(u32, f64, f64); 51] = [ + (0, 0.0, 20.0), + (1, 20.0, 40.0), + (2, 40.0, 60.0), + (3, 60.0, 80.0), + (4, 80.0, 100.0), + (5, 100.0, 130.0), + (6, 130.0, 160.0), + (7, 160.0, 190.0), + (8, 190.0, 220.0), + (9, 220.0, 250.0), + (10, 250.0, 280.0), + (11, 280.0, 310.0), + (12, 310.0, 340.0), + (13, 340.0, 370.0), + (14, 370.0, 400.0), + (15, 9360.0, 13860.0), + (16, 13860.0, 20460.0), + (17, 20460.0, 30160.0), + (18, 30160.0, 44360.0), + (19, 44360.0, 65160.0), + (20, 65160.0, 95560.0), + (21, 95560.0, 140160.0), + (22, 140160.0, 206160.0), + (23, 206160.0, 303160.0), + (24, 303160.0, 447160.0), + (25, 447160.0, 657160.0), + (26, 657160.0, 969160.0), + (27, 969160.0, 1426160.0), + (28, 1426160.0, 2096160.0), + (29, 2096160.0, 3076160.0), + (30, 3076160.0, 4526160.0), + (31, 4526160.0, 6626160.0), + (32, 6626160.0, 9746160.0), + (33, 9746160.0, 14316160.0), + (34, 14316160.0, 21016160.0), + (35, 21016160.0, 30816160.0), + (36, 30816160.0, 45316160.0), + (37, 45316160.0, 66316160.0), + (38, 66316160.0, 97516160.0), + (39, 97516160.0, 143516160.0), + (40, 143516160.0, 210516160.0), + (41, 210516160.0, 308516160.0), + (42, 308516160.0, 453516160.0), + (43, 453516160.0, 663516160.0), + (44, 663516160.0, 975516160.0), + (45, 975516160.0, 1437516160.0), + (46, 1437516160.0, 2107516160.0), + (47, 2107516160.0, 3087516160.0), + (48, 3087516160.0, 4537516160.0), + (49, 4537516160.0, 6637516160.0), + (50, 6637516160.0, 9757516160.0), +]; + +fn get_level(xp: f64) -> (u32, f64, f64) { + for &(level, required_xp, next_level_required_xp) in LEVELS.iter().rev() { + if xp >= required_xp { + let level_progress = xp - required_xp; + let level_progress_total = next_level_required_xp - required_xp; + return (level, level_progress, level_progress_total); + } + } + (0, 0.0, 20.0) +} diff --git a/src/command_run/anilist/ln.rs b/src/command_run/anilist/ln.rs new file mode 100644 index 00000000..409481b5 --- /dev/null +++ b/src/command_run/anilist/ln.rs @@ -0,0 +1,26 @@ +use serenity::all::{CommandDataOption, CommandInteraction, Context}; + +use crate::anilist_struct::run::media::{send_embed, MediaWrapper}; +use crate::error_enum::AppError; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut value = String::new(); + for option_data in options { + if option_data.name.as_str() != "type" { + let option_value = option_data.value.as_str().clone().unwrap(); + value = option_value.to_string().clone() + } + } + + let data: MediaWrapper = if value.parse::<i32>().is_ok() { + MediaWrapper::new_ln_by_id(value.parse().unwrap()).await? + } else { + MediaWrapper::new_ln_by_search(&value).await? + }; + + send_embed(ctx, command, data).await +} diff --git a/src/command_run/anilist/manga.rs b/src/command_run/anilist/manga.rs new file mode 100644 index 00000000..9cd137f6 --- /dev/null +++ b/src/command_run/anilist/manga.rs @@ -0,0 +1,26 @@ +use serenity::all::{CommandDataOption, CommandInteraction, Context}; + +use crate::anilist_struct::run::media::{send_embed, MediaWrapper}; +use crate::error_enum::AppError; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut value = String::new(); + for option_data in options { + if option_data.name.as_str() != "type" { + let option_value = option_data.value.as_str().clone().unwrap(); + value = option_value.to_string().clone() + } + } + + let data: MediaWrapper = if value.parse::<i32>().is_ok() { + MediaWrapper::new_manga_by_id(value.parse().unwrap()).await? + } else { + MediaWrapper::new_manga_by_search(&value).await? + }; + + send_embed(ctx, command, data).await +} diff --git a/src/cmd/anilist_module/mod.rs b/src/command_run/anilist/mod.rs similarity index 69% rename from src/cmd/anilist_module/mod.rs rename to src/command_run/anilist/mod.rs index 935e62c2..df988ae6 100644 --- a/src/cmd/anilist_module/mod.rs +++ b/src/command_run/anilist/mod.rs @@ -2,16 +2,13 @@ pub mod add_activity; pub mod anime; pub mod character; pub mod compare; -//pub mod get_activity; -pub mod get_register_user; pub mod level; pub mod ln; pub mod manga; pub mod random; pub mod register; -pub mod register_waifu; pub mod search; -pub mod send_activity; +pub mod seiyuu; pub mod staff; pub mod studio; pub mod user; diff --git a/src/command_run/anilist/random.rs b/src/command_run/anilist/random.rs new file mode 100644 index 00000000..e1a1ccb5 --- /dev/null +++ b/src/command_run/anilist/random.rs @@ -0,0 +1,199 @@ +use chrono::Utc; +use rand::{thread_rng, Rng}; +use serenity::all::CreateInteractionResponse::Defer; +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponseFollowup, CreateInteractionResponseMessage, Timestamp, +}; + +use crate::anilist_struct::run::random::PageWrapper; +use crate::anilist_struct::run::site_statistic_anime::SiteStatisticsAnimeWrapper; +use crate::anilist_struct::run::site_statistic_manga::SiteStatisticsMangaWrapper; +use crate::common::html_parser::convert_to_discord_markdown; +use crate::common::trimer::trim; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, DIFFERED_COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::anilist::random::{load_localization_random, RandomLocalised}; +use crate::sqls::general::cache::{get_database_random_cache, set_database_random_cache}; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let random_localised = load_localization_random(guild_id).await?; + + let builder_message = Defer(CreateInteractionResponseMessage::new()); + + command + .create_response(&ctx.http, builder_message) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone())?; + let option = &options.get(0).ok_or(OPTION_ERROR.clone())?.value; + if let CommandDataOptionValue::String(random_type) = option { + let row: (Option<String>, Option<i64>, Option<i64>) = + get_database_random_cache(random_type).await?; + let (response, last_updated, last_page): (Option<String>, Option<i64>, Option<i64>) = row; + let page_number = last_page.unwrap_or(1620); // This is as today date the last page, i will update it sometime. + let previous_page = page_number - 1; + let cached_response = response.unwrap_or("Nothing".to_string()); + if let Some(updated) = last_updated { + let duration_since_updated = Utc::now().timestamp() - updated; + if duration_since_updated < 24 * 60 * 60 { + return embed( + page_number, + random_type.to_string(), + ctx, + command, + random_localised, + ) + .await; + } + } + update_cache( + page_number, + random_type, + ctx, + command, + previous_page, + cached_response, + random_localised, + ) + .await + } else { + Err(AppError::NoCommandOption(String::from( + "The command contain no option.", + ))) + } +} + +pub async fn embed( + last_page: i64, + random_type: String, + ctx: &Context, + command: &CommandInteraction, + random_localised: RandomLocalised, +) -> Result<(), AppError> { + let number = thread_rng().gen_range(1..=last_page); + return if random_type == "manga" { + let data = PageWrapper::new_manga_page(number).await?; + let url = format!( + "https://anilist.co/manga/{}", + data.data.page.media.clone()[0].id + ); + follow_up_message(ctx, command, data, url, random_localised).await + } else if random_type == "anime" { + let data = PageWrapper::new_anime_page(number).await?; + let url = format!( + "https://anilist.co/anime/{}", + data.data.page.media.clone()[0].id + ); + follow_up_message(ctx, command, data, url, random_localised).await + } else { + Ok(()) + }; +} + +pub async fn follow_up_message( + ctx: &Context, + command: &CommandInteraction, + data: PageWrapper, + url: String, + random_localised: RandomLocalised, +) -> Result<(), AppError> { + let media = data.data.page.media.clone()[0].clone(); + let format = media.format.clone(); + let genres = media.genres.join("/"); + let tags = media + .tags + .into_iter() + .map(|tag| tag.name.clone()) + .collect::<Vec<String>>() + .join("/"); + let mut desc = media.description; + desc = convert_to_discord_markdown(desc); + let length_diff = 4096 - desc.len() as i32; + if length_diff <= 0 { + trim(desc.clone(), length_diff); + } + let rj = media.title.native; + let user_pref = media.title.user_preferred; + let title = format!("{}/{}", user_pref, rj); + + let full_desc = random_localised + .desc + .replace("$format$", format.as_str()) + .replace("$tags$", tags.as_str()) + .replace("$genres$", genres.as_str()) + .replace("$desc$", desc.as_str()); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .title(title) + .description(full_desc) + .url(url); + + let builder_message = CreateInteractionResponseFollowup::new().embed(builder_embed); + + command + .create_followup(&ctx.http, builder_message) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + Ok(()) +} + +pub async fn update_cache( + mut page_number: i64, + random_type: &String, + ctx: &Context, + command: &CommandInteraction, + mut previous_page: i64, + mut cached_response: String, + random_localised: RandomLocalised, +) -> Result<(), AppError> { + let now = Utc::now().timestamp(); + + if random_type.as_str() == "manga" { + loop { + let (data, res) = SiteStatisticsMangaWrapper::new_manga(page_number).await?; + let has_next_page = data.has_next_page(); + + if !has_next_page { + break; + } + cached_response = res.to_string(); + previous_page = page_number; + + page_number += 1; + } + } else if random_type.as_str() == "anime" { + loop { + let (data, res) = SiteStatisticsAnimeWrapper::new_anime(page_number).await?; + let has_next_page = data.has_next_page(); + + if !has_next_page { + break; + } + cached_response = res.to_string(); + previous_page = page_number; + + page_number += 1; + } + } + + set_database_random_cache(random_type, cached_response.as_str(), now, previous_page).await?; + embed( + previous_page, + random_type.to_string(), + ctx, + command, + random_localised, + ) + .await +} diff --git a/src/command_run/anilist/register.rs b/src/command_run/anilist/register.rs new file mode 100644 index 00000000..9306a2ea --- /dev/null +++ b/src/command_run/anilist/register.rs @@ -0,0 +1,74 @@ +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, +}; + +use crate::anilist_struct::run::user::{get_color, get_user_url, UserWrapper}; +use crate::constant::{COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NoCommandOption; +use crate::lang_struct::anilist::register::load_localization_register; +use crate::sqls::general::data::set_registered_user; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let option = &options.get(0).ok_or(OPTION_ERROR.clone())?.value; + + let value = match option { + CommandDataOptionValue::String(lang) => lang, + _ => { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + }; + + let data: UserWrapper = if value.parse::<i32>().is_ok() { + UserWrapper::new_user_by_id(value.parse().unwrap()).await? + } else { + UserWrapper::new_user_by_search(value).await? + }; + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let register_localised = load_localization_register(guild_id).await?; + + let user_data = data.data.user.clone(); + + let user_id = &command.user.id.to_string(); + let username = &command.user.name; + + set_registered_user(user_id, &user_data.id.unwrap_or(0).to_string()).await?; + + let desc = register_localised + .desc + .replace("$user$", username.as_str()) + .replace("$id$", user_id) + .replace( + "$anilist$", + user_data.name.clone().unwrap_or(String::new()).as_str(), + ); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(get_color(user_data.clone())) + .title(user_data.name.unwrap_or(String::new())) + .url(get_user_url(&user_data.id.unwrap_or(0))) + .thumbnail(user_data.avatar.large.unwrap()) + .description(desc); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/anilist/search.rs b/src/command_run/anilist/search.rs new file mode 100644 index 00000000..1905fd03 --- /dev/null +++ b/src/command_run/anilist/search.rs @@ -0,0 +1,29 @@ +use serenity::all::{CommandDataOption, CommandInteraction, Context}; + +use crate::command_run::anilist::{anime, character, ln, manga, staff, studio, user}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NotAValidTypeError; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut search_type = String::new(); + + for option in options { + if option.name.as_str() == "type" { + search_type = option.value.as_str().unwrap().to_string() + } + } + match search_type.as_str() { + "anime" => anime::run(options, ctx, command).await, + "character" => character::run(options, ctx, command).await, + "ln" => ln::run(options, ctx, command).await, + "manga" => manga::run(options, ctx, command).await, + "staff" => staff::run(options, ctx, command).await, + "user" => user::run(options, ctx, command).await, + "studio" => studio::run(options, ctx, command).await, + _ => Err(NotAValidTypeError(String::from("Invalid type"))), + } +} diff --git a/src/command_run/anilist/seiyuu.rs b/src/command_run/anilist/seiyuu.rs new file mode 100644 index 00000000..a0327b80 --- /dev/null +++ b/src/command_run/anilist/seiyuu.rs @@ -0,0 +1,205 @@ +use std::fs; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; + +use image::imageops::FilterType; +use image::{DynamicImage, GenericImage, GenericImageView}; +use serenity::all::CreateInteractionResponse::Defer; +use serenity::all::{ + CommandDataOption, CommandInteraction, Context, CreateAttachment, CreateEmbed, + CreateInteractionResponseFollowup, CreateInteractionResponseMessage, Timestamp, +}; +use tracing::{debug, error, trace}; +use uuid::Uuid; + +use crate::anilist_struct::run::seiyuu::{StaffImageNodes, StaffImageWrapper}; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, DIFFERED_COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + DifferedCreatingImageError, DifferedFailedToGetBytes, DifferedFailedUrlError, + DifferedReadingFileError, DifferedWritingFile, +}; +use crate::lang_struct::anilist::seiyuu::load_localization_seiyuu; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut value = String::new(); + for option_data in options { + if option_data.name.as_str() != "type" { + let option_value = option_data.value.as_str().clone().unwrap(); + value = option_value.to_string().clone() + } + } + + let data = if value.parse::<i32>().is_ok() { + StaffImageWrapper::new_staff_by_id(value.parse().unwrap()).await? + } else { + StaffImageWrapper::new_staff_by_search(&value).await? + }; + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let seiyuu_localised = load_localization_seiyuu(guild_id).await?; + + let builder_message = Defer(CreateInteractionResponseMessage::new()); + + command + .create_response(&ctx.http, builder_message) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone())?; + + let mut uuids: Vec<Uuid> = Vec::new(); + for _ in 0..5 { + let uuid = Uuid::new_v4(); + uuids.push(uuid); + } + + let url = get_staff_image(data.clone()); + let response = reqwest::get(url) + .await + .map_err(|_| DifferedFailedUrlError(String::from("failed to get the image.")))?; + let bytes = response.bytes().await.map_err(|_| { + DifferedFailedToGetBytes(String::from("Failed to get bytes data from response.")) + })?; + let mut buffer = File::create(format!("{}.png", uuids[0])) + .map_err(|_| DifferedWritingFile(String::from("Failed to write the file bytes.")))?; + buffer + .write_all(&bytes) + .map_err(|_| DifferedWritingFile(String::from("Failed to write the file bytes.")))?; + let mut i = 1; + let characters_images_url = get_characters_image(data.clone()); + for character_image in characters_images_url { + let response = reqwest::get(&character_image.image.large) + .await + .map_err(|_| DifferedFailedUrlError(String::from("failed to get the image.")))?; + let bytes = response.bytes().await.map_err(|_| { + DifferedFailedToGetBytes(String::from("Failed to get bytes data from response.")) + })?; + let mut buffer = File::create(format!("{}.png", uuids[i])) + .map_err(|_| DifferedWritingFile(String::from("Failed to write the file bytes.")))?; + buffer + .write_all(&bytes) + .map_err(|_| DifferedWritingFile(String::from("Failed to write the file bytes.")))?; + i += 1 + } + + let mut images: Vec<DynamicImage> = Vec::new(); + for uuid in &uuids { + let path = format!("{}.png", uuid); + let img_path = Path::new(&path); + // Read the image file into a byte vector + let mut file = match File::open(img_path) { + Ok(f) => f, + Err(e) => { + error!("{}", e); + continue; + } + }; + + let mut buffer = Vec::new(); + match file.read_to_end(&mut buffer) { + Ok(f) => f, + Err(e) => { + error!("{}", e); + continue; + } + }; + + // Load the image from the byte vector + images.push(image::load_from_memory(&buffer).map_err(|_| { + DifferedCreatingImageError(String::from("Failed to create the image from the file.")) + })?); + } + + let (width, height) = images[0].dimensions(); + let sub_image = images[0].to_owned().crop(0, 0, width, height); + let aspect_ratio = width as f32 / height as f32; + let new_height = 2000; + let new_width = (new_height as f32 * aspect_ratio) as u32; + + let smaller_height = new_height / 2; + let smaller_width = new_width / 2; + + let total_width = smaller_width * 2 + new_width; + + let mut combined_image = DynamicImage::new_rgba16(total_width, 2000); + + let resized_img = + image::imageops::resize(&sub_image, new_width, new_height, FilterType::CatmullRom); + combined_image.copy_from(&resized_img, 0, 0).unwrap(); + let pos_list = [ + (new_width, 0), + (new_width + smaller_width, 0), + (new_width, smaller_height), + (new_width + smaller_width, smaller_height), + ]; + images.remove(0); + for (i, img) in images.iter().enumerate() { + let (width, height) = img.dimensions(); + let sub_image = img.to_owned().crop(0, 0, width, height); + let resized_img = image::imageops::resize( + &sub_image, + smaller_width, + smaller_height, + FilterType::CatmullRom, + ); + let (pos_width, pos_height) = pos_list[i]; + combined_image + .copy_from(&resized_img, pos_width, pos_height) + .map_err(|_| { + DifferedCreatingImageError(String::from( + "Failed to create the image from the file.", + )) + })?; + } + + let combined_uuid = Uuid::new_v4(); + combined_image + .save(format!("{}.png", combined_uuid)) + .map_err(|_| DifferedWritingFile(String::from("Failed to write the file bytes.")))?; + uuids.push(combined_uuid); + let image_path = &format!("{}.png", combined_uuid); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .image(format!("attachment://{}", &image_path)) + .title(&seiyuu_localised.title); + + let attachement = CreateAttachment::path(&image_path) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + let builder_message = CreateInteractionResponseFollowup::new() + .embed(builder_embed) + .files(vec![attachement]); + + command + .create_followup(&ctx.http, builder_message) + .await + .map_err(|_| DIFFERED_COMMAND_SENDING_ERROR.clone())?; + + for uuid in uuids { + let path = format!("{}.png", uuid); + match fs::remove_file(path) { + Ok(_) => debug!("File {} has been removed successfully", uuid), + Err(e) => error!("Failed to remove file {}: {}", uuid, e), + } + } + Ok(()) +} + +fn get_characters_image(staff: StaffImageWrapper) -> Vec<StaffImageNodes> { + staff.data.staff.characters.nodes +} + +fn get_staff_image(staff: StaffImageWrapper) -> String { + staff.data.staff.image.large +} diff --git a/src/command_run/anilist/staff.rs b/src/command_run/anilist/staff.rs new file mode 100644 index 00000000..914cbd6c --- /dev/null +++ b/src/command_run/anilist/staff.rs @@ -0,0 +1,179 @@ +use serenity::all::{ + CommandDataOption, CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; + +use crate::anilist_struct::run::staff::StaffWrapper; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::anilist::staff::load_localization_staff; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut value = String::new(); + for option_data in options { + if option_data.name.as_str() != "type" { + let option_value = option_data.value.as_str().clone().unwrap(); + value = option_value.to_string().clone() + } + } + + let data: StaffWrapper = if value.parse::<i32>().is_ok() { + StaffWrapper::new_staff_by_id(value.parse().unwrap()).await? + } else { + StaffWrapper::new_staff_by_search(&value).await? + }; + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let staff_localised = load_localization_staff(guild_id).await?; + let staff = data.data.staff.clone(); + + let mut date = String::new(); + let mut day = false; + let mut month = false; + + match staff.date_of_birth.month { + Some(m) => { + month = true; + date.push_str(m.to_string().as_str()) + } + None => {} + } + match staff.date_of_birth.day { + Some(d) => { + day = true; + if month { + date.push_str("/") + } + date.push_str(d.to_string().as_str()) + } + None => {} + } + match staff.date_of_birth.year { + Some(y) => { + if day { + date.push_str("/") + } + date.push_str(y.to_string().as_str()) + } + None => {} + } + let dob = staff_localised + .date_of_birth + .replace("$date$", date.as_str()); + + let mut date = String::new(); + let mut day = false; + let mut month = false; + + match staff.date_of_death.month { + Some(m) => { + month = true; + date.push_str(m.to_string().as_str()) + } + None => {} + } + match staff.date_of_death.day { + Some(d) => { + day = true; + if month { + date.push_str("/") + } + date.push_str(d.to_string().as_str()) + } + None => {} + } + match staff.date_of_death.year { + Some(y) => { + if day { + date.push_str("/") + } + date.push_str(y.to_string().as_str()) + } + None => {} + } + let dod = staff_localised + .date_of_death + .replace("$date$", date.as_str()); + let desc = staff_localised + .desc + .replace("$dob$", dob.as_str()) + .replace("$dod$", dod.as_str()) + .replace("$job$", staff.primary_occupations[0].as_str()) + .replace( + "$gender$", + staff + .gender + .clone() + .unwrap_or(String::from("Unknown.")) + .as_str(), + ) + .replace("$age$", staff.age.unwrap_or(0).to_string().as_str()); + + let name = staff + .name + .full + .clone() + .unwrap_or(staff.name.native.clone().unwrap()); + + let va = staff + .characters + .nodes + .iter() + .filter_map(|x| { + let full = x.name.full.as_ref().map(|s| s.as_str()); + let native = x.name.native.as_ref().map(|s| s.as_str()); + match (full, native) { + (Some(full), Some(native)) => Some(format!("{}/{}", full, native)), + (Some(full), None) => Some(full.to_string()), + (None, Some(native)) => Some(native.to_string()), + (None, None) => None, + } + }) + .take(5) + .collect::<Vec<String>>() + .join("\n"); + + let media = staff + .staff_media + .edges + .iter() + .filter_map(|x| { + let romaji = x.node.title.romaji.as_ref().map(|s| s.as_str()); + let english = x.node.title.english.as_ref().map(|s| s.as_str()); + match (romaji, english) { + (Some(romaji), Some(english)) => Some(format!("{}/{}", romaji, english)), + (Some(romaji), None) => Some(romaji.to_string()), + (None, Some(english)) => Some(english.to_string()), + (None, None) => None, + } + }) + .take(5) + .collect::<Vec<String>>() + .join("\n"); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(desc) + .title(name) + .url(staff.site_url) + .thumbnail(staff.image.large) + .field(&staff_localised.field2_title, va, true) + .field(&staff_localised.field1_title, media, true); + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/anilist/studio.rs b/src/command_run/anilist/studio.rs new file mode 100644 index 00000000..19879cdc --- /dev/null +++ b/src/command_run/anilist/studio.rs @@ -0,0 +1,74 @@ +use serenity::all::{ + CommandDataOption, CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; + +use crate::anilist_struct::run::studio::StudioWrapper; +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::anilist::studio::load_localization_studio; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let mut value = String::new(); + for option_data in options { + if option_data.name.as_str() != "type" { + let option_value = option_data.value.as_str().clone().unwrap(); + value = option_value.to_string().clone() + } + } + + let data: StudioWrapper = if value.parse::<i32>().is_ok() { + StudioWrapper::new_studio_by_id(value.parse().unwrap()).await? + } else { + StudioWrapper::new_studio_by_search(&value).await? + }; + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let studio = data.data.studio.clone(); + let studio_localised = load_localization_studio(guild_id).await?; + + let mut content = String::new(); + for m in studio.media.nodes { + let title = m.title.clone(); + let rj = title.romaji; + let en = title.user_preferred; + let text = format!("[{}/{}]({})", rj, en, m.site_url); + content.push_str(text.as_str()); + content.push_str("\n"); + } + + let desc = studio_localised + .desc + .replace("$id$", studio.id.to_string().as_str()) + .replace("$fav$", studio.favourites.to_string().as_str()) + .replace( + "$animation$", + studio.is_animation_studio.to_string().as_str(), + ) + .replace("$list$", content.as_str()); + + let name = studio.name; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(desc) + .title(name) + .url(studio.site_url); + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/anilist/user.rs b/src/command_run/anilist/user.rs new file mode 100644 index 00000000..288096fd --- /dev/null +++ b/src/command_run/anilist/user.rs @@ -0,0 +1,41 @@ +use serenity::all::{CommandDataOption, CommandInteraction, Context}; +use tracing::trace; + +use crate::anilist_struct::run::user::{send_embed, UserWrapper}; +use crate::constant::OPTION_ERROR; +use crate::error_enum::AppError; +use crate::sqls::general::data::get_registered_user; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + trace!("{:?}", options); + for option in options { + if option.name.as_str() != "type" { + match option.value.as_str() { + Some(a) => { + let value = &a.to_string(); + + let data: UserWrapper = if value.parse::<i32>().is_ok() { + UserWrapper::new_user_by_id(value.parse().unwrap()).await? + } else { + UserWrapper::new_user_by_search(value).await? + }; + + return send_embed(ctx, command, data).await; + } + + None => {} + } + } + } + let user_id = &command.user.id.to_string(); + let row: (Option<String>, Option<String>) = get_registered_user(user_id).await?; + trace!("{:?}", row); + let (user, _): (Option<String>, Option<String>) = row; + let user = user.ok_or(OPTION_ERROR.clone())?; + let data = UserWrapper::new_user_by_id((&user).parse::<i32>().unwrap()).await?; + return send_embed(ctx, command, data).await; +} diff --git a/src/command_run/anilist/waifu.rs b/src/command_run/anilist/waifu.rs new file mode 100644 index 00000000..6c240694 --- /dev/null +++ b/src/command_run/anilist/waifu.rs @@ -0,0 +1,10 @@ +use serenity::all::{CommandInteraction, Context}; + +use crate::anilist_struct::run::character::{send_embed, CharacterWrapper}; +use crate::error_enum::AppError; + +pub async fn run(ctx: &Context, command: &CommandInteraction) -> Result<(), AppError> { + let data: CharacterWrapper = CharacterWrapper::new_character_by_id(156323).await?; + + send_embed(ctx, command, data).await +} diff --git a/src/command_run/command_dispatch.rs b/src/command_run/command_dispatch.rs new file mode 100644 index 00000000..6935b463 --- /dev/null +++ b/src/command_run/command_dispatch.rs @@ -0,0 +1,207 @@ +use serenity::all::{CommandInteraction, Context}; + +use crate::command_run::ai::{image, transcript, translation}; +use crate::command_run::anilist::{ + add_activity, anime, character, compare, level, ln, manga, random, register, search, seiyuu, + staff, studio, user, waifu, +}; +use crate::command_run::general::module::check_activation_status; +use crate::command_run::general::{avatar, banner, credit, info, lang, module, ping, profile}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::UnknownCommandError; +use crate::sqls::general::data::get_data_module_activation_kill_switch_status; + +pub async fn command_dispatching( + ctx: Context, + command: CommandInteraction, +) -> Result<(), AppError> { + let ai_module_error = AppError::ModuleOffError(String::from("AI module is off.")); + let anilist_module_error = AppError::ModuleOffError(String::from("Anilist module is off.")); + match command.data.name.as_str() { + /* + + THIS IS THE GENERAL MODULE. + + */ + "avatar" => avatar::run(&command.data.options, &ctx, &command).await?, + "banner" => banner::run(&command.data.options, &ctx, &command).await?, + "credit" => credit::run(&ctx, &command).await?, + "info" => info::run(&ctx, &command).await?, + "lang_struct" => lang::run(&command.data.options, &ctx, &command).await?, + "module" => module::run(&command.data.options, &ctx, &command).await?, + "ping" => ping::run(&ctx, &command).await?, + "profile" => profile::run(&command.data.options, &ctx, &command).await?, + + /* + + THIS IS THE AI MODULE. + + */ + "image" => { + if check_if_ai_moule_is_on(&command).await? { + image::run(&command.data.options, &ctx, &command).await? + } else { + return Err(ai_module_error); + } + } + "transcript" => { + if check_if_ai_moule_is_on(&command).await? { + transcript::run(&command.data.options(), &ctx, &command).await? + } else { + return Err(ai_module_error); + } + } + "translation" => { + if check_if_ai_moule_is_on(&command).await? { + translation::run(&command.data.options(), &ctx, &command).await? + } else { + return Err(ai_module_error); + } + } + + /* + + THIS IS THE ANILIST MODULE. + + */ + "anime" => { + if check_if_anilist_moule_is_on(&command).await? { + anime::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "ln" => { + if check_if_anilist_moule_is_on(&command).await? { + ln::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "manga" => { + if check_if_anilist_moule_is_on(&command).await? { + manga::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "add_activity" => { + if check_if_anilist_moule_is_on(&command).await? { + add_activity::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "user" => { + if check_if_anilist_moule_is_on(&command).await? { + user::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "character" => { + if check_if_anilist_moule_is_on(&command).await? { + character::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "waifu" => { + if check_if_anilist_moule_is_on(&command).await? { + waifu::run(&ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "compare" => { + if check_if_anilist_moule_is_on(&command).await? { + compare::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "random" => { + if check_if_anilist_moule_is_on(&command).await? { + random::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "register" => { + if check_if_anilist_moule_is_on(&command).await? { + register::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "staff" => { + if check_if_anilist_moule_is_on(&command).await? { + staff::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "studio" => { + if check_if_anilist_moule_is_on(&command).await? { + studio::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "search" => { + if check_if_anilist_moule_is_on(&command).await? { + search::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "seiyuu" => { + if check_if_anilist_moule_is_on(&command).await? { + seiyuu::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + "level" => { + if check_if_anilist_moule_is_on(&command).await? { + level::run(&command.data.options, &ctx, &command).await? + } else { + return Err(anilist_module_error); + } + } + _ => return Err(UnknownCommandError(String::from("Command does not exist."))), + } + + Ok(()) +} + +async fn check_if_ai_moule_is_on(command: &CommandInteraction) -> Result<bool, AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => return Ok(true), + }; + let state = check_activation_status("AI", guild_id.clone()).await?; + let state = state && check_kill_switch_status("AI").await?; + Ok(state) +} + +async fn check_kill_switch_status(module: &str) -> Result<bool, AppError> { + let row: (Option<String>, Option<bool>, Option<bool>) = + get_data_module_activation_kill_switch_status().await?; + let (_, ai_module, anilist_module): (Option<String>, Option<bool>, Option<bool>) = row; + Ok(match module { + "ANILIST" => anilist_module.unwrap_or(true), + "AI" => ai_module.unwrap_or(true), + _ => false, + }) +} + +async fn check_if_anilist_moule_is_on(command: &CommandInteraction) -> Result<bool, AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => return Ok(true), + }; + let state = check_activation_status("ANILIST", guild_id.clone()).await?; + let state = state && check_kill_switch_status("ANILIST").await?; + Ok(state) +} diff --git a/src/command_run/general/avatar.rs b/src/command_run/general/avatar.rs new file mode 100644 index 00000000..f916e52b --- /dev/null +++ b/src/command_run/general/avatar.rs @@ -0,0 +1,71 @@ +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, User, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::FailedToGetUser; +use crate::lang_struct::general::avatar::load_localization_avatar; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + if let Some(option) = options.get(0) { + let resolved = &option.value; + if let CommandDataOptionValue::User(user, ..) = resolved { + let user = user + .to_user(&ctx.http) + .await + .map_err(|_| FailedToGetUser(String::from("Could not get the user.")))?; + return avatar_with_user(ctx, command, &user).await; + } + } + avatar_without_user(ctx, command).await +} + +async fn avatar_without_user(ctx: &Context, command: &CommandInteraction) -> Result<(), AppError> { + let user = command.user.clone(); + avatar_with_user(ctx, command, &user).await +} + +async fn avatar_with_user( + ctx: &Context, + command: &CommandInteraction, + user: &User, +) -> Result<(), AppError> { + let avatar_url = user.avatar_url().ok_or(OPTION_ERROR.clone())?; + + send_embed(avatar_url, ctx, command, user.name.clone()).await +} + +pub async fn send_embed( + avatar_url: String, + ctx: &Context, + command: &CommandInteraction, + username: String, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let avatar_localised = load_localization_avatar(guild_id).await?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .image(avatar_url) + .title(avatar_localised.title.replace("$user$", username.as_str())); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/general/banner.rs b/src/command_run/general/banner.rs new file mode 100644 index 00000000..810ab115 --- /dev/null +++ b/src/command_run/general/banner.rs @@ -0,0 +1,103 @@ +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, User, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::FailedToGetUser; +use crate::lang_struct::general::banner::load_localization_banner; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + if let Some(option) = options.get(0) { + let resolved = &option.value; + if let CommandDataOptionValue::User(user, ..) = resolved { + let user = user + .to_user(&ctx.http) + .await + .map_err(|_| FailedToGetUser(String::from("Failed to get the user.")))?; + return banner_with_user(ctx, command, &user).await; + } + } + banner_without_user(ctx, command).await +} + +pub async fn no_banner( + ctx: &Context, + command: &CommandInteraction, + username: &str, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + let banner_localised = load_localization_banner(guild_id).await?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(banner_localised.no_banner.replace("$user$", username)) + .title(&banner_localised.no_banner_title); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} + +pub async fn banner_without_user( + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let user = &command.user; + + banner_with_user(ctx, command, user).await +} + +pub async fn banner_with_user( + ctx: &Context, + command: &CommandInteraction, + user_data: &User, +) -> Result<(), AppError> { + let user = user_data; + let banner_url = match user.banner_url() { + Some(banner) => banner, + None => return no_banner(ctx, command, &user.name).await, + }; + send_embed(ctx, command, banner_url).await +} + +pub async fn send_embed( + ctx: &Context, + command: &CommandInteraction, + banner: String, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + let banner_localised = load_localization_banner(guild_id).await?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .image(banner) + .title(&banner_localised.no_banner_title); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/general/credit.rs b/src/command_run/general/credit.rs new file mode 100644 index 00000000..9e201e22 --- /dev/null +++ b/src/command_run/general/credit.rs @@ -0,0 +1,35 @@ +use serenity::all::{ + CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::general::credit::load_localization_credit; + +pub async fn run(ctx: &Context, command: &CommandInteraction) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + let credit_localised = load_localization_credit(guild_id).await?; + let mut desc: String = "".to_string(); + for x in credit_localised.credits { + desc += x.desc.as_str() + } + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(desc) + .title(&credit_localised.title); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/general/info.rs b/src/command_run/general/info.rs new file mode 100644 index 00000000..3ba7e055 --- /dev/null +++ b/src/command_run/general/info.rs @@ -0,0 +1,54 @@ +use serenity::all::{ + ButtonStyle, CommandInteraction, Context, CreateActionRow, CreateButton, CreateEmbed, + CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::general::info::load_localization_info; + +pub async fn run(ctx: &Context, command: &CommandInteraction) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + let info_localised = load_localization_info(guild_id).await?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(&info_localised.desc) + .title(&info_localised.title) + .footer(CreateEmbedFooter::new(&info_localised.footer)); + let mut buttons = Vec::new(); + let mut components = Vec::new(); + + let button = CreateButton::new_link("https://github.com/ValgulNecron/kasuki") + .style(ButtonStyle::Primary) + .label(&info_localised.button_see_on_github); + buttons.push(button); + let button = CreateButton::new_link("https://kasuki.valgul.moe/") + .style(ButtonStyle::Primary) + .label(&info_localised.button_official_website); + buttons.push(button); + let button = CreateButton::new_link("https://discord.gg/h4hYxMURQx") + .style(ButtonStyle::Primary) + .label(&info_localised.button_official_discord); + buttons.push(button); + let button = CreateButton::new_link("https://discord.com/api/oauth2/authorize?client_id=923286536445894697&permissions=533113194560&scope=bot") + .style(ButtonStyle::Primary) + .label(&info_localised.button_add_the_bot); + buttons.push(button); + components.push(CreateActionRow::Buttons(buttons)); + + let builder_message = CreateInteractionResponseMessage::new() + .embed(builder_embed) + .components(components); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/general/lang.rs b/src/command_run/general/lang.rs new file mode 100644 index 00000000..e0197502 --- /dev/null +++ b/src/command_run/general/lang.rs @@ -0,0 +1,50 @@ +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::NoCommandOption; +use crate::lang_struct::general::lang::load_localization_lang; +use crate::sqls::general::data::set_data_guild_langage; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let lang = options.get(0).ok_or(OPTION_ERROR.clone())?; + let lang = lang.value.clone(); + + let lang = match lang { + CommandDataOptionValue::String(lang) => lang, + _ => { + return Err(NoCommandOption(String::from( + "The command contain no option.", + ))); + } + }; + + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + let _ = set_data_guild_langage(&guild_id, &lang).await; + let lang_localised = load_localization_lang(guild_id).await?; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(lang_localised.desc.replace("$lang$", lang.as_str())) + .title(&lang_localised.title); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/cmd/general_module/mod.rs b/src/command_run/general/mod.rs similarity index 69% rename from src/cmd/general_module/mod.rs rename to src/command_run/general/mod.rs index b5aaa846..345f44ff 100644 --- a/src/cmd/general_module/mod.rs +++ b/src/command_run/general/mod.rs @@ -3,7 +3,6 @@ pub mod banner; pub mod credit; pub mod info; pub mod lang; -pub mod lang_struct; -pub mod module_activation; +pub mod module; pub mod ping; pub mod profile; diff --git a/src/command_run/general/module.rs b/src/command_run/general/module.rs new file mode 100644 index 00000000..147e68a0 --- /dev/null +++ b/src/command_run/general/module.rs @@ -0,0 +1,107 @@ +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::general::module::load_localization_module_activation; +use crate::sqls::general::data::{ + get_data_module_activation_status, set_data_module_activation_status, +}; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + let module_localised = load_localization_module_activation(guild_id.clone()).await?; + let mut module = "".to_string(); + let mut state = false; + for option in options { + if option.name == "module_name" { + let resolved = &option.value; + if let CommandDataOptionValue::String(module_option) = resolved { + module = module_option.clone() + } else { + module = "".to_string(); + } + } + if option.name == "state" { + let resolved = &option.value; + if let CommandDataOptionValue::Boolean(state_option) = resolved { + state = *state_option + } else { + state = false + } + } + } + + let desc; + match module.as_str() { + "ANIME" => { + let row = get_data_module_activation_status(&guild_id).await?; + let (_, ai_module, _): (Option<String>, Option<bool>, Option<bool>) = row; + + let ai_value = ai_module.unwrap_or(false); + + set_data_module_activation_status(&guild_id, state, ai_value).await?; + + desc = if state { + &module_localised.on + } else { + &module_localised.off + }; + } + "AI" => { + let row = get_data_module_activation_status(&guild_id).await?; + let (_, _, anilist_module): (Option<String>, Option<bool>, Option<bool>) = row; + + let anilist_value = anilist_module.unwrap_or(false); + + set_data_module_activation_status(&guild_id, anilist_value, state).await?; + + desc = if state { + &module_localised.on + } else { + &module_localised.off + }; + } + _ => { + return Err(AppError::ModuleError(String::from( + "This module does not exist.", + ))); + } + } + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description(desc) + .title(module); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} + +pub async fn check_activation_status(module: &str, guild_id: String) -> Result<bool, AppError> { + let row: (Option<String>, Option<bool>, Option<bool>) = + get_data_module_activation_status(&guild_id).await?; + + let (_, ai_module, anilist_module): (Option<String>, Option<bool>, Option<bool>) = row; + Ok(match module { + "ANILIST" => anilist_module.unwrap_or(true), + "AI" => ai_module.unwrap_or(true), + _ => false, + }) +} diff --git a/src/command_run/general/ping.rs b/src/command_run/general/ping.rs new file mode 100644 index 00000000..ccbcfea0 --- /dev/null +++ b/src/command_run/general/ping.rs @@ -0,0 +1,60 @@ +use serenity::all::{ + CommandInteraction, Context, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, Timestamp, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::lang_struct::general::ping::load_localization_ping; +use crate::struct_shard_manager::ShardManagerContainer; + +pub async fn run(ctx: &Context, command: &CommandInteraction) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + let ping_localised = load_localization_ping(guild_id).await?; + let data_read = ctx.data.read().await; + let shard_manager = match data_read.get::<ShardManagerContainer>() { + Some(data) => data, + None => return Err(OPTION_ERROR.clone()), + } + .runners + .clone(); + let shard_manager = shard_manager.lock().await; + + let shard_id = ctx.shard_id; + + let shard_runner_info = match shard_manager.get(&shard_id) { + Some(data) => data, + None => return Err(OPTION_ERROR.clone()), + }; + + let latency = match shard_runner_info.latency { + Some(latency) => format!("{:.2}ms", latency.as_millis()), + None => "?,??ms".to_string(), + }; + + let tx_status = &shard_runner_info.stage.to_string(); + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .description( + ping_localised + .desc + .replace("$shard$", shard_id.to_string().as_str()) + .replace("$latency$", latency.as_str()) + .replace("$status$", tx_status), + ) + .title(&ping_localised.title); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/command_run/general/profile.rs b/src/command_run/general/profile.rs new file mode 100644 index 00000000..18b16afa --- /dev/null +++ b/src/command_run/general/profile.rs @@ -0,0 +1,107 @@ +use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, Context, CreateEmbed, + CreateInteractionResponse, CreateInteractionResponseMessage, Timestamp, User, +}; + +use crate::constant::{COLOR, COMMAND_SENDING_ERROR, OPTION_ERROR}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::FailedToGetUser; +use crate::lang_struct::general::profile::load_localization_profile; + +pub async fn run( + options: &[CommandDataOption], + ctx: &Context, + command: &CommandInteraction, +) -> Result<(), AppError> { + if let Some(option) = options.get(0) { + let resolved = &option.value; + if let CommandDataOptionValue::User(user, ..) = resolved { + let user = user + .to_user(&ctx.http) + .await + .map_err(|_| FailedToGetUser(String::from("Could not get the user.")))?; + return profile_with_user(ctx, command, &user).await; + } + } + profile_without_user(ctx, command).await +} + +async fn profile_without_user(ctx: &Context, command: &CommandInteraction) -> Result<(), AppError> { + let user = command.user.clone(); + profile_with_user(ctx, command, &user).await +} + +async fn profile_with_user( + ctx: &Context, + command: &CommandInteraction, + user: &User, +) -> Result<(), AppError> { + let avatar_url = user.avatar_url().ok_or(OPTION_ERROR.clone())?; + + send_embed(avatar_url, ctx, command, user).await +} + +pub async fn send_embed( + avatar_url: String, + ctx: &Context, + command: &CommandInteraction, + user: &User, +) -> Result<(), AppError> { + let guild_id = match command.guild_id { + Some(id) => id.to_string(), + None => String::from("0"), + }; + + let profile_localised = load_localization_profile(guild_id).await?; + + let member = &command.member.clone().ok_or(OPTION_ERROR.clone())?; + + let public_flag = match user.public_flags { + Some(public_flag) => { + let mut user_flags = Vec::new(); + for (flag, _) in public_flag.iter_names() { + user_flags.push(flag); + } + user_flags.join(" / ") + } + None => "None".to_string(), + }; + + let builder_embed = CreateEmbed::new() + .timestamp(Timestamp::now()) + .color(COLOR) + .thumbnail(avatar_url) + .title( + profile_localised + .title + .replace("$user$", user.name.as_str()), + ) + .description( + profile_localised + .desc + .replace("$user$", user.name.as_str()) + .replace("$id$", user.id.to_string().as_str()) + .replace("$creation_date$", user.created_at().to_string().as_str()) + .replace( + "$joined_date$", + member + .joined_at + .ok_or(OPTION_ERROR.clone())? + .to_string() + .as_str(), + ) + .replace("$bot$", user.bot.to_string().as_str()) + .replace("$public_flag$", public_flag.as_str()) + .replace("$nitro$", format!("{:?}", user.premium_type).as_str()) + .replace("$system$", user.system.to_string().as_str()), + ); + + let builder_message = CreateInteractionResponseMessage::new().embed(builder_embed); + + let builder = CreateInteractionResponse::Message(builder_message); + + command + .create_response(&ctx.http, builder) + .await + .map_err(|_| COMMAND_SENDING_ERROR.clone()) +} diff --git a/src/structure/embed/mod.rs b/src/command_run/mod.rs similarity index 63% rename from src/structure/embed/mod.rs rename to src/command_run/mod.rs index 8921ff42..6d64a90c 100644 --- a/src/structure/embed/mod.rs +++ b/src/command_run/mod.rs @@ -1,4 +1,4 @@ pub mod ai; pub mod anilist; -pub mod error; +pub mod command_dispatch; pub mod general; diff --git a/src/common/get_guild_lang.rs b/src/common/get_guild_lang.rs new file mode 100644 index 00000000..a5a44873 --- /dev/null +++ b/src/common/get_guild_lang.rs @@ -0,0 +1,12 @@ +use crate::sqls::general::data::get_data_guild_lang; + +pub async fn get_guild_langage(guild_id: String) -> String { + if guild_id == String::from("0") { + return String::from("en"); + }; + + let (lang, _): (Option<String>, Option<String>) = + get_data_guild_lang(guild_id).await.unwrap_or((None, None)); + + lang.unwrap_or("en".to_string()) +} diff --git a/src/common/get_nsfw.rs b/src/common/get_nsfw.rs new file mode 100644 index 00000000..b96d26b5 --- /dev/null +++ b/src/common/get_nsfw.rs @@ -0,0 +1,7 @@ +use serenity::all::{ChannelId, CommandInteraction, Context}; + +pub async fn get_nsfw(command: &CommandInteraction, ctx: &Context) -> bool { + let channel_id: ChannelId = command.channel_id; + let channel = channel_id.to_channel(&ctx.http).await.unwrap(); + channel.is_nsfw() +} diff --git a/src/function/general/html_parser.rs b/src/common/html_parser.rs similarity index 100% rename from src/function/general/html_parser.rs rename to src/common/html_parser.rs diff --git a/src/common/make_anilist_request.rs b/src/common/make_anilist_request.rs new file mode 100644 index 00000000..bff1785b --- /dev/null +++ b/src/common/make_anilist_request.rs @@ -0,0 +1,58 @@ +use chrono::Utc; +use reqwest::Client; +use serde_json::Value; + +use crate::constant::DAYS; +use crate::sqls::general::cache::{get_database_cache, set_database_cache}; + +pub async fn make_request_anilist(json: Value, always_update: bool) -> String { + if always_update { + do_request(json, always_update).await + } else { + get_cache(json.clone()).await + } +} + +async fn get_cache(json: Value) -> String { + let (json_resp, response, last_updated): (Option<String>, Option<String>, Option<i64>) = + get_database_cache(json.clone()) + .await + .unwrap_or((None, None, None)); + + if json_resp.is_none() || response.is_none() || last_updated.is_none() { + do_request(json.clone(), false).await + } else { + let updated_at = last_updated.unwrap(); + let duration_since_updated = Utc::now().timestamp() - updated_at; + if duration_since_updated < (DAYS * 24 * 60 * 60) { + response.unwrap() + } else { + do_request(json.clone(), false).await + } + } +} + +async fn add_cache(json: Value, resp: String) -> bool { + set_database_cache(json, resp).await.unwrap_or(()); + + true +} + +async fn do_request(json: Value, always_update: bool) -> String { + let client = Client::new(); + let res = client + .post("https://graphql.anilist.co/") + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .body(json.clone().to_string()) + .send() + .await + .unwrap() + .text() + .await; + let resp = res.unwrap(); + if !always_update { + add_cache(json.clone(), resp.clone()).await; + } + resp +} diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 00000000..a8218f0d --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,5 @@ +pub mod get_guild_lang; +pub mod get_nsfw; +pub mod html_parser; +pub mod make_anilist_request; +pub mod trimer; diff --git a/src/function/general/trim.rs b/src/common/trimer.rs similarity index 97% rename from src/function/general/trim.rs rename to src/common/trimer.rs index 7260214e..058d0278 100644 --- a/src/function/general/trim.rs +++ b/src/common/trimer.rs @@ -38,7 +38,7 @@ pub fn trim(desc: String, lenght_diff: i32) -> String { let count = desc_trim.matches("||").count(); if count % 2 != 0 { let trim_length = desc.len() - ((-lenght_diff) as usize + 5); - desc_trim = format!("{}||..", &desc[..trim_length]); + desc_trim = format!("{}||...", &desc[..trim_length]); } desc_trim.clone() } else { diff --git a/src/constant.rs b/src/constant.rs index 342fed7d..c5de2ee9 100644 --- a/src/constant.rs +++ b/src/constant.rs @@ -1,6 +1,32 @@ -use serenity::utils::Colour; +use once_cell::sync::Lazy; +use serenity::all::Colour; + +use crate::error_enum::AppError; pub const DAYS: i64 = 3; pub const ACTIVITY_NAME: &str = "Let you get info from anilist."; pub const COLOR: Colour = Colour::FABLED_PINK; -pub const N_A: &str = "N/A"; +pub const DATA_SQLITE_DB: &str = "./data.db"; +pub const CACHE_SQLITE_DB: &str = "./cache.db"; +pub const PING_UPDATE_DELAYS: u64 = 600; + +pub static OPTION_ERROR: Lazy<AppError> = + Lazy::new(|| AppError::OptionError(String::from("The option contain no value"))); +pub static COMMAND_SENDING_ERROR: Lazy<AppError> = Lazy::new(|| { + AppError::CommandSendingError(String::from( + "Error while sending the response of the command.", + )) +}); +pub static NO_AVATAR_ERROR: Lazy<AppError> = + Lazy::new(|| AppError::NoAvatarError(String::from("Error while getting the user avatar."))); +pub static DIFFERED_COMMAND_SENDING_ERROR: Lazy<AppError> = Lazy::new(|| { + AppError::DifferedCommandSendingError(String::from( + "Error while sending the response of the command.", + )) +}); +pub static DIFFERED_OPTION_ERROR: Lazy<AppError> = + Lazy::new(|| AppError::DifferedOptionError(String::from("The option contain no value"))); +pub static AUTOCOMPLETE_COUNT: u32 = 8; +pub static OTHER_CRATE_LEVEL: &str = "warn"; + +pub static UNKNOWN: &str = "Unknown"; diff --git a/src/error_enum.rs b/src/error_enum.rs new file mode 100644 index 00000000..bd309382 --- /dev/null +++ b/src/error_enum.rs @@ -0,0 +1,47 @@ +#[derive(Debug, Clone)] +pub enum AppError { + OptionError(String), + CommandSendingError(String), + LocalisationFileError(String), + LocalisationReadError(String), + LocalisationParsingError(String), + LangageGuildIdError(String), + NoLangageError(String), + FailedToGetUser(String), + NoAvatarError(String), + NoCommandOption(String), + SqlInsertError(String), + SqlSelectError(String), + SqlCreateError(String), + ModuleError(String), + ModuleOffError(String), + UnknownCommandError(String), + NoAnimeError(String), + NoAnimeDifferedError(String), + NoMediaDifferedError(String), + CreatingWebhookDifferedError(String), + CreatingPoolError(String), + FailedToCreateAFile(String), + NsfwError(String), + DifferedTokenError(String), + DifferedImageModelError(String), + DifferedHeaderError(String), + DifferedResponseError(String), + DifferedFailedUrlError(String), + DifferedOptionError(String), + DifferedGettingUrlResponse(String), + DifferedFailedToGetBytes(String), + DifferedWritingFile(String), + DifferedCommandSendingError(String), + SetLoggerError(String), + DifferedFileTypeError(String), + DifferedFileExtensionError(String), + DifferedCopyBytesError(String), + DifferedGettingBytesError(String), + MediaGettingError(String), + DifferedNotAiringError(String), + NoStatisticDifferedError(String), + NotAValidTypeError(String), + DifferedReadingFileError(String), + DifferedCreatingImageError(String), +} diff --git a/src/error_management/error_dispatch.rs b/src/error_management/error_dispatch.rs new file mode 100644 index 00000000..1838d537 --- /dev/null +++ b/src/error_management/error_dispatch.rs @@ -0,0 +1,11 @@ +use tracing::error; + +use crate::error_enum::AppError; + +pub async fn command_dispatching(error: AppError) { + match error { + _ => { + error!("{:?}", error) + } + } +} diff --git a/src/error_management/mod.rs b/src/error_management/mod.rs new file mode 100644 index 00000000..de174ce7 --- /dev/null +++ b/src/error_management/mod.rs @@ -0,0 +1 @@ +pub mod error_dispatch; diff --git a/src/function/anilist/command_media.rs b/src/function/anilist/command_media.rs deleted file mode 100644 index 2ac87a5a..00000000 --- a/src/function/anilist/command_media.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::constant::COLOR; -use crate::function::error_management::common::custom_error; -use crate::function::error_management::error_not_nsfw::error_not_nsfw; -use crate::function::general::get_nsfw_channel::get_nsfw; -use crate::structure::anilist::media::struct_media::MediaWrapper; -use crate::structure::embed::anilist::struct_lang_media::MediaLocalisedText; -use serenity::client::Context; -use serenity::model::application::interaction::application_command::CommandDataOptionValue; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::prelude::interaction::application_command::{ - ApplicationCommandInteraction, CommandDataOption, -}; -use serenity::model::Timestamp; - -pub async fn embed( - options: &[CommandDataOption], - ctx: &Context, - command: &ApplicationCommandInteraction, - search_type: &str, -) { - let option = options - .get(0) - .expect("Expected name option") - .resolved - .as_ref() - .expect("Expected name object"); - if let CommandDataOptionValue::String(value) = option { - let localised_text = match MediaLocalisedText::get_media_localised(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - let data: MediaWrapper; - if value.parse::<i32>().is_ok() { - if search_type == "NOVEL" { - data = - match MediaWrapper::new_ln_by_id(value.parse().unwrap(), localised_text.clone()) - .await - { - Ok(character_wrapper) => character_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - data = match MediaWrapper::new_manga_by_id( - value.parse().unwrap(), - localised_text.clone(), - ) - .await - { - Ok(character_wrapper) => character_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } - } else if search_type == "NOVEL" { - data = - match MediaWrapper::new_ln_by_search(value.parse().unwrap(), localised_text.clone()) - .await - { - Ok(character_wrapper) => character_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } else { - data = match MediaWrapper::new_manga_by_search( - value.parse().unwrap(), - localised_text.clone(), - ) - .await - { - Ok(character_wrapper) => character_wrapper, - Err(error) => { - custom_error(ctx, command, &error).await; - return; - } - } - } - - if data.get_nsfw() && !get_nsfw(command, ctx).await { - error_not_nsfw(ctx, command).await; - return; - } - - let banner_image = format!("https://img.anili.st/media/{}", data.data.media.id); - let desc = data.get_desc(); - let thumbnail = data.get_thumbnail(); - let site_url = data.get_url(); - let name = data.get_name(); - - let info = data.get_media_info(localised_text.clone()); - let genre = data.get_genres(); - let tag = data.get_tags(); - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(name) - .url(site_url) - .timestamp(Timestamp::now()) - .color(COLOR) - .description(desc) - .thumbnail(thumbnail) - .image(banner_image) - .field("Info", info, false) - .fields(vec![("Genre", genre, true), ("Tag", tag, true)]) - }) - }) - }) - .await - { - println!("Error creating slash command: {}", why); - } - } -} diff --git a/src/function/anilist/mod.rs b/src/function/anilist/mod.rs deleted file mode 100644 index 051b894e..00000000 --- a/src/function/anilist/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod command_media; diff --git a/src/function/error_management/common.rs b/src/function/error_management/common.rs deleted file mode 100644 index 282046a3..00000000 --- a/src/function/error_management/common.rs +++ /dev/null @@ -1,182 +0,0 @@ -use log::error; -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::constant::COLOR; -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_cant_read_langage_file_edit, error_langage_file_not_found, - error_langage_file_not_found_edit, error_no_langage_guild_id, error_no_langage_guild_id_edit, - error_parsing_langage_json, error_parsing_langage_json_edit, no_langage_error, - no_langage_error_edit, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use crate::structure::embed::error::ErrorLocalisedText; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::model::prelude::InteractionResponseType; -use serenity::model::Timestamp; - -pub async fn send_embed_message( - ctx: &Context, - command: &ApplicationCommandInteraction, - title: String, - desc: &str, -) { - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(&title) - .description(desc) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - }) - }) - .await - { - error!("Cannot respond to slash command: {}", why); - return; - } - error!("{} {}", title, desc) -} - -pub async fn edit_embed_message(ctx: &Context, mut message: Message, title: String, desc: &str) { - if let Err(why) = message - .edit(&ctx.http, |message| { - message.embed(|m| { - m.title(&title) - .description(desc) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - }) - .await - { - error!("Cannot respond to slash command: {}", why); - } - error!("{} {}", title, desc) -} - -pub async fn send_followup_embed_message( - ctx: &Context, - command: &ApplicationCommandInteraction, - title: String, - desc: &str, -) { - if let Err(why) = command - .create_followup_message(&ctx.http, |message| { - message.embed(|m| { - m.title(&title) - .description(desc) - .timestamp(Timestamp::now()) - .color(COLOR) - }) - }) - .await - { - error!("Cannot respond to slash command: {}", why); - } - error!("{} {}", title, desc) -} - -pub async fn get_localised_langage( - ctx: &Context, - command: &ApplicationCommandInteraction, -) -> Result<ErrorLocalisedText, &'static str> { - let mut file = match File::open("lang_file/embed/error.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, ErrorLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - }; -} - -pub async fn get_localised_langage_edit( - ctx: &Context, - message: Message, - command: &ApplicationCommandInteraction, -) -> Result<ErrorLocalisedText, &'static str> { - let mut file = match File::open("lang_file/embed/error.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found_edit(ctx, message.clone()).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file_edit(ctx, message.clone()).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, ErrorLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json_edit(ctx, message.clone()).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id_edit(ctx, message.clone()).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error_edit(ctx, message.clone()).await; - Err("not found") - } -} - -pub async fn custom_error(ctx: &Context, command: &ApplicationCommandInteraction, error: &str) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message(ctx, command, localised_text.error_title, error).await; -} diff --git a/src/function/error_management/error_avatar.rs b/src/function/error_management/error_avatar.rs deleted file mode 100644 index 3b54f1b6..00000000 --- a/src/function/error_management/error_avatar.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::function::error_management::common::{get_localised_langage, send_embed_message}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_no_avatar(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - println!("Error: Failed to fetch the discord user avatar."); - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.error_no_avatar.as_str(), - ) - .await; -} diff --git a/src/function/error_management/error_base_url.rs b/src/function/error_management/error_base_url.rs deleted file mode 100644 index 56f1b9eb..00000000 --- a/src/function/error_management/error_base_url.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_no_base_url_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.no_base_url.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_creating_header.rs b/src/function/error_management/error_creating_header.rs deleted file mode 100644 index ddbd6023..00000000 --- a/src/function/error_management/error_creating_header.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_creating_header_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.error_creating_header.as_str(), - ) - .await; -} diff --git a/src/function/error_management/error_file.rs b/src/function/error_management/error_file.rs deleted file mode 100644 index 84df1f49..00000000 --- a/src/function/error_management/error_file.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::function::error_management::common::{get_localised_langage, send_embed_message}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_file_type(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.error_file_type.as_str(), - ) - .await -} - -pub async fn error_file_extension(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.error_file_extension.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_getting_option.rs b/src/function/error_management/error_getting_option.rs deleted file mode 100644 index bc2c8274..00000000 --- a/src/function/error_management/error_getting_option.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::function::error_management::common::{get_localised_langage, send_embed_message}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_no_option(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.forgot_module.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_instance_admin.rs b/src/function/error_management/error_instance_admin.rs deleted file mode 100644 index 864e75e8..00000000 --- a/src/function/error_management/error_instance_admin.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_instance_admin_models_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.admin_instance_error.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_module.rs b/src/function/error_management/error_module.rs deleted file mode 100644 index 66062b67..00000000 --- a/src/function/error_management/error_module.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::function::error_management::common::{get_localised_langage, send_embed_message}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_no_module(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.forgot_module.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_no.rs b/src/function/error_management/error_no.rs deleted file mode 100644 index 83fe4700..00000000 --- a/src/function/error_management/error_no.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::function::error_management::common::{get_localised_langage, send_embed_message}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_no_user_specified(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.forgot_module.as_str(), - ) - .await -} - -pub async fn error_no_anime_specified(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.error_no_anime_specified.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_not_implemented.rs b/src/function/error_management/error_not_implemented.rs deleted file mode 100644 index 5f20afb7..00000000 --- a/src/function/error_management/error_not_implemented.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::function::error_management::common::{get_localised_langage, send_embed_message}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_not_implemented(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.not_implemented.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_not_nsfw.rs b/src/function/error_management/error_not_nsfw.rs deleted file mode 100644 index 241f42ce..00000000 --- a/src/function/error_management/error_not_nsfw.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::function::error_management::common::{get_localised_langage, send_embed_message}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_not_nsfw(ctx: &Context, command: &ApplicationCommandInteraction) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.forgot_module.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_parsing_json.rs b/src/function/error_management/error_parsing_json.rs deleted file mode 100644 index 81d497fc..00000000 --- a/src/function/error_management/error_parsing_json.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::model::prelude::Message; - -pub async fn error_parsing_json_edit( - ctx: &Context, - message: Message, - command: &ApplicationCommandInteraction, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title, - localised_text.error_parsing_json.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_request.rs b/src/function/error_management/error_request.rs deleted file mode 100644 index 2d88378e..00000000 --- a/src/function/error_management/error_request.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_making_request_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.error_request.as_str(), - ) - .await; -} diff --git a/src/function/error_management/error_resolving_value.rs b/src/function/error_management/error_resolving_value.rs deleted file mode 100644 index 5347c869..00000000 --- a/src/function/error_management/error_resolving_value.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{ - get_localised_langage, send_followup_embed_message, -}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_resolving_value_followup( - ctx: &Context, - command: &ApplicationCommandInteraction, -) { - let localised_text = match get_localised_langage(ctx, command).await { - Ok(data) => data, - Err(_) => return, - }; - send_followup_embed_message( - ctx, - command, - localised_text.error_title.clone(), - localised_text.error_resolving_value.as_str(), - ) - .await -} diff --git a/src/function/error_management/error_response.rs b/src/function/error_management/error_response.rs deleted file mode 100644 index 5068cb7b..00000000 --- a/src/function/error_management/error_response.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_getting_response_from_url_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.error_getting_response_from_url.as_str(), - ) - .await; -} - -pub async fn error_getting_bytes_response_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.error_getting_bytes.as_str(), - ) - .await; -} - -pub async fn error_writing_file_response_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.error_writing_file.as_str(), - ) - .await; -} diff --git a/src/function/error_management/error_token.rs b/src/function/error_management/error_token.rs deleted file mode 100644 index 34be20a8..00000000 --- a/src/function/error_management/error_token.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_no_token_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.no_token.as_str(), - ) - .await; -} diff --git a/src/function/error_management/error_url.rs b/src/function/error_management/error_url.rs deleted file mode 100644 index b2938b52..00000000 --- a/src/function/error_management/error_url.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, get_localised_langage_edit}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn error_no_url_edit( - ctx: &Context, - command: &ApplicationCommandInteraction, - message: Message, -) { - let localised_text = match get_localised_langage_edit(ctx, message.clone(), command).await { - Ok(data) => data, - Err(_) => return, - }; - edit_embed_message( - ctx, - message, - localised_text.error_title.clone(), - localised_text.error_url.as_str(), - ) - .await; -} diff --git a/src/function/error_management/mod.rs b/src/function/error_management/mod.rs deleted file mode 100644 index 3c9d38d2..00000000 --- a/src/function/error_management/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub mod common; -pub mod error_avatar; -pub mod error_base_url; -pub mod error_creating_header; -pub mod error_file; -pub mod error_getting_option; -pub mod error_instance_admin; -pub mod error_module; -pub mod error_no; -pub mod error_not_implemented; -pub mod error_not_nsfw; -pub mod error_parsing_json; -pub mod error_request; -pub mod error_resolving_value; -pub mod error_response; -pub mod error_token; -pub mod error_url; -pub mod no_lang_error; diff --git a/src/function/error_management/no_lang_error.rs b/src/function/error_management/no_lang_error.rs deleted file mode 100644 index dff407f3..00000000 --- a/src/function/error_management/no_lang_error.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::function::error_management::common::{edit_embed_message, send_embed_message}; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -pub async fn no_langage_error(ctx: &Context, command: &ApplicationCommandInteraction) { - println!("Error: Langage does not exist."); - send_embed_message(ctx, command, "Error".to_string(), "Langage does not exist.").await; -} - -pub async fn error_langage_file_not_found(ctx: &Context, command: &ApplicationCommandInteraction) { - println!("Error: The langage file was not found."); - send_embed_message( - ctx, - command, - "Error".to_string(), - "The langage file was not found.", - ) - .await; -} - -pub async fn error_cant_read_langage_file(ctx: &Context, command: &ApplicationCommandInteraction) { - println!("Error: The langage file can't be read."); - send_embed_message( - ctx, - command, - "Error".to_string(), - "The langage file can't be read.", - ) - .await; -} - -pub async fn error_parsing_langage_json(ctx: &Context, command: &ApplicationCommandInteraction) { - println!("Error: Failed to parse the langage json file."); - send_embed_message( - ctx, - command, - "Error".to_string(), - "Failed to parse the langage json file.", - ) - .await; -} - -pub async fn error_no_langage_guild_id(ctx: &Context, command: &ApplicationCommandInteraction) { - println!("Error: Failed to get the guild id."); - send_embed_message( - ctx, - command, - "Error".to_string(), - "Failed to get the guild id.", - ) - .await; -} - -pub async fn no_langage_error_edit(ctx: &Context, message: Message) { - edit_embed_message(ctx, message, "Error".to_string(), "Langage does not exist.").await -} - -pub async fn error_langage_file_not_found_edit(ctx: &Context, message: Message) { - edit_embed_message( - ctx, - message, - "Error".to_string(), - "The langage file was not found.", - ) - .await -} - -pub async fn error_cant_read_langage_file_edit(ctx: &Context, message: Message) { - edit_embed_message( - ctx, - message, - "Error".to_string(), - "The langage file can't be read.", - ) - .await -} - -pub async fn error_parsing_langage_json_edit(ctx: &Context, message: Message) { - edit_embed_message( - ctx, - message, - "Error".to_string(), - "Failed to parse the json file.", - ) - .await -} - -pub async fn error_no_langage_guild_id_edit(ctx: &Context, message: Message) { - edit_embed_message( - ctx, - message, - "Error".to_string(), - "Failed to get the guild id.", - ) - .await -} diff --git a/src/function/general/differed_response.rs b/src/function/general/differed_response.rs deleted file mode 100644 index 2c4b5447..00000000 --- a/src/function/general/differed_response.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::model::prelude::InteractionResponseType; - -/// An asynchronous function to respond to an Application Command Interaction in "deferred" mode. -/// -/// This function, which is part of a structure `Context`, receives two arguments: -/// - A reference (`&`) to `Context` (most likely the Discord communication context) -/// - A reference (`&`) to `ApplicationCommandInteraction` (which represents the command that triggered this function) -/// -/// As a result of the function, a response to the initial interaction is created with a type set to `DeferredChannelMessageWithSource`. -/// If the `create_interaction_response()` method fails to create a response, the error is printed to the console. -/// -/// # Arguments -/// -/// * `ctx` - `Context` reference which represents the context in which this function operates and commands are processed. -/// * `command` - An `ApplicationCommandInteraction` reference, which represents the specific command interaction to respond to. -/// -/// # Example -/// -/// ``` -/// let ctx = // insert how to get context here -/// let command = // insert how to get command here -/// differed_response(&ctx, &command).await; -/// ``` -/// -/// # Note -/// -/// This function is defined as `pub async`, read more about asynchronous functions in Rust if you are unfamiliar. -pub async fn differed_response(ctx: &Context, command: &ApplicationCommandInteraction) { - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } -} - -/// Asynchronously responds to an application command interaction with a differed message which includes the command's source. -/// This function also attempts to delete a specified file regardless of whether the interaction response is successful or not. -/// -/// # Arguments -/// -/// * `ctx` - A reference to the context of the command interaction. -/// * `command` - A reference to the ApplicationCommandInteraction; the command that was interacted with. -/// * `file_to_delete` - A PathBuf object specifying the file to delete. -/// -/// # Error -/// -/// If the interaction response fails, it tries to remove the specified file -/// and logs the error message with the reason on the console. The error of file deletion -/// will be suppressed. -/// -/// # Example -/// -/// ```no_run -/// use serenity::client::Context; -/// use serenity::model::interactions::application_command::ApplicationCommandInteraction; -/// use std::path::PathBuf; -/// -/// let ctx: Context = // ... -/// let command: ApplicationCommandInteraction = // ... -/// let file_to_delete: PathBuf = // ... -/// -/// differed_response_with_file_deletion(&ctx, &command, file_to_delete).await; -/// ``` -pub async fn differed_response_with_file_deletion( - ctx: &Context, - command: &ApplicationCommandInteraction, - file_to_delete: PathBuf, -) { - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) - .await - { - let _ = fs::remove_file(&file_to_delete); - println!("Cannot respond to slash command: {}", why); - } -} diff --git a/src/function/general/get_guild_langage.rs b/src/function/general/get_guild_langage.rs deleted file mode 100644 index 3d0a3673..00000000 --- a/src/function/general/get_guild_langage.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::function::sql::sqlite::pool::get_sqlite_pool; - -/// Asynchronously fetches the language of a given guild from the database. -/// -/// This function takes the guild_id as an argument and returns the language associated with that guild. -/// The function attempts to connect to the data.db database, then executes a SQL query to select -/// the language of the given guild from the `guild_lang` table. If the given guild_id is not found -/// in the database or if there's any issue with the query, the function will still return a default -/// language of "En". -/// -/// # Arguments -/// -/// * `guild_id` - A string slice that holds the id of the guild. -/// -/// # Examples -/// -/// ``` -/// let guild_id = "some id"; -/// let guild_language = get_guild_langage(guild_id).await; -/// assert_eq!(guild_language, "En"); -/// ``` -/// -/// # Returns -/// -/// * `String` - The language of the guild. Returns "En" if no language is found for the guild. -/// -/// # Panics -/// -/// The function will panic if the database connection cannot be established or the SQL query -/// execution fails. -/// -/// # Errors -/// -/// This function will return a tuple (None, None) if there is any error trying to fetch the guild -/// language from the database. -pub async fn get_guild_langage(guild_id: String) -> String { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - let row: (Option<String>, Option<String>) = - sqlx::query_as("SELECT lang, guild FROM guild_lang WHERE guild = ?") - .bind(guild_id) - .fetch_one(&pool) - .await - .unwrap_or((None, None)); - let (lang, _): (Option<String>, Option<String>) = row; - - lang.unwrap_or("En".to_string()) -} diff --git a/src/function/general/get_nsfw_channel.rs b/src/function/general/get_nsfw_channel.rs deleted file mode 100644 index e6a9a754..00000000 --- a/src/function/general/get_nsfw_channel.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::model::prelude::ChannelId; - -pub async fn get_nsfw(command: &ApplicationCommandInteraction, ctx: &Context) -> bool { - let channel_id: ChannelId = command.channel_id; - let channel = channel_id.to_channel(&ctx.http).await.unwrap(); - channel.is_nsfw() -} diff --git a/src/function/general/in_progress.rs b/src/function/general/in_progress.rs deleted file mode 100644 index e206fc6b..00000000 --- a/src/function/general/in_progress.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::structure::embed::general::struct_lang_in_progress::InProgressLocalisedText; -use serenity::client::Context; -use serenity::model::channel::Message; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::model::Timestamp; -use serenity::utils::Colour; - -/// An asynchronous function that sends an embedded message indicating a command is in progress. -/// -/// This function accepts a context and an application command interaction as parameters, -/// builds a localised in-progress message based on these parameters -/// and colorizes it with `Colour::FABLED_PINK`. -/// -/// It creates a follow-up message and embeds the localised text message -/// with timestamp and color in it. -/// -/// This embedded message is then sent asynchronously. -/// -/// Ideally, this function could be used to notify a user that a certain command -/// they initiated is currently being processed. -/// -/// # Parameters -/// * `ctx`: A reference to the context, which stores data such as the HTTP client for the bot to use. -/// * `command`: A reference to the ApplicationCommandInteraction which represents the user's command. -/// -/// # Return -/// This function returns a `Result<Option<Message>, String>`. -/// If the function succeeds, `Ok(Some(message))` is returned where `message` -/// is the unwrapped followup message containing the embedded in-progress information. -/// If an error occurs while fetching the localised text, `Err(String)` is returned -/// which carries the error message. -/// -/// # Errors -/// This function will return an `Err` variant if the call to `get_in_progress_localised` fails. -/// -/// # Example -/// ```no_run -/// #[tokio::main] -/// async fn main() -> Result<(), Box<dyn std::error::Error>> { -/// let ctx = ...; // Some context -/// let command = ...; // Some command -/// let result = in_progress_embed(ctx, command).await; -/// println!("Result: {:?}", result); -/// Ok(()) -/// } -/// ``` -pub async fn in_progress_embed( - ctx: &Context, - command: &ApplicationCommandInteraction, -) -> Result<Option<Message>, String> { - let color = Colour::FABLED_PINK; - let localised_text = - match InProgressLocalisedText::get_in_progress_localised(ctx, command).await { - Ok(data) => data, - Err(data) => return Err(data.parse().unwrap()), - }; - let message = command - .create_followup_message(&ctx.http, |f| { - f.embed(|e| { - e.title(&localised_text.title) - .description(&localised_text.description) - .timestamp(Timestamp::now()) - .color(color) - }) - }) - .await; - Ok(Some(message.unwrap())) -} diff --git a/src/function/general/mod.rs b/src/function/general/mod.rs deleted file mode 100644 index 61db7bb5..00000000 --- a/src/function/general/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod differed_response; -pub mod get_guild_langage; -pub mod get_nsfw_channel; -pub mod html_parser; -pub mod in_progress; -pub mod trim; diff --git a/src/function/mod.rs b/src/function/mod.rs deleted file mode 100644 index 002463fd..00000000 --- a/src/function/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod anilist; -pub mod error_management; -pub mod general; -pub mod requests; -pub mod sql; diff --git a/src/function/requests/mod.rs b/src/function/requests/mod.rs deleted file mode 100644 index be9378d9..00000000 --- a/src/function/requests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod request; diff --git a/src/function/requests/request.rs b/src/function/requests/request.rs deleted file mode 100644 index f4ad6918..00000000 --- a/src/function/requests/request.rs +++ /dev/null @@ -1,227 +0,0 @@ -use crate::constant::DAYS; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use chrono::Utc; -use reqwest::Client; -use serde_json::Value; - -/// the number of day before the cache is too old and need to be renewed. -/// Makes a requests to AniList. -/// -/// This function takes a JSON object and a boolean flag `always_update` as arguments. -/// -/// If `always_update` is set to true, it will directly make a new requests regardless of the cache. -/// Otherwise, if `always_update` is false, it will first check if a cached response exists for the -/// given JSON object. If a cached response exists, it will be returned; if not, a new requests will -/// be made. -/// -/// The result of this function is a JSON response in the form of a String. -/// -/// The function is asynchronous. It may need to wait for I/O operations (network and disk) to complete. -/// -/// # Arguments -/// -/// * `json` - A JSON object that represents the requests to be sent. -/// * `always_update` - A boolean flag. If it's true, a new requests will be made regardless of the cache. -/// -/// # Examples -/// -/// ``` -/// let json = serde_json::json!({ "query": "...", "variables": { "id": 12345 }}); -/// let always_update = false; -/// let response = make_request_anilist(json, always_update).await; -/// -/// println!("{}", response); -/// ``` -/// -/// # Returns -/// -/// * A JSON response in the form of a String. -/// -pub async fn make_request_anilist(json: Value, always_update: bool) -> String { - if always_update { - do_request(json, always_update).await - } else { - get_cache(json.clone()).await - } -} - -/// Asynchronously retrieves cached requests data from a SQLite database. -/// -/// This function takes a JSON object (`json`) as input and queries a SQLite database (`./cache.db`) -/// to try and retrieve any cached requests data that matches the input. -/// -/// If the database does not contain any matching requests data, the function executes -/// an unspecified requests operation (`do_request()`) with the given `json` object. -/// Furthermore, if any cached data hasn't been updated within a specified time period -/// (calculated as the number of `DAYS` times the number of seconds in a day), -/// `do_request()` will be executed as well. -/// -/// # Arguments -/// -/// * `json` - A Serde JSON object representing the requests data to be queried in the SQLite database. -/// -/// # Returns -/// -/// * A `String` value representing the response related to the provided requests data (i.e., `json`). -/// This `String` is either directly fetched from the database or is the result of a fresh requests. -/// -/// # Errors -/// -/// This function will panic (`unwrap()`) if there is any issue executing or fetching from the SQL statement. -/// It will also panic if `get_pool()` or `do_request()` encounter errors. -/// -/// # Examples -/// -/// ```rust -/// let json = serde_json::json!({"some": "data"}); -/// let result = get_cache(json); -/// ``` -/// -/// # Note -/// -/// This function is `async`, meaning it returns a `Future`. It must be `await`ed in an asynchronous context. -async fn get_cache(json: Value) -> String { - let database_url = "./cache.db"; - let pool = get_sqlite_pool(database_url).await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS request_cache ( - json TEXT PRIMARY KEY, - response TEXT NOT NULL, - last_updated INTEGER NOT NULL - )", - ) - .execute(&pool) - .await - .unwrap(); - - let row: (Option<String>, Option<String>, Option<i64>) = - sqlx::query_as("SELECT json, response, last_updated FROM request_cache WHERE json = ?") - .bind(json.clone()) - .fetch_one(&pool) - .await - .unwrap_or((None, None, None)); - let (json_resp, response, last_updated): (Option<String>, Option<String>, Option<i64>) = row; - - if json_resp.is_none() || response.is_none() || last_updated.is_none() { - do_request(json.clone(), false).await - } else { - let updated_at = last_updated.unwrap(); - let duration_since_updated = Utc::now().timestamp() - updated_at; - if duration_since_updated < (DAYS * 24 * 60 * 60) { - response.unwrap() - } else { - do_request(json.clone(), false).await - } - } -} - -/// Asynchronous function to add a JSON requests and its corresponding response to a cache database. -/// -/// # Arguments -/// -/// * `json` - A Value object representing the JSON requests. -/// * `resp` - A String object containing the response for the JSON requests. -/// -/// # Returns -/// -/// * A boolean, currently always true, indicating whether the operation was successful. -/// -/// # Database interactions -/// -/// * Connects to a SQLite database located at "./cache.db" on the local filesystem. -/// * Executes an SQL query to insert the JSON requests, the response, and a timestamp of the last modification into a table named "request_cache". -/// * The parameters provided to the SQL query are the JSON requests, the response, and the current timestamp. -/// * In case the table already contains a record with the same JSON requests, it will be replaced due to the "REPLACE" keyword. -/// -/// # Panics -/// -/// * If the operation to execute the SQL query fails, the program will panic due to the use of `unwrap()`. -/// -/// # Example -/// -/// ```rust -/// let json_request: Json = get_json_request_represented_as_value(); -/// let response: String = get_the_corresponding_response_as_string(); -/// add_cache(json_request, response).await; -/// ``` -/// -/// # Note -/// -/// This function always currently returns true, so the boolean return value is currently not indicative of whether the operation was successful. This might be subject for future improvements, such as implementing error handling, to prevent possible panics. -async fn add_cache(json: Value, resp: String) -> bool { - let database_url = "./cache.db"; - let pool = get_sqlite_pool(database_url).await; - let now = Utc::now().timestamp(); - sqlx::query( - "INSERT OR REPLACE INTO request_cache (json, response, last_updated) VALUES (?, ?, ?)", - ) - .bind(json.clone()) - .bind(resp.clone()) - .bind(now) - .execute(&pool) - .await - .unwrap(); - - true -} - -/// This function executes a POST requests to a specific URL (https://graphql.anilist.co/). -/// -/// # Arguments -/// -/// * `json: Value` - The JSON content that acts as the body for the POST requests. -/// This is cloned into the body of the requests. -/// -/// * `always_update: bool` - A boolean flag determining whether the response should be added to the cache. -/// If `always_update` is false, the response from the server will be added to the cache. -/// -/// # Return -/// -/// This function returns a `String`, the content of the response obtained from the server. -/// -/// This function is async, so it'll return a Future that should be awaited for. -/// -/// # Error Handling -/// -/// Note that in case of any error occuring while executing the requests or retrieving the response, -/// the function will panic due to the use of `unwrap()`. -/// -/// # Usage -/// -/// ``` -/// let resp = do_request(json_data, true).await; -/// println!("{}", resp); -/// ``` -/// -/// # Features -/// -/// * Uses POST method to send the requests. -/// * Adds common headers such as "Content-Type" and "Accept". -/// * Caches the response if the `always_update` flag is set to false. -/// -/// # Improvements -/// -/// For better error handling, consider replacing `unwrap()` calls with proper error handling code. -/// -/// # Async -/// -/// This function is `async` and therefore needs to be awaited for when called. -async fn do_request(json: Value, always_update: bool) -> String { - let client = Client::new(); - let res = client - .post("https://graphql.anilist.co/") - .header("Content-Type", "application/json") - .header("Accept", "application/json") - .body(json.clone().to_string()) - .send() - .await - .unwrap() - .text() - .await; - let resp = res.unwrap(); - if !always_update { - add_cache(json.clone(), resp.clone()).await; - } - resp -} diff --git a/src/function/sql/postgresql/mod.rs b/src/function/sql/postgresql/mod.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/function/sql/postgresql/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/function/sql/sqlite/init.rs b/src/function/sql/sqlite/init.rs deleted file mode 100644 index 15d51c5d..00000000 --- a/src/function/sql/sqlite/init.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use sqlx::{Pool, Sqlite}; -use std::fs::File; -use std::path::Path; - -pub async fn init_sqlite() { - let paths = ["./data.db", "./cache.db"]; - - for path in &paths { - let p = Path::new(path); - if !p.exists() { - match File::create(p) { - Ok(_) => {} - Err(e) => { - println!("Failed to create the file {} : {}", path, e); - return; - } - } - } - } - let pool = get_sqlite_pool(paths[1]).await; - init_sqlite_cache(&pool).await; - let pool = get_sqlite_pool(paths[0]).await; - init_sqlite_data(&pool).await; -} - -async fn init_sqlite_cache(_pool: &Pool<Sqlite>) {} - -async fn init_sqlite_data(_pool: &Pool<Sqlite>) {} diff --git a/src/function/sql/sqlite/mod.rs b/src/function/sql/sqlite/mod.rs deleted file mode 100644 index d79bd6fd..00000000 --- a/src/function/sql/sqlite/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod init; -pub mod pool; diff --git a/src/function/sql/sqlite/pool.rs b/src/function/sql/sqlite/pool.rs deleted file mode 100644 index 54230787..00000000 --- a/src/function/sql/sqlite/pool.rs +++ /dev/null @@ -1,5 +0,0 @@ -use sqlx::{Pool, Sqlite, SqlitePool}; - -pub async fn get_sqlite_pool(database_url: &str) -> Pool<Sqlite> { - SqlitePool::connect(database_url).await.unwrap() -} diff --git a/src/lang_struct/ai/image.rs b/src/lang_struct/ai/image.rs new file mode 100644 index 00000000..bdfe40de --- /dev/null +++ b/src/lang_struct/ai/image.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct ImageLocalised { + pub title: String, +} + +pub async fn load_localization_image(guild_id: String) -> Result<ImageLocalised, AppError> { + let mut file = File::open("json/message/ai/image.json") + .map_err(|_| LocalisationFileError(String::from("File image.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File image.json can't be read.")))?; + + let json_data: HashMap<String, ImageLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse image.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let image_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(image_localised_text.clone()) +} diff --git a/src/lang_struct/ai/mod.rs b/src/lang_struct/ai/mod.rs new file mode 100644 index 00000000..dd8793cd --- /dev/null +++ b/src/lang_struct/ai/mod.rs @@ -0,0 +1,3 @@ +pub mod image; +pub mod transcript; +pub mod translation; diff --git a/src/lang_struct/ai/transcript.rs b/src/lang_struct/ai/transcript.rs new file mode 100644 index 00000000..889c330e --- /dev/null +++ b/src/lang_struct/ai/transcript.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct TranscriptLocalised { + pub title: String, +} + +pub async fn load_localization_transcript( + guild_id: String, +) -> Result<TranscriptLocalised, AppError> { + let mut file = File::open("json/message/ai/transcript.json") + .map_err(|_| LocalisationFileError(String::from("File transcript.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File transcript.json can't be read.")))?; + + let json_data: HashMap<String, TranscriptLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse transcript.json.")))?; + + let transcript_choice = get_guild_langage(guild_id).await; + + let transcript_localised_text = json_data + .get(transcript_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(transcript_localised_text.clone()) +} diff --git a/src/lang_struct/ai/translation.rs b/src/lang_struct/ai/translation.rs new file mode 100644 index 00000000..abb0aaad --- /dev/null +++ b/src/lang_struct/ai/translation.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct TranslationtLocalised { + pub title: String, +} + +pub async fn load_localization_translation( + guild_id: String, +) -> Result<TranslationtLocalised, AppError> { + let mut file = File::open("json/message/ai/translation.json") + .map_err(|_| LocalisationFileError(String::from("File translation.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File translation.json can't be read.")))?; + + let json_data: HashMap<String, TranslationtLocalised> = + serde_json::from_str(&json).map_err(|_| { + LocalisationParsingError(String::from("Failing to parse translation.json.")) + })?; + + let translation_choice = get_guild_langage(guild_id).await; + + let transcript_localised_text = json_data + .get(translation_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(transcript_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/add_activity.rs b/src/lang_struct/anilist/add_activity.rs new file mode 100644 index 00000000..ea0f6513 --- /dev/null +++ b/src/lang_struct/anilist/add_activity.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct AddActivityLocalised { + pub success: String, + pub fail: String, + pub fail_desc: String, + pub success_desc: String, +} + +pub async fn load_localization_add_activity( + guild_id: String, +) -> Result<AddActivityLocalised, AppError> { + let mut file = File::open("json/message/anilist/add_activity.json") + .map_err(|_| LocalisationFileError(String::from("File add_activity.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json).map_err(|_| { + LocalisationReadError(String::from("File add_activity.json can't be read.")) + })?; + + let json_data: HashMap<String, AddActivityLocalised> = + serde_json::from_str(&json).map_err(|_| { + LocalisationParsingError(String::from("Failing to parse add_activity.json.")) + })?; + + let lang_choice = get_guild_langage(guild_id).await; + + let add_activity_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(add_activity_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/character.rs b/src/lang_struct/anilist/character.rs new file mode 100644 index 00000000..64dbfa62 --- /dev/null +++ b/src/lang_struct/anilist/character.rs @@ -0,0 +1,41 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct CharacterLocalised { + pub desc: String, + pub date_of_birth: String, +} + +pub async fn load_localization_character(guild_id: String) -> Result<CharacterLocalised, AppError> { + let mut file = File::open("json/message/anilist/character.json") + .map_err(|_| LocalisationFileError(String::from("File character.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File character.json can't be read.")))?; + + let json_data: HashMap<String, CharacterLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse character.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let character_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(character_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/compare.rs b/src/lang_struct/anilist/compare.rs new file mode 100644 index 00000000..8344a355 --- /dev/null +++ b/src/lang_struct/anilist/compare.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct CompareLocalised { + pub more_anime: String, + pub same_anime: String, + pub more_watch_time: String, + pub same_watch_time: String, + pub genre_anime: String, + pub same_genre_anime: String, + pub tag_anime: String, + pub same_tag_anime: String, + pub more_manga: String, + pub same_manga: String, + pub genre_manga: String, + pub same_genre_manga: String, + pub tag_manga: String, + pub same_tag_manga: String, + pub more_manga_chapter: String, + pub same_manga_chapter: String, +} + +pub async fn load_localization_compare(guild_id: String) -> Result<CompareLocalised, AppError> { + let mut file = File::open("json/message/anilist/compare.json") + .map_err(|_| LocalisationFileError(String::from("File compare.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File compare.json can't be read.")))?; + + let json_data: HashMap<String, CompareLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse compare.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let compare_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(compare_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/level.rs b/src/lang_struct/anilist/level.rs new file mode 100644 index 00000000..2410644c --- /dev/null +++ b/src/lang_struct/anilist/level.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct LevelLocalised { + pub desc: String, +} + +pub async fn load_localization_level(guild_id: String) -> Result<LevelLocalised, AppError> { + let mut file = File::open("json/message/anilist/level.json") + .map_err(|_| LocalisationFileError(String::from("File level.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File level.json can't be read.")))?; + + let json_data: HashMap<String, LevelLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse level.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let level_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(level_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/media.rs b/src/lang_struct/anilist/media.rs new file mode 100644 index 00000000..76ea81f9 --- /dev/null +++ b/src/lang_struct/anilist/media.rs @@ -0,0 +1,43 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct MediaLocalised { + pub field1_title: String, + pub field2_title: String, + pub desc: String, + pub staff_text: String, +} + +pub async fn load_localization_media(guild_id: String) -> Result<MediaLocalised, AppError> { + let mut file = File::open("json/message/anilist/media.json") + .map_err(|_| LocalisationFileError(String::from("File media.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File media.json can't be read.")))?; + + let json_data: HashMap<String, MediaLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse media.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let media_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(media_localised_text.clone()) +} diff --git a/src/structure/anilist/mod.rs b/src/lang_struct/anilist/mod.rs similarity index 53% rename from src/structure/anilist/mod.rs rename to src/lang_struct/anilist/mod.rs index 2fcc1f5f..907787a6 100644 --- a/src/structure/anilist/mod.rs +++ b/src/lang_struct/anilist/mod.rs @@ -1,9 +1,12 @@ +pub mod add_activity; pub mod character; +pub mod compare; pub mod level; pub mod media; pub mod random; +pub mod register; +pub mod seiyuu; +pub mod send_activity; pub mod staff; -pub mod struct_autocomplete; -pub mod struct_minimal_anime; pub mod studio; pub mod user; diff --git a/src/lang_struct/anilist/random.rs b/src/lang_struct/anilist/random.rs new file mode 100644 index 00000000..f81a87d0 --- /dev/null +++ b/src/lang_struct/anilist/random.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct RandomLocalised { + pub desc: String, +} + +pub async fn load_localization_random(guild_id: String) -> Result<RandomLocalised, AppError> { + let mut file = File::open("json/message/anilist/random.json") + .map_err(|_| LocalisationFileError(String::from("File random.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File random.json can't be read.")))?; + + let json_data: HashMap<String, RandomLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse random.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let random_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(random_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/register.rs b/src/lang_struct/anilist/register.rs new file mode 100644 index 00000000..d8327305 --- /dev/null +++ b/src/lang_struct/anilist/register.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct RegisterLocalised { + pub desc: String, +} + +pub async fn load_localization_register(guild_id: String) -> Result<RegisterLocalised, AppError> { + let mut file = File::open("json/message/anilist/register.json") + .map_err(|_| LocalisationFileError(String::from("File register.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File register.json can't be read.")))?; + + let json_data: HashMap<String, RegisterLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse register.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let register_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(register_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/seiyuu.rs b/src/lang_struct/anilist/seiyuu.rs new file mode 100644 index 00000000..0f377bee --- /dev/null +++ b/src/lang_struct/anilist/seiyuu.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct SeiyuuLocalised { + pub title: String, +} + +pub async fn load_localization_seiyuu(guild_id: String) -> Result<SeiyuuLocalised, AppError> { + let mut file = File::open("json/message/anilist/seiyuu.json") + .map_err(|_| LocalisationFileError(String::from("File seiyuu.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File seiyuu.json can't be read.")))?; + + let json_data: HashMap<String, SeiyuuLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse seiyuu.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let seiyuu_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(seiyuu_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/send_activity.rs b/src/lang_struct/anilist/send_activity.rs new file mode 100644 index 00000000..fcf1deb6 --- /dev/null +++ b/src/lang_struct/anilist/send_activity.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct SendActivityLocalised { + pub title: String, + pub desc: String, +} + +pub async fn load_localization_send_activity( + guild_id: String, +) -> Result<SendActivityLocalised, AppError> { + let mut file = File::open("json/message/anilist/send_activity.json") + .map_err(|_| LocalisationFileError(String::from("File send_activity.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json).map_err(|_| { + LocalisationReadError(String::from("File send_activity.json can't be read.")) + })?; + + let json_data: HashMap<String, SendActivityLocalised> = + serde_json::from_str(&json).map_err(|_| { + LocalisationParsingError(String::from("Failing to parse send_activity.json.")) + })?; + + let send_activity_choice = get_guild_langage(guild_id).await; + + let add_activity_localised_text = json_data + .get(send_activity_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(add_activity_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/staff.rs b/src/lang_struct/anilist/staff.rs new file mode 100644 index 00000000..1695884d --- /dev/null +++ b/src/lang_struct/anilist/staff.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct StaffLocalised { + pub field1_title: String, + pub field2_title: String, + pub desc: String, + pub date_of_birth: String, + pub date_of_death: String, +} + +pub async fn load_localization_staff(guild_id: String) -> Result<StaffLocalised, AppError> { + let mut file = File::open("json/message/anilist/staff.json") + .map_err(|_| LocalisationFileError(String::from("File staff.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File staff.json can't be read.")))?; + + let json_data: HashMap<String, StaffLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse staff.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let staff_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(staff_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/studio.rs b/src/lang_struct/anilist/studio.rs new file mode 100644 index 00000000..dfeb5f54 --- /dev/null +++ b/src/lang_struct/anilist/studio.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct StudioLocalised { + pub desc: String, +} + +pub async fn load_localization_studio(guild_id: String) -> Result<StudioLocalised, AppError> { + let mut file = File::open("json/message/anilist/studio.json") + .map_err(|_| LocalisationFileError(String::from("File studio.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File studio.json can't be read.")))?; + + let json_data: HashMap<String, StudioLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse studio.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let studio_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(studio_localised_text.clone()) +} diff --git a/src/lang_struct/anilist/user.rs b/src/lang_struct/anilist/user.rs new file mode 100644 index 00000000..f0fb6f47 --- /dev/null +++ b/src/lang_struct/anilist/user.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct UserLocalised { + pub manga: String, + pub anime: String, + pub week: String, + pub day: String, + pub hour: String, + pub minute: String, + pub weeks: String, + pub days: String, + pub hours: String, + pub minutes: String, +} + +pub async fn load_localization_user(guild_id: String) -> Result<UserLocalised, AppError> { + let mut file = File::open("json/message/anilist/user.json") + .map_err(|_| LocalisationFileError(String::from("File user.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File user.json can't be read.")))?; + + let json_data: HashMap<String, UserLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse user.json.")))?; + + trace!("{}", guild_id); + trace!("{}", guild_id != String::from("0")); + + let lang_choice = get_guild_langage(guild_id).await; + + let user_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(user_localised_text.clone()) +} diff --git a/src/lang_struct/general/avatar.rs b/src/lang_struct/general/avatar.rs new file mode 100644 index 00000000..90377814 --- /dev/null +++ b/src/lang_struct/general/avatar.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct AvatarLocalised { + pub title: String, +} + +pub async fn load_localization_avatar(guild_id: String) -> Result<AvatarLocalised, AppError> { + let mut file = File::open("json/message/general/avatar.json") + .map_err(|_| LocalisationFileError(String::from("File avatar.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File avatar.json can't be read.")))?; + + let json_data: HashMap<String, AvatarLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse avatar.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let avatar_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(avatar_localised_text.clone()) +} diff --git a/src/lang_struct/general/banner.rs b/src/lang_struct/general/banner.rs new file mode 100644 index 00000000..1acdf3a0 --- /dev/null +++ b/src/lang_struct/general/banner.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct BannerLocalised { + pub title: String, + pub no_banner: String, + pub no_banner_title: String, +} + +pub async fn load_localization_banner(guild_id: String) -> Result<BannerLocalised, AppError> { + let mut file = File::open("json/message/general/banner.json") + .map_err(|_| LocalisationFileError(String::from("File image.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File image.json can't be read.")))?; + + let json_data: HashMap<String, BannerLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse image.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let banner_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(banner_localised_text.clone()) +} diff --git a/src/lang_struct/general/credit.rs b/src/lang_struct/general/credit.rs new file mode 100644 index 00000000..2c00fec7 --- /dev/null +++ b/src/lang_struct/general/credit.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CreditLocalisedLine { + pub desc: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CreditLocalised { + pub title: String, + pub credits: Vec<CreditLocalisedLine>, +} + +pub async fn load_localization_credit(guild_id: String) -> Result<CreditLocalised, AppError> { + let mut file = File::open("json/message/general/credit.json") + .map_err(|_| LocalisationFileError(String::from("File credit.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File credit.json can't be read.")))?; + + let json_data: HashMap<String, CreditLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse credit.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let credit_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(credit_localised_text.clone()) +} diff --git a/src/lang_struct/general/info.rs b/src/lang_struct/general/info.rs new file mode 100644 index 00000000..0efb44b6 --- /dev/null +++ b/src/lang_struct/general/info.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct InfoLocalised { + pub title: String, + pub desc: String, + pub footer: String, + pub button_see_on_github: String, + pub button_official_website: String, + pub button_official_discord: String, + pub button_add_the_bot: String, +} + +pub async fn load_localization_info(guild_id: String) -> Result<InfoLocalised, AppError> { + let mut file = File::open("json/message/general/info.json") + .map_err(|_| LocalisationFileError(String::from("File info.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File info.json can't be read.")))?; + + let json_data: HashMap<String, InfoLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse info.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let info_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(info_localised_text.clone()) +} diff --git a/src/lang_struct/general/lang.rs b/src/lang_struct/general/lang.rs new file mode 100644 index 00000000..b2cf50e3 --- /dev/null +++ b/src/lang_struct/general/lang.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct LangLocalised { + pub title: String, + pub desc: String, +} + +pub async fn load_localization_lang(guild_id: String) -> Result<LangLocalised, AppError> { + let mut file = File::open("json/message/general/lang.json") + .map_err(|_| LocalisationFileError(String::from("File lang_struct.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File lang_struct.json can't be read.")))?; + + let json_data: HashMap<String, LangLocalised> = serde_json::from_str(&json).map_err(|_| { + LocalisationParsingError(String::from("Failing to parse lang_struct.json.")) + })?; + + let lang_choice = get_guild_langage(guild_id).await; + + let lang_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(lang_localised_text.clone()) +} diff --git a/src/lang_struct/general/mod.rs b/src/lang_struct/general/mod.rs new file mode 100644 index 00000000..345f44ff --- /dev/null +++ b/src/lang_struct/general/mod.rs @@ -0,0 +1,8 @@ +pub mod avatar; +pub mod banner; +pub mod credit; +pub mod info; +pub mod lang; +pub mod module; +pub mod ping; +pub mod profile; diff --git a/src/lang_struct/general/module.rs b/src/lang_struct/general/module.rs new file mode 100644 index 00000000..baa2fe43 --- /dev/null +++ b/src/lang_struct/general/module.rs @@ -0,0 +1,39 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ModuleLocalised { + pub on: String, + pub off: String, +} + +pub async fn load_localization_module_activation( + guild_id: String, +) -> Result<ModuleLocalised, AppError> { + let mut file = File::open("json/message/general/module.json") + .map_err(|_| LocalisationFileError(String::from("File module.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File module.json can't be read.")))?; + + let json_data: HashMap<String, ModuleLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse module.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let module_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(module_localised_text.clone()) +} diff --git a/src/lang_struct/general/ping.rs b/src/lang_struct/general/ping.rs new file mode 100644 index 00000000..1084de71 --- /dev/null +++ b/src/lang_struct/general/ping.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PingLocalised { + pub title: String, + pub desc: String, +} + +pub async fn load_localization_ping(guild_id: String) -> Result<PingLocalised, AppError> { + let mut file = File::open("json/message/general/ping.json") + .map_err(|_| LocalisationFileError(String::from("File ping.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File ping.json can't be read.")))?; + + let json_data: HashMap<String, PingLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse ping.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let ping_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(ping_localised_text.clone()) +} diff --git a/src/lang_struct/general/profile.rs b/src/lang_struct/general/profile.rs new file mode 100644 index 00000000..29c66df4 --- /dev/null +++ b/src/lang_struct/general/profile.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; + +use serde::{Deserialize, Serialize}; + +use crate::common::get_guild_lang::get_guild_langage; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{ + LocalisationFileError, LocalisationParsingError, LocalisationReadError, NoLangageError, +}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct ProfileLocalised { + pub title: String, + pub desc: String, +} + +pub async fn load_localization_profile(guild_id: String) -> Result<ProfileLocalised, AppError> { + let mut file = File::open("json/message/general/profile.json") + .map_err(|_| LocalisationFileError(String::from("File profile.json not found.")))?; + + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|_| LocalisationReadError(String::from("File profile.json can't be read.")))?; + + let json_data: HashMap<String, ProfileLocalised> = serde_json::from_str(&json) + .map_err(|_| LocalisationParsingError(String::from("Failing to parse profile.json.")))?; + + let lang_choice = get_guild_langage(guild_id).await; + + let profile_localised_text = json_data + .get(lang_choice.as_str()) + .ok_or(NoLangageError(String::from("not found")))?; + + Ok(profile_localised_text.clone()) +} diff --git a/src/structure/register/mod.rs b/src/lang_struct/mod.rs similarity index 100% rename from src/structure/register/mod.rs rename to src/lang_struct/mod.rs diff --git a/src/logger.rs b/src/logger.rs index 7f73eb49..bd4904de 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,31 +1,162 @@ -use log::{Level, Metadata, Record}; -use log::{LevelFilter, SetLoggerError}; +use std::fs; +use std::fs::{File, OpenOptions}; +use std::io::Error; +use std::io::Write; +use std::path::Path; +use std::str::FromStr; -static LOGGER: SimpleLogger = SimpleLogger; +use chrono::Utc; +use tracing_core::*; +use tracing_subscriber::filter::{Directive, EnvFilter}; +use tracing_subscriber::layer::{Context, SubscriberExt}; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{fmt, Layer}; +use uuid::Uuid; -pub fn init(log: String) -> Result<(), SetLoggerError> { - let level_filter = match log.as_str() { - "info" => LevelFilter::Info, - "warn" => LevelFilter::Warn, - "error" => LevelFilter::Error, - _ => LevelFilter::Debug, +use crate::constant::OTHER_CRATE_LEVEL; +use crate::error_enum::AppError; +use crate::error_enum::AppError::SetLoggerError; + +/// Initializes the logger with the specified log level filter. +/// +/// # Arguments +/// +/// * `log` - A string representing the desired log level. Possible values are "info", +/// "warn", "error", "debug", and "trace". +/// +/// # Returns +/// +/// A `Result` indicating success or failure. If successful, the logger is initialized +/// and the log level filter is set. If an error occurs, the corresponding error is + +pub fn init_logger(log: &str) -> Result<(), AppError> { + let kasuki_filter = match log { + "warn" => "kasuki=warn", + "error" => "kasuki=error", + "debug" => "kasuki=debug", + "trace" => "kasuki=trace", + _ => "kasuki=info", + }; + + let crate_log = match Directive::from_str(OTHER_CRATE_LEVEL) { + Ok(d) => d, + Err(e) => { + eprintln!("{}", e); + return Err(SetLoggerError(String::from("Error creating the Logger"))); + } + }; + let kasuki_log = match Directive::from_str(kasuki_filter) { + Ok(d) => d, + Err(e) => { + eprintln!("{}", e); + return Err(SetLoggerError(String::from("Error creating the Logger"))); + } }; - log::set_logger(&LOGGER).map(|()| log::set_max_level(level_filter)) + let filter = EnvFilter::from_default_env() + .add_directive(crate_log) + .add_directive(kasuki_log); + + let format = fmt::layer().with_ansi(true); + + tracing_subscriber::registry() + .with(filter) + .with(format) + .with(SimpleSubscriber::new()) + .init(); + + Ok(()) } -struct SimpleLogger; +/// Removes old logs from the "./logs" directory. +/// +/// # Errors +/// +/// This function will return an error if: +/// +/// - The directory "./logs" does not exist. +/// - There is an error reading the directory. +/// - There is an error getting the metadata of a file. +/// - There is an error removing a file. +pub fn remove_old_logs() -> Result<(), Error> { + let path = Path::new("./logs"); + let mut entries: Vec<_> = fs::read_dir(path)?.filter_map(Result::ok).collect(); -impl log::Log for SimpleLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - metadata.level() <= Level::Warn || metadata.target().starts_with("kasuki") + // Sort the entries by modification time + entries.sort_by_key(|e| e.metadata().unwrap().modified().unwrap()); + + // Remove the oldest ones until there are only 5 left + for entry in entries.iter().clone().take(entries.len().saturating_sub(5)) { + fs::remove_file(entry.path())?; } - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - println!("{} - {}", record.level(), record.args()); + Ok(()) +} + +/// Creates a directory named "logs" if it doesn't exist. +/// # Errors +/// +/// Returns an `std::io::Result` indicating whether the directory was successfully created or encountered an error. +/// +/// # Remarks +/// +/// If the directory already exists, this function does nothing and returns `Ok(())`. +pub fn create_log_directory() -> std::io::Result<()> { + fs::create_dir_all("./logs") +} + +struct SimpleSubscriber { + uuid: Uuid, +} + +impl SimpleSubscriber { + pub fn new() -> Self { + let uuid_generated = Uuid::new_v4(); + let _ = File::create(format!("./logs/log_{}.log", uuid_generated)).is_ok(); + SimpleSubscriber { + uuid: uuid_generated, } } +} + +impl<S: Subscriber> Layer<S> for SimpleSubscriber { + fn on_event(&self, event: &Event, _ctx: Context<S>) { + let level = event.metadata().level(); + let mut file = OpenOptions::new() + .write(true) + .append(true) + .open(format!("./logs/log_{}.log", &self.uuid)) + .unwrap(); + let level_str = level.to_string(); + let target = event.metadata().target(); + let target_str = target; + let mut visitor = MessageVisitor::new(); + event.record(&mut visitor); + let message = visitor.message; + let date = Utc::now().to_string(); + + let text = format!("{} - {} | {} - {}", date, level_str, target_str, message); + + writeln!(file, "{}", text).unwrap(); + } +} - fn flush(&self) {} +struct MessageVisitor { + message: String, +} + +impl MessageVisitor { + fn new() -> Self { + MessageVisitor { + message: String::new(), + } + } +} + +impl field::Visit for MessageVisitor { + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + if field.name() == "message" { + self.message = format!("{:?}", value); + } + } } diff --git a/src/main.rs b/src/main.rs index 10808af3..2d5c8593 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,55 +1,33 @@ -extern crate core; - -use std::collections::HashMap; use std::env; -use std::fs::File; -use std::io::Read; use std::sync::Arc; -use std::time::Duration; - -use chrono::Utc; -use log::{debug, error, info, set_max_level, trace, warn, LevelFilter, Log}; -use serenity::async_trait; -use serenity::client::Context; -use serenity::model::application::command::Command; -use serenity::model::application::interaction::Interaction; -use serenity::model::application::interaction::InteractionResponseType; -use serenity::model::gateway::Activity; -use serenity::model::gateway::Ready; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::model::Timestamp; -use serenity::prelude::*; -use serenity::utils::Colour; -use tokio::time::sleep; - -use structure::struct_shard_manager::ShardManagerContainer; - -use crate::cmd::ai_module::{image, transcript, translation}; -use crate::cmd::anilist_module::send_activity::manage_activity; -use crate::cmd::anilist_module::{ - add_activity, anime, character, compare, level, ln, manga, random, register, search, staff, - studio, user, waifu, -}; -use crate::cmd::general_module::module_activation::check_activation_status; -use crate::cmd::general_module::{ - avatar, banner, credit, info, lang, module_activation, ping, profile, -}; -use crate::constant::{ACTIVITY_NAME, COLOR}; -use crate::function::error_management::no_lang_error::no_langage_error; -use crate::function::general::get_guild_langage::get_guild_langage; -use crate::function::sql::sqlite::pool::get_sqlite_pool; -use crate::logger::init; -use crate::structure::anilist::media::struct_autocomplete_media; -use crate::structure::anilist::user::struct_autocomplete_user; -use crate::structure::embed::error::ErrorLocalisedText; -mod available_lang; -mod cmd; +use serenity::all::{ActivityData, Context, EventHandler, GatewayIntents, Interaction, Ready}; +use serenity::{async_trait, Client}; +use tracing::{debug, error, info, trace}; + +use struct_shard_manager::ShardManagerContainer; + +use crate::activity::anime_activity::manage_activity; +use crate::command_autocomplete::autocomplete_dispatch::autocomplete_dispatching; +use crate::command_register::command_registration::creates_commands; +use crate::command_run::command_dispatch::command_dispatching; +use crate::constant::ACTIVITY_NAME; +use crate::logger::{create_log_directory, init_logger, remove_old_logs}; +use crate::sqls::general::sql::init_sql_database; + +mod activity; +mod anilist_struct; +mod command_autocomplete; +mod command_register; +mod command_run; +mod common; mod constant; -mod function; +mod error_enum; +mod error_management; +mod lang_struct; mod logger; -mod structure; -mod tests; +mod sqls; +pub mod struct_shard_manager; struct Handler; @@ -57,219 +35,79 @@ struct Handler; impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { // Add activity to the bot as the type in activity_type and with ACTIVITY_NAME as name - let activity_type: Activity = Activity::playing(ACTIVITY_NAME); - ctx.set_activity(activity_type).await; + let activity_type = Some(ActivityData::custom(ACTIVITY_NAME)); + ctx.set_activity(activity_type); info!( "Shard {:?} of {} is connected!", ready.shard, ready.user.name ); + let is_ok = env::var("REMOVE_OLD_COMMAND_ON_STARTUP") + .unwrap_or("false".to_string()) + .to_lowercase() + .as_str() + == "true"; - // Create all the commande found in cmd. (if a command is added it will need to be added here). - let guild_command = Command::set_global_application_commands(&ctx.http, |commands| { - commands - // General module. - .create_application_command(|command| ping::register(command)) - .create_application_command(|command| lang::register(command)) - .create_application_command(|command| info::register(command)) - .create_application_command(|command| banner::register(command)) - .create_application_command(|command| profile::register(command)) - .create_application_command(|command| module_activation::register(command)) - .create_application_command(|command| credit::register(command)) - .create_application_command(|command| avatar::register(command)) - // Anilist module. - .create_application_command(|command| anime::register(command)) - .create_application_command(|command| character::register(command)) - .create_application_command(|command| compare::register(command)) - .create_application_command(|command| level::register(command)) - .create_application_command(|command| ln::register(command)) - .create_application_command(|command| manga::register(command)) - .create_application_command(|command| random::register(command)) - .create_application_command(|command| register::register(command)) - .create_application_command(|command| search::register(command)) - .create_application_command(|command| staff::register(command)) - .create_application_command(|command| user::register(command)) - .create_application_command(|command| waifu::register(command)) - .create_application_command(|command| studio::register(command)) - .create_application_command(|command| add_activity::register(command)) - // AI module. - .create_application_command(|command| image::register(command)) - .create_application_command(|command| transcript::register(command)) - .create_application_command(|command| translation::register(command)) - }) - .await; - match guild_command { - Ok(commands) => { - for command in commands { - trace!("Command {}, Version {}", command.name, command.version); - } - } - Err(e) => { - error!("Error while creating command: {}", e) - } - } + creates_commands(&ctx.http, is_ok).await; } async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - if let Interaction::ApplicationCommand(command) = interaction { + if let Interaction::Command(command) = interaction.clone() { info!( "Received command interaction: {}, Option: {:#?}, User: {}({})", command.data.name, command.data.options, command.user.name, command.user.id ); - match command.data.name.as_str() { - // General module. - "ping" => ping::run(&ctx, &command).await, - "lang" => lang::run(&command.data.options, &ctx, &command).await, - "info" => info::run(&ctx, &command).await, - "banner" => banner::run(&command.data.options, &ctx, &command).await, - "profile" => profile::run(&command.data.options, &ctx, &command).await, - "module" => module_activation::run(&command.data.options, &ctx, &command).await, - "credit" => credit::run(&ctx, &command).await, - "avatar" => avatar::run(&command.data.options, &ctx, &command).await, - - // Anilist module - "anime" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - anime::run(&command.data.options, &ctx, &command).await - } - "character" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - character::run(&command.data.options, &ctx, &command).await - } - "compare" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - compare::run(&command.data.options, &ctx, &command).await - } - "level" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - level::run(&command.data.options, &ctx, &command).await - } - "ln" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - ln::run(&command.data.options, &ctx, &command).await - } - "manga" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - manga::run(&command.data.options, &ctx, &command).await - } - "random" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - random::run(&command.data.options, &ctx, &command).await - } - "register" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - register::run(&command.data.options, &ctx, &command).await - } - "search" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - search::run(&command.data.options, &ctx, &command).await - } - "staff" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - staff::run(&command.data.options, &ctx, &command).await - } - "user" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - user::run(&command.data.options, &ctx, &command).await - } - "waifu" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - waifu::run(&ctx, &command).await - } - "studio" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - studio::run(&command.data.options, &ctx, &command).await - } - "add_activity" => { - if !check_if_anime_is_on(&command, COLOR, &ctx).await { - return; - } - add_activity::run(&command.data.options, &ctx, &command).await - } - - // AI module - "image" => { - if !check_if_ai_is_on(&command, COLOR, &ctx).await { - return; - } - image::run(&command.data.options, &ctx, &command).await - } - "transcript" => { - if !check_if_ai_is_on(&command, COLOR, &ctx).await { - return; - } - transcript::run(&command.data.options, &ctx, &command).await - } - "translation" => { - if !check_if_ai_is_on(&command, COLOR, &ctx).await { - return; - } - translation::run(&command.data.options, &ctx, &command).await - } - - _ => return, + trace!("{:#?}", command); + match command_dispatching(ctx, command).await { + Err(e) => error_management::error_dispatch::command_dispatching(e).await, + Ok(a) => a, }; // check if the command was successfully done. - } else if let Interaction::Autocomplete(command) = interaction { - match command.data.name.as_str() { - "anime" => struct_autocomplete_media::autocomplete(ctx, command).await, - "manga" => manga::autocomplete(ctx, command).await, - "ln" => ln::autocomplete(ctx, command).await, - "character" => character::autocomplete(ctx, command).await, - "staff" => staff::autocomplete(ctx, command).await, - "user" => struct_autocomplete_user::autocomplete(ctx, command).await, - "compare" => compare::autocomplete(ctx, command).await, - "level" => struct_autocomplete_user::autocomplete(ctx, command).await, - "studio" => studio::autocomplete(ctx, command).await, - "add_activity" => struct_autocomplete_media::autocomplete(ctx, command).await, - _ => print!(""), - } + } else if let Interaction::Autocomplete(command) = interaction.clone() { + autocomplete_dispatching(ctx, command).await; } } } #[tokio::main] async fn main() { + match init_sql_database().await { + Ok(_) => {} + Err(e) => { + error!("{:?}", e); + return; + } + } + // Configure the client with your Discord bot token in the environment. let my_path = "./.env"; let path = std::path::Path::new(my_path); let _ = dotenv::from_path(path); - let env = env::var("LOG").unwrap_or_else(|_| "info".to_string()); - let log = env.to_lowercase(); - match init(log) { + let env = env::var("RUST_LOG") + .unwrap_or("info".to_string()) + .to_lowercase(); + let log = env.as_str(); + match create_log_directory() { + Ok(_) => {} + Err(_) => return, + }; + + let _ = remove_old_logs().is_ok(); + + match init_logger(log) { Ok(_) => {} Err(e) => { - eprintln!("{}", e); + eprintln!("{:?}", e); return; } }; + + tokio::spawn(async move { + info!("Launching the activity management thread!"); + manage_activity().await + }); + info!("starting the bot."); let token = match env::var("DISCORD_TOKEN") { Ok(token) => { @@ -297,170 +135,15 @@ async fn main() { } }; - tokio::spawn(async move { - // create_server().await.expect("Web server running"); - }); - - let manager = client.shard_manager.clone(); - - tokio::spawn(async move { - let database_url = "./data.db"; - let pool = get_sqlite_pool(database_url).await; - - match sqlx::query( - "CREATE TABLE IF NOT EXISTS ping_history ( - shard_id TEXT, - timestamp TEXT, - ping TEXT NOT NULL, - PRIMARY KEY (shard_id, timestamp) - )", - ) - .execute(&pool) - .await - { - Ok(_) => {} - Err(e) => { - error!("Error while creating the table: {}", e) - } - }; - - loop { - sleep(Duration::from_secs(600)).await; - - let lock = manager.lock().await; - let shard_runners = lock.runners.lock().await; - - for (id, runner) in shard_runners.iter() { - let shard_id = id.0.to_string(); - let latency_content = runner.latency.unwrap_or(Duration::from_secs(0)); - let latency = format!("{:?}", latency_content); - let now = Utc::now().timestamp().to_string(); - match sqlx::query( - "INSERT OR REPLACE INTO ping_history (shard_id, timestamp, ping) VALUES (?, ?, ?)", - ) - .bind(shard_id) - .bind(now) - .bind(latency) - .execute(&pool) - .await - { - Ok(_) => {} - Err(e) => { - error!("Error while creating the table: {}", e) - } - }; - } - } - }); - - tokio::spawn(async move { manage_activity().await }); - { let mut data = client.data.write().await; - data.insert::<ShardManagerContainer>(Arc::clone(&client.shard_manager)); + let shard_manager = &client.shard_manager; + + data.insert::<ShardManagerContainer>(Arc::clone(shard_manager)); } if let Err(why) = client.start_autosharded().await { error!("Client error: {:?}", why); } } - -pub async fn check_if_anime_is_on( - command: &ApplicationCommandInteraction, - color: Colour, - ctx: &Context, -) -> bool { - let guild_id = match command.guild_id { - Some(id) => id, - None => { - error!("No guild id"); - return true; - } - } - .0 - .to_string() - .clone(); - if !check_activation_status("ANILIST", guild_id.clone()).await { - send_deactivated_message(command, color, ctx, guild_id).await; - false - } else { - true - } -} - -pub async fn check_if_ai_is_on( - command: &ApplicationCommandInteraction, - color: Colour, - ctx: &Context, -) -> bool { - let guild_id = match command.guild_id { - Some(id) => id, - None => { - error!("No guild id"); - return true; - } - } - .0 - .to_string() - .clone(); - if !check_activation_status("AI", guild_id.clone()).await { - send_deactivated_message(command, color, ctx, guild_id).await; - false - } else { - true - } -} - -pub async fn send_deactivated_message( - command: &ApplicationCommandInteraction, - color: Colour, - ctx: &Context, - guild_id: String, -) { - let path = "./lang_file/embed/error.json"; - let mut file = match File::open(path) { - Ok(file) => file, - Err(e) => { - error!("Error while opening lang file at {}: {}", path, e); - return; - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(e) => { - error!("Failed to read the file {}: {}", path, e) - } - }; - let json_data: HashMap<String, ErrorLocalisedText> = match serde_json::from_str(&json) { - Ok(json) => json, - Err(e) => { - error!("Error when paring the json file: {}", e); - return; - } - }; - - let lang_choice = get_guild_langage(guild_id.clone()).await; - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.embed(|m| { - m.title(&localised_text.error_title) - .description(&localised_text.module_off) - .timestamp(Timestamp::now()) - .color(color) - }) - }) - }) - .await - { - println!("Cannot respond to slash command: {}", why); - } - } else { - no_langage_error(ctx, command).await - } -} diff --git a/src/sqls/general/cache.rs b/src/sqls/general/cache.rs new file mode 100644 index 00000000..68f8ca7b --- /dev/null +++ b/src/sqls/general/cache.rs @@ -0,0 +1,62 @@ +use std::env; + +use serde_json::Value; + +use crate::error_enum::AppError; +use crate::sqls::sqlite::cache::{ + get_database_cache_sqlite, get_database_random_cache_sqlite, set_database_cache_sqlite, + set_database_random_cache_sqlite, +}; + +pub async fn get_database_random_cache( + random_type: &str, +) -> Result<(Option<String>, Option<i64>, Option<i64>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_database_random_cache_sqlite(random_type).await + } else if db_type == *"postgresql" { + Ok((None, None, None)) + } else { + get_database_random_cache_sqlite(random_type).await + } +} + +pub async fn set_database_random_cache( + random_type: &str, + cached_response: &str, + now: i64, + previous_page: i64, +) -> Result<(), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + set_database_random_cache_sqlite(random_type, cached_response, now, previous_page).await + } else if db_type == *"postgresql" { + Ok(()) + } else { + set_database_random_cache_sqlite(random_type, cached_response, now, previous_page).await + } +} + +pub async fn get_database_cache( + json: Value, +) -> Result<(Option<String>, Option<String>, Option<i64>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_database_cache_sqlite(json).await + } else if db_type == *"postgresql" { + Ok((None, None, None)) + } else { + get_database_cache_sqlite(json).await + } +} + +pub async fn set_database_cache(json: Value, resp: String) -> Result<(), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + set_database_cache_sqlite(json, resp).await + } else if db_type == *"postgresql" { + Ok(()) + } else { + set_database_cache_sqlite(json, resp).await + } +} diff --git a/src/sqls/general/data.rs b/src/sqls/general/data.rs new file mode 100644 index 00000000..0ec794fc --- /dev/null +++ b/src/sqls/general/data.rs @@ -0,0 +1,200 @@ +use std::env; + +use crate::anilist_struct::run::minimal_anime::ActivityData; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{SqlInsertError, SqlSelectError}; +use crate::sqls::sqlite::data::{ + get_data_activity_sqlite, get_data_guild_lang_sqlite, get_data_guild_langage_sqlite, + get_data_module_activation_kill_switch_status_sqlite, get_data_module_activation_status_sqlite, + get_one_activity_sqlite, get_registered_user_sqlite, remove_data_activity_status_sqlite, + set_data_activity_sqlite, set_data_guild_langage_sqlite, + set_data_module_activation_status_sqlite, set_data_ping_history_sqlite, + set_registered_user_sqlite, +}; + +pub async fn set_data_ping_history(shard_id: String, latency: String) -> Result<(), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + set_data_ping_history_sqlite(shard_id, latency).await + } else if db_type == *"postgresql" { + Ok(()) + } else { + set_data_ping_history_sqlite(shard_id, latency).await + } +} + +pub async fn get_data_guild_langage( + guild_id: &str, +) -> Result<(Option<String>, Option<String>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_data_guild_langage_sqlite(guild_id).await + } else if db_type == *"postgresql" { + Ok((None, None)) + } else { + get_data_guild_langage_sqlite(guild_id).await + } +} + +pub async fn set_data_guild_langage(guild_id: &String, lang: &String) -> Result<(), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + set_data_guild_langage_sqlite(&guild_id, lang).await + } else if db_type == *"postgresql" { + Ok(()) + } else { + set_data_guild_langage_sqlite(&guild_id, lang).await + } +} + +pub async fn get_data_activity(now: String) -> Result<Vec<ActivityData>, AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_data_activity_sqlite(now).await + } else if db_type == *"postgresql" { + Ok(Vec::new()) + } else { + get_data_activity_sqlite(now).await + } +} + +pub async fn set_data_activity( + anime_id: i32, + timestamp: i64, + guild_id: String, + webhook: String, + episode: i32, + name: String, + delays: i64, +) -> Result<(), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + set_data_activity_sqlite( + anime_id, timestamp, guild_id, webhook, episode, name, delays, + ) + .await + } else if db_type == *"postgresql" { + Ok(()) + } else { + set_data_activity_sqlite( + anime_id, timestamp, guild_id, webhook, episode, name, delays, + ) + .await + } +} + +pub async fn get_data_module_activation_status( + guild_id: &String, +) -> Result<(Option<String>, Option<bool>, Option<bool>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_data_module_activation_status_sqlite(guild_id).await + } else if db_type == *"postgresql" { + Err(SqlSelectError(String::from("Error"))) + } else { + get_data_module_activation_status_sqlite(guild_id).await + } +} + +pub async fn set_data_module_activation_status( + guild_id: &String, + anilist_value: bool, + ai_value: bool, +) -> Result<(), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + set_data_module_activation_status_sqlite(guild_id, anilist_value, ai_value).await + } else if db_type == *"postgresql" { + Err(SqlInsertError(String::from("Error"))) + } else { + set_data_module_activation_status_sqlite(guild_id, anilist_value, ai_value).await + } +} + +pub async fn remove_data_module_activation_status( + server_id: String, + anime_id: String, +) -> Result<(), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + remove_data_activity_status_sqlite(server_id, anime_id).await + } else if db_type == *"postgresql" { + Err(SqlInsertError(String::from("Error"))) + } else { + remove_data_activity_status_sqlite(server_id, anime_id).await + } +} + +pub async fn get_one_activity( + anime_id: i32, + server_id: String, +) -> Result< + ( + Option<String>, + Option<String>, + Option<String>, + Option<String>, + ), + AppError, +> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_one_activity_sqlite(server_id, anime_id).await + } else if db_type == *"postgresql" { + Ok((None, None, None, None)) + } else { + get_one_activity_sqlite(server_id, anime_id).await + } +} + +pub async fn get_data_guild_lang( + guild_id: String, +) -> Result<(Option<String>, Option<String>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_data_guild_lang_sqlite(guild_id).await + } else if db_type == *"postgresql" { + Ok((None, None)) + } else { + get_data_guild_lang_sqlite(guild_id).await + } +} + +pub async fn get_registered_user( + user_id: &String, +) -> Result<(Option<String>, Option<String>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_registered_user_sqlite(user_id).await + } else if db_type == *"postgresql" { + Ok((None, None)) + } else { + get_registered_user_sqlite(user_id).await + } +} + +pub async fn set_registered_user( + user_id: &String, + username: &String, +) -> Result<(Option<String>, Option<String>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + set_registered_user_sqlite(user_id, username).await + } else if db_type == *"postgresql" { + Ok((None, None)) + } else { + set_registered_user_sqlite(user_id, username).await + } +} + +pub async fn get_data_module_activation_kill_switch_status( +) -> Result<(Option<String>, Option<bool>, Option<bool>), AppError> { + let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); + if db_type == *"sqlite" { + get_data_module_activation_kill_switch_status_sqlite().await + } else if db_type == *"postgresql" { + Ok((None, None, None)) + } else { + get_data_module_activation_kill_switch_status_sqlite().await + } +} diff --git a/src/sqls/general/mod.rs b/src/sqls/general/mod.rs new file mode 100644 index 00000000..763d8099 --- /dev/null +++ b/src/sqls/general/mod.rs @@ -0,0 +1,3 @@ +pub mod cache; +pub mod data; +pub mod sql; diff --git a/src/function/sql/sql.rs b/src/sqls/general/sql.rs similarity index 76% rename from src/function/sql/sql.rs rename to src/sqls/general/sql.rs index 454412a3..548e29ef 100644 --- a/src/function/sql/sql.rs +++ b/src/sqls/general/sql.rs @@ -1,6 +1,8 @@ -use crate::function::sql::sqlite::init::init_sqlite; use std::env; +use crate::error_enum::AppError; +use crate::sqls::sqlite::init::init_sqlite; + /// Asynchronously establish a connection pool to a SQLite database. /// /// # Arguments @@ -15,23 +17,16 @@ use std::env; /// /// * This function will panic if it fails to establish a connection to the database. /// -/// # Examples -/// -/// ```rust -/// let database_url = "sqlite:./my_db.db"; -/// let pool = get_pool(database_url).await; -/// ``` -/// /// # Notes /// /// * This function is async and should be awaited. /// - -pub async fn init() { +pub async fn init_sql_database() -> Result<(), AppError> { let db_type = env::var("DB_TYPE").unwrap_or("sqlite".to_string()); if db_type == *"sqlite" { init_sqlite().await } else if db_type == *"postgresql" { + Ok(()) } else { init_sqlite().await } diff --git a/src/function/sql/mod.rs b/src/sqls/mod.rs similarity index 67% rename from src/function/sql/mod.rs rename to src/sqls/mod.rs index 4e5f0894..f22d34ba 100644 --- a/src/function/sql/mod.rs +++ b/src/sqls/mod.rs @@ -1,3 +1,3 @@ +pub mod general; pub mod postgresql; -pub mod sql; pub mod sqlite; diff --git a/src/cmd/games_module/mod.rs b/src/sqls/postgresql/mod.rs similarity index 100% rename from src/cmd/games_module/mod.rs rename to src/sqls/postgresql/mod.rs diff --git a/src/sqls/sqlite/cache.rs b/src/sqls/sqlite/cache.rs new file mode 100644 index 00000000..54c0bbe7 --- /dev/null +++ b/src/sqls/sqlite/cache.rs @@ -0,0 +1,111 @@ +use chrono::Utc; +use serde_json::Value; + +use crate::constant::CACHE_SQLITE_DB; +use crate::error_enum::AppError; +use crate::error_enum::AppError::SqlInsertError; +use crate::sqls::sqlite::pool::get_sqlite_pool; + +/// Retrieves the cache statistics for a given random type from a SQLite database using a connection pool. +/// The cache statistics include the response, last updated timestamp, and last page. +/// If the cache statistics for the given random type are found in the database, they are returned. +/// If no cache statistics are found, `None` is returned for each value. +/// +/// # Arguments +/// +/// * `random_type` - The random type to retrieve cache statistics for. +/// +/// # Returns +/// +/// A tuple containing the response, last updated timestamp, and last page of the cache statistics. +pub async fn get_database_random_cache_sqlite( + random_type: &str, +) -> Result<(Option<String>, Option<i64>, Option<i64>), AppError> { + let pool = get_sqlite_pool(CACHE_SQLITE_DB).await?; + let row: (Option<String>, Option<i64>, Option<i64>) = + sqlx::query_as("SELECT response, last_updated, last_page FROM cache_stats WHERE key = ?") + .bind(random_type) + .fetch_one(&pool) + .await + .unwrap_or((None, None, None)); + pool.close().await; + Ok(row) +} + +/// Sets the database random cache for SQLite. +/// +/// This function inserts or replaces a record in the `cache_stats` table of the SQLite database with the given parameters. +/// +/// # Arguments +/// +/// * `random_type` - The key identifying the random type. +/// * `cached_response` - The cached response to be stored. +/// * `now` - The timestamp for the last update. +/// * `previous_page` - The value of the last page. +/// +pub async fn set_database_random_cache_sqlite( + random_type: &str, + cached_response: &str, + now: i64, + previous_page: i64, +) -> Result<(), AppError> { + let pool = get_sqlite_pool(CACHE_SQLITE_DB).await?; + sqlx::query("INSERT OR REPLACE INTO cache_stats (key, response, last_updated, last_page) VALUES (?, ?, ?, ?)") + .bind(random_type) + .bind(cached_response) + .bind(now) + .bind(previous_page) + .execute(&pool) + .await + .map_err(|_| SqlInsertError(String::from("Failed to insert data.")))?; + pool.close().await; + Ok(()) +} + +/// Retrieves data from a SQLite database cache based on the provided JSON. +/// +/// # Arguments +/// +/// * `json` - The JSON data to search for in the cache. +/// +/// # Returns +/// +/// A tuple containing the JSON, response, and last_updated values from the cache. +/// If no matching JSON is found in the cache, the returned tuple will contain `None` values. +/// +pub async fn get_database_cache_sqlite( + json: Value, +) -> Result<(Option<String>, Option<String>, Option<i64>), AppError> { + let pool = get_sqlite_pool(CACHE_SQLITE_DB).await?; + let row: (Option<String>, Option<String>, Option<i64>) = + sqlx::query_as("SELECT json, response, last_updated FROM request_cache WHERE json = ?") + .bind(json.clone()) + .fetch_one(&pool) + .await + .unwrap_or((None, None, None)); + pool.close().await; + Ok(row) +} + +/// Sets the database cache for SQLite. +/// +/// # Arguments +/// +/// * `json` - The JSON value to be stored in the cache. +/// * `resp` - The response string to be stored in the cache. +/// +pub async fn set_database_cache_sqlite(json: Value, resp: String) -> Result<(), AppError> { + let pool = get_sqlite_pool(CACHE_SQLITE_DB).await?; + let now = Utc::now().timestamp(); + sqlx::query( + "INSERT OR REPLACE INTO request_cache (json, response, last_updated) VALUES (?, ?, ?)", + ) + .bind(json.clone()) + .bind(resp.clone()) + .bind(now) + .execute(&pool) + .await + .map_err(|_| SqlInsertError(String::from("Failed to insert data.")))?; + pool.close().await; + Ok(()) +} diff --git a/src/sqls/sqlite/data.rs b/src/sqls/sqlite/data.rs new file mode 100644 index 00000000..d84a3f24 --- /dev/null +++ b/src/sqls/sqlite/data.rs @@ -0,0 +1,281 @@ +use chrono::Utc; + +use crate::anilist_struct::run::minimal_anime::ActivityData; +use crate::constant::DATA_SQLITE_DB; +use crate::error_enum::AppError; +use crate::error_enum::AppError::SqlInsertError; +use crate::sqls::sqlite::pool::get_sqlite_pool; + +/// Inserts or replaces a record in the `ping_history` table of a SQLite database. +/// +/// The function takes a 'shard_id' and a 'latency', both of type `String`, as input. +/// It attempts to insert or replace a record with these values into the `ping_history` table. +/// The `shard_id` and `latency` are most likely related to a latency reported for a specific shard ID. +/// The current timestamp is also stored with each record. +/// The function is asynchronous and returns nothing. +/// +/// # Arguments +/// +/// * `shard_id` - A String containing the ID of a shard. +/// * `latency` - A String containing the latency value. +/// +/// # Errors +/// +/// This function will log errors encountered when executing the SQL command, but does not return them. +pub async fn set_data_ping_history_sqlite( + shard_id: String, + latency: String, +) -> Result<(), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let now = Utc::now().timestamp().to_string(); + sqlx::query("INSERT OR REPLACE INTO ping_history (shard_id, timestamp, ping) VALUES (?, ?, ?)") + .bind(shard_id) + .bind(now) + .bind(latency) + .execute(&pool) + .await + .map_err(|_| SqlInsertError(String::from("Failed to insert into the table.")))?; + pool.close().await; + Ok(()) +} + +/// This function retrieves language data for a guild from a SQLite database. +/// +/// # Arguments +/// +/// * `guild_id` - A string representing the ID of the guild. +/// +/// # Returns +/// +/// A tuple containing the language and guild ID as optional strings. +/// If the data is found in the database, it will be returned. +/// If not found, both values will be `None`. +pub async fn get_data_guild_langage_sqlite( + guild_id: &str, +) -> Result<(Option<String>, Option<String>), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let row: (Option<String>, Option<String>) = + sqlx::query_as("SELECT lang_struct, guild FROM guild_lang WHERE guild = ?") + .bind(guild_id) + .fetch_one(&pool) + .await + .unwrap_or((None, None)); + pool.close().await; + Ok(row) +} + +/// Sets the language for a guild in the SQLite database. +/// +/// # Arguments +/// +/// * `guild_id` - The ID of the guild. +/// * `lang_struct` - The language to set for the guild. +pub async fn set_data_guild_langage_sqlite( + guild_id: &&String, + lang: &String, +) -> Result<(), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + sqlx::query("INSERT OR REPLACE INTO guild_lang (guild, lang_struct) VALUES (?, ?)") + .bind(guild_id) + .bind(lang) + .execute(&pool) + .await + .map_err(|_| SqlInsertError(String::from("Failed to insert into the table.")))?; + pool.close().await; + Ok(()) +} + +/// Retrieves activity data from SQLite database. +/// +/// # Returns +/// +/// A `Vec<ActivityData>` containing the retrieved data. +/// +pub async fn get_data_activity_sqlite(now: String) -> Result<Vec<ActivityData>, AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let rows: Vec<ActivityData> = sqlx::query_as( + "SELECT anime_id, timestamp, server_id, webhook, episode, name, delays FROM activity_data WHERE timestamp = ?", + ) + .bind(now) + .fetch_all(&pool) + .await + .unwrap(); + Ok(rows) +} + +/// Sets data activity in SQLite database. +/// +/// # Arguments +/// +/// * `anime_id` - The ID of the anime. +/// * `timestamp` - The timestamp. +/// * `guild_id` - The ID of the guild. +/// * `webhook` - The webhook URL. +/// * `episode` - The episode number. +/// * `name` - The name of the anime. +/// * `delays` - The delays. +/// +pub async fn set_data_activity_sqlite( + anime_id: i32, + timestamp: i64, + guild_id: String, + webhook: String, + episode: i32, + name: String, + delays: i64, +) -> Result<(), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + sqlx::query( + "INSERT OR REPLACE INTO activity_data (anime_id, timestamp, server_id, webhook, episode, name, delays) VALUES (?, ?, ?, ?, ?, ?, ?)", + ) + .bind(anime_id) + .bind(timestamp) + .bind(guild_id) + .bind(webhook) + .bind(episode) + .bind(name) + .bind(delays) + .execute(&pool) + .await.map_err(|_| SqlInsertError(String::from("Failed to insert into the table.")))?; + pool.close().await; + Ok(()) +} + +pub async fn get_data_module_activation_status_sqlite( + guild_id: &String, +) -> Result<(Option<String>, Option<bool>, Option<bool>), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let row: (Option<String>, Option<bool>, Option<bool>) = sqlx::query_as( + "SELECT guild_id, ai_module, anilist_module FROM module_activation WHERE guild = ?", + ) + .bind(guild_id) + .fetch_one(&pool) + .await + .unwrap_or((None, None, None)); + pool.close().await; + Ok(row) +} + +pub async fn set_data_module_activation_status_sqlite( + guild_id: &String, + anilist_value: bool, + ai_value: bool, +) -> Result<(), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let _ = sqlx::query( + "INSERT OR REPLACE INTO module_activation (guild_id, anilist_module, ai_module) VALUES (?, ?, ?)", + ) + .bind(guild_id) + .bind(anilist_value) + .bind(ai_value) + .execute(&pool) + .await + .map_err(|_| SqlInsertError(String::from("Failed to insert data.")))?; + pool.close().await; + Ok(()) +} + +pub async fn remove_data_activity_status_sqlite( + server_id: String, + anime_id: String, +) -> Result<(), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let _ = sqlx::query("DELETE FROM activity_data WHERE anime_id = ? AND server_id = ?") + .bind(anime_id) + .bind(server_id) + .execute(&pool) + .await + .map_err(|_| SqlInsertError(String::from("Failed to insert data.")))?; + pool.close().await; + + Ok(()) +} + +pub async fn get_data_module_activation_kill_switch_status_sqlite( +) -> Result<(Option<String>, Option<bool>, Option<bool>), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let row: (Option<String>, Option<bool>, Option<bool>) = sqlx::query_as( + "SELECT id, ai_module, anilist_module FROM module_activation WHERE guild = 1", + ) + .fetch_one(&pool) + .await + .unwrap_or((None, None, None)); + pool.close().await; + + Ok(row) +} + +pub async fn get_data_guild_lang_sqlite( + guild_id: String, +) -> Result<(Option<String>, Option<String>), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let row: (Option<String>, Option<String>) = + sqlx::query_as("SELECT lang_struct, guild FROM guild_lang WHERE guild = ?") + .bind(guild_id) + .fetch_one(&pool) + .await + .unwrap_or((None, None)); + pool.close().await; + + Ok(row) +} + +pub async fn get_one_activity_sqlite( + server_id: String, + anime_id: i32, +) -> Result< + ( + Option<String>, + Option<String>, + Option<String>, + Option<String>, + ), + AppError, +> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let row: (Option<String>, Option<String>, Option<String>, Option<String>) = sqlx::query_as( + "SELECT anime_id, timestamp, server_id, webhook FROM activity_data WHERE anime_id = ? AND server_id = ?", + ) + .bind(anime_id) + .bind(server_id) + .fetch_one(&pool) + .await + .unwrap_or((None, None, None, None)); + + pool.close().await; + + Ok(row) +} + +pub async fn get_registered_user_sqlite( + user_id: &String, +) -> Result<(Option<String>, Option<String>), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let row: (Option<String>, Option<String>) = + sqlx::query_as("SELECT anilist_id, user_id FROM registered_user WHERE user_id = ?") + .bind(user_id) + .fetch_one(&pool) + .await + .unwrap_or((None, None)); + pool.close().await; + + Ok(row) +} + +pub async fn set_registered_user_sqlite( + user_id: &String, + username: &String, +) -> Result<(Option<String>, Option<String>), AppError> { + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + let row: (Option<String>, Option<String>) = sqlx::query_as( + "INSERT OR REPLACE INTO registered_user (user_id, anilist_id) VALUES (?, ?)", + ) + .bind(user_id) + .bind(username) + .fetch_one(&pool) + .await + .unwrap_or((None, None)); + pool.close().await; + + Ok(row) +} diff --git a/src/sqls/sqlite/init.rs b/src/sqls/sqlite/init.rs new file mode 100644 index 00000000..f167b75d --- /dev/null +++ b/src/sqls/sqlite/init.rs @@ -0,0 +1,178 @@ +use std::fs::File; +use std::path::Path; + +use sqlx::{Pool, Sqlite}; + +use crate::constant::{CACHE_SQLITE_DB, DATA_SQLITE_DB}; +use crate::error_enum::AppError; +use crate::error_enum::AppError::{FailedToCreateAFile, SqlCreateError}; +use crate::sqls::sqlite::pool::get_sqlite_pool; + +/// Initializes SQLite database. +/// +/// This function checks if the SQLite database files exist and creates them if they don't. +/// It then initializes the database by creating necessary tables and indices. +/// This function uses two separate SQLite databases: one for data and one for cache. +pub async fn init_sqlite() -> Result<(), AppError> { + let p = Path::new(DATA_SQLITE_DB); + if !p.exists() { + match File::create(p) { + Ok(_) => {} + Err(e) => { + println!("Failed to create the file {} : {}", DATA_SQLITE_DB, e); + return Err(FailedToCreateAFile(String::from( + "Failed to create db file.", + ))); + } + } + } + let p = Path::new(CACHE_SQLITE_DB); + if !p.exists() { + match File::create(p) { + Ok(_) => {} + Err(e) => { + println!("Failed to create the file {} : {}", CACHE_SQLITE_DB, e); + return Err(FailedToCreateAFile(String::from( + "Failed to create db file.", + ))); + } + } + } + let pool = get_sqlite_pool(CACHE_SQLITE_DB).await?; + init_sqlite_cache(&pool).await?; + pool.close().await; + let pool = get_sqlite_pool(DATA_SQLITE_DB).await?; + init_sqlite_data(&pool).await?; + pool.close().await; + Ok(()) +} + +async fn init_sqlite_cache(pool: &Pool<Sqlite>) -> Result<(), AppError> { + sqlx::query( + "CREATE TABLE IF NOT EXISTS request_cache ( + json TEXT PRIMARY KEY, + response TEXT NOT NULL, + last_updated INTEGER NOT NULL + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS cache_stats ( + key TEXT PRIMARY KEY, + response TEXT NOT NULL, + last_updated INTEGER NOT NULL, + last_page INTEGER NOT NULL + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS activity_data ( + anime_id TEXT, + timestamp TEXT, + server_id TEXT, + webhook TEXT, + episode TEXT, + name TEXT, + delays INTEGER DEFAULT 0, + PRIMARY KEY (anime_id, server_id) + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + Ok(()) +} + +/// Initializes the SQLite tables and data. +/// +/// # Arguments +/// +/// * `_pool` - A reference to the SQLite connection pool. +async fn init_sqlite_data(pool: &Pool<Sqlite>) -> Result<(), AppError> { + sqlx::query( + "CREATE TABLE IF NOT EXISTS ping_history ( + shard_id TEXT, + timestamp TEXT, + ping TEXT NOT NULL, + PRIMARY KEY (shard_id, timestamp) + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS guild_lang ( + guild TEXT PRIMARY KEY, + lang_struct TEXT NOT NULL + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS activity_data ( + anime_id TEXT, + timestamp TEXT, + server_id TEXT, + webhook TEXT, + episode TEXT, + name TEXT, + delays INTEGER DEFAULT 0, + PRIMARY KEY (anime_id, server_id) + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS module_activation ( + guild_id TEXT PRIMARY KEY, + ai_module INTEGER, + anilist_module INTEGER + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS registered_user ( + user_id TEXT PRIMARY KEY, + anilist_id TEXT + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS global_kill_switch ( + id TEXT PRIMARY KEY, + ai_module INTEGER, + anilist_module INTEGER + )", + ) + .execute(pool) + .await + .map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + sqlx::query( + "INSERT OR REPLACE INTO global_kill_switch (id, anilist_module, ai_module) VALUES (?, ?, ?)", + ) + .bind("1") + .bind(1) + .bind(1) + .execute(pool) + .await.map_err(|_| SqlCreateError(String::from("Failed to create the database table.")))?; + + Ok(()) +} diff --git a/src/sqls/sqlite/mod.rs b/src/sqls/sqlite/mod.rs new file mode 100644 index 00000000..0340192e --- /dev/null +++ b/src/sqls/sqlite/mod.rs @@ -0,0 +1,4 @@ +pub mod cache; +pub mod data; +pub mod init; +pub mod pool; diff --git a/src/sqls/sqlite/pool.rs b/src/sqls/sqlite/pool.rs new file mode 100644 index 00000000..5302dc56 --- /dev/null +++ b/src/sqls/sqlite/pool.rs @@ -0,0 +1,22 @@ +use sqlx::{Pool, Sqlite, SqlitePool}; + +use crate::error_enum::AppError; +use crate::error_enum::AppError::CreatingPoolError; + +/// Connects to a SQLite database and returns a connection pool. +/// The function is asynchronous and returns a `Pool` wrapped in a `Result`. +/// +/// # Arguments +/// +/// * `database_url` - A string slice representing the URL to the SQLite database. +/// +/// # Returns +/// +/// The function returns a `Pool<Sqlite>` wrapped in a `Result`. If the connection to +/// the database is successful, the `Pool` is returned. Otherwise, an error is returned. +/// +pub async fn get_sqlite_pool(database_url: &str) -> Result<Pool<Sqlite>, AppError> { + SqlitePool::connect(database_url) + .await + .map_err(|_| CreatingPoolError(String::from("Failed to create the pool."))) +} diff --git a/src/structure/struct_shard_manager.rs b/src/struct_shard_manager.rs similarity index 53% rename from src/structure/struct_shard_manager.rs rename to src/struct_shard_manager.rs index 789f7575..3f2ac41a 100644 --- a/src/structure/struct_shard_manager.rs +++ b/src/struct_shard_manager.rs @@ -1,11 +1,10 @@ use std::sync::Arc; -use serenity::client::bridge::gateway::ShardManager; +use serenity::all::ShardManager; use serenity::prelude::TypeMapKey; -use tokio::sync::Mutex; pub struct ShardManagerContainer; impl TypeMapKey for ShardManagerContainer { - type Value = Arc<Mutex<ShardManager>>; + type Value = Arc<ShardManager>; } diff --git a/src/structure/anilist/character/mod.rs b/src/structure/anilist/character/mod.rs deleted file mode 100644 index a339048b..00000000 --- a/src/structure/anilist/character/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod struct_autocomplete_character; -pub mod struct_character; diff --git a/src/structure/anilist/character/struct_autocomplete_character.rs b/src/structure/anilist/character/struct_autocomplete_character.rs deleted file mode 100644 index 05651040..00000000 --- a/src/structure/anilist/character/struct_autocomplete_character.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::function::requests::request::make_request_anilist; -use crate::structure::anilist::struct_autocomplete::AutocompleteOption; -use serde::Deserialize; -use serde_json::{json, Value}; - -#[derive(Debug, Deserialize)] -pub struct AutocompleteName { - pub full: String, - #[serde(rename = "userPreferred")] - pub user_preferred: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct AutocompleteCharacter { - pub id: u32, - pub name: Option<AutocompleteName>, -} - -#[derive(Debug, Deserialize)] -pub struct CharacterPage { - pub characters: Option<Vec<Option<AutocompleteCharacter>>>, -} - -#[derive(Debug, Deserialize)] -pub struct CharacterPageData { - #[serde(rename = "Page")] - pub page: CharacterPage, -} - -#[derive(Debug, Deserialize)] -pub struct CharacterPageWrapper { - pub data: CharacterPageData, -} - -impl CharacterPageWrapper { - pub async fn new_autocomplete_character(search: &Value, count: i32) -> CharacterPageWrapper { - let query_str = "query ($search: String, $count: Int) { - Page(perPage: $count) { - characters(search: $search) { - id - name { - full - } - } - } - } - "; - let json = json!({"query": query_str, "variables": { - "search": search, - "count": count, - }}); - let res = make_request_anilist(json, true).await; - let data: CharacterPageWrapper = serde_json::from_str(&res).unwrap(); - data - } - - pub fn get_choices(&self) -> Value { - if let Some(character) = &self.data.page.characters { - let suggestions: Vec<AutocompleteOption> = character - .iter() - .filter_map(|item| { - item.as_ref().map(|item| AutocompleteOption { - name: match &item.name { - Some(name) => { - let english = name.user_preferred.clone(); - let romaji = name.full.clone(); - english.unwrap_or(romaji) - } - None => String::default(), - }, - value: item.id.to_string(), - }) - }) - .collect(); - let choices = json!(suggestions); - choices - } else { - let choices = json!("Error"); - choices - } - } -} diff --git a/src/structure/anilist/character/struct_character.rs b/src/structure/anilist/character/struct_character.rs deleted file mode 100644 index 0764bd57..00000000 --- a/src/structure/anilist/character/struct_character.rs +++ /dev/null @@ -1,205 +0,0 @@ -use crate::function::general::html_parser::convert_to_discord_markdown; -use crate::function::general::trim::trim; -use crate::function::requests::request::make_request_anilist; -use crate::structure::embed::anilist::struct_lang_character::CharacterLocalisedText; -use serde::Deserialize; -use serde_json::json; - -#[derive(Deserialize)] -pub struct CharacterWrapper { - pub data: CharacterData, -} - -#[derive(Deserialize)] -pub struct CharacterData { - #[serde(rename = "Character")] - pub character: Character, -} - -#[derive(Deserialize)] -pub struct Character { - pub id: u32, - pub name: Name, - #[serde(rename = "siteUrl")] - pub site_url: String, - pub description: String, - pub gender: String, - pub age: String, - #[serde(rename = "dateOfBirth")] - pub date_of_birth: DateOfBirth, - pub image: Image, - pub favourites: u32, - #[serde(rename = "modNotes")] - pub mod_notes: Option<String>, -} - -#[derive(Deserialize)] -pub struct Name { - pub full: String, - pub native: String, - #[serde(rename = "userPreferred")] - pub user_preferred: String, -} - -#[derive(Deserialize)] -pub struct DateOfBirth { - pub year: Option<u32>, - pub month: Option<u32>, - pub day: Option<u32>, -} - -#[derive(Deserialize)] -pub struct Image { - pub large: String, -} - -impl CharacterWrapper { - pub async fn new_character_by_id( - value: i32, - localised_text: CharacterLocalisedText, - ) -> Result<CharacterWrapper, String> { - let query_id: &str = " - query ($name: Int) { - Character(id: $name) { - id - name { - full - native - userPreferred - } - siteUrl - description - gender - age - dateOfBirth { - year - month - day - } - image { - large - } - favourites - modNotes - } - } - "; - let json = json!({"query": query_id, "variables": {"name": value}}); - let resp = make_request_anilist(json, false).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(localised_text.error_no_character) - } - } - } - - pub async fn new_character_by_search( - value: &String, - localised_text: CharacterLocalisedText, - ) -> Result<CharacterWrapper, String> { - let query_string: &str = " -query ($name: String) { - Character(search: $name) { - id - name { - full - native - userPreferred - } - siteUrl - description - gender - age - dateOfBirth { - year - month - day - } - image { - large - } - favourites - modNotes - } -} -"; - let json = json!({"query": query_string, "variables": {"name": value}}); - let resp = make_request_anilist(json, false).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(localised_text.error_no_character) - } - } - } - - pub fn get_name(&self) -> String { - format!( - "{}/{}", - self.data.character.name.user_preferred, self.data.character.name.native - ) - } - - pub fn get_desc(&self, localised_text: CharacterLocalisedText) -> String { - let mut desc = self.data.character.description.clone(); - desc = format!("{} {}", localised_text.desc.clone(), desc); - desc = convert_to_discord_markdown(desc); - let lenght_diff = 4096 - desc.len() as i32; - if lenght_diff <= 0 { - desc = trim(desc, lenght_diff); - } - - desc - } - - pub fn get_info(&self, localised_text: CharacterLocalisedText) -> String { - let age = &self.get_age(); - let gender = &self.get_gender(); - let favourite = &self.get_fav(); - let date_of_birth = &self.get_date_of_birth(); - let full_description = format!( - "{}{}{}{}{}{}{}{}.", - &localised_text.age, - age, - &localised_text.gender, - gender, - &localised_text.date_of_birth, - date_of_birth, - &localised_text.favourite, - favourite, - ); - full_description - } - - pub fn get_age(&self) -> String { - self.data.character.age.clone() - } - - pub fn get_gender(&self) -> String { - self.data.character.gender.clone() - } - - pub fn get_fav(&self) -> u32 { - self.data.character.favourites - } - - pub fn get_date_of_birth(&self) -> String { - format!( - "{}/{}/{}", - self.data.character.date_of_birth.month.unwrap_or(0), - self.data.character.date_of_birth.day.unwrap_or(0), - self.data.character.date_of_birth.year.unwrap_or(0) - ) - } - - pub fn get_image(&self) -> String { - self.data.character.image.large.clone() - } - - pub fn get_url(&self) -> String { - self.data.character.site_url.clone() - } -} diff --git a/src/structure/anilist/level/mod.rs b/src/structure/anilist/level/mod.rs deleted file mode 100644 index 47d7d0ff..00000000 --- a/src/structure/anilist/level/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod struct_level; diff --git a/src/structure/anilist/level/struct_level.rs b/src/structure/anilist/level/struct_level.rs deleted file mode 100644 index fb2c75c8..00000000 --- a/src/structure/anilist/level/struct_level.rs +++ /dev/null @@ -1,69 +0,0 @@ -pub struct LevelSystem {} - -impl LevelSystem { - pub const LEVELS: [(u32, f64, f64); 51] = [ - (0, 0.0, 20.0), - (1, 20.0, 40.0), - (2, 40.0, 60.0), - (3, 60.0, 80.0), - (4, 80.0, 100.0), - (5, 100.0, 130.0), - (6, 130.0, 160.0), - (7, 160.0, 190.0), - (8, 190.0, 220.0), - (9, 220.0, 250.0), - (10, 250.0, 280.0), - (11, 280.0, 310.0), - (12, 310.0, 340.0), - (13, 340.0, 370.0), - (14, 370.0, 400.0), - (15, 9360.0, 13860.0), - (16, 13860.0, 20460.0), - (17, 20460.0, 30160.0), - (18, 30160.0, 44360.0), - (19, 44360.0, 65160.0), - (20, 65160.0, 95560.0), - (21, 95560.0, 140160.0), - (22, 140160.0, 206160.0), - (23, 206160.0, 303160.0), - (24, 303160.0, 447160.0), - (25, 447160.0, 657160.0), - (26, 657160.0, 969160.0), - (27, 969160.0, 1426160.0), - (28, 1426160.0, 2096160.0), - (29, 2096160.0, 3076160.0), - (30, 3076160.0, 4526160.0), - (31, 4526160.0, 6626160.0), - (32, 6626160.0, 9746160.0), - (33, 9746160.0, 14316160.0), - (34, 14316160.0, 21016160.0), - (35, 21016160.0, 30816160.0), - (36, 30816160.0, 45316160.0), - (37, 45316160.0, 66316160.0), - (38, 66316160.0, 97516160.0), - (39, 97516160.0, 143516160.0), - (40, 143516160.0, 210516160.0), - (41, 210516160.0, 308516160.0), - (42, 308516160.0, 453516160.0), - (43, 453516160.0, 663516160.0), - (44, 663516160.0, 975516160.0), - (45, 975516160.0, 1437516160.0), - (46, 1437516160.0, 2107516160.0), - (47, 2107516160.0, 3087516160.0), - (48, 3087516160.0, 4537516160.0), - (49, 4537516160.0, 6637516160.0), - (50, 6637516160.0, 9757516160.0), - ]; - - pub fn get_level(xp: f64) -> Option<(u32, f64, f64)> { - for &(level, required_xp, next_level_required_xp) in Self::LEVELS.iter().rev() { - if xp >= required_xp { - let level_progress = xp - required_xp; - let level_progress_total = next_level_required_xp - required_xp; - return Some((level, level_progress, level_progress_total)); - } - } - - None - } -} diff --git a/src/structure/anilist/media/mod.rs b/src/structure/anilist/media/mod.rs deleted file mode 100644 index 17728263..00000000 --- a/src/structure/anilist/media/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod struct_anime_song; -pub mod struct_autocomplete_media; -pub mod struct_media; diff --git a/src/structure/anilist/media/struct_anime_song.rs b/src/structure/anilist/media/struct_anime_song.rs deleted file mode 100644 index 4bb879c3..00000000 --- a/src/structure/anilist/media/struct_anime_song.rs +++ /dev/null @@ -1,95 +0,0 @@ -#[derive(Debug, serde::Deserialize)] -pub struct Artist { - #[serde(rename = "id")] - pub id: i32, - #[serde(rename = "names")] - pub names: Vec<String>, - #[serde(rename = "line_up_id")] - pub line_up_id: i32, - #[serde(rename = "groups")] - pub groups: Option<Vec<Group>>, - #[serde(rename = "members")] - pub members: Option<Vec<Artist>>, -} - -#[derive(Debug, serde::Deserialize)] -pub struct Group { - #[serde(rename = "id")] - pub id: i32, - #[serde(rename = "names")] - pub names: Vec<String>, - #[serde(rename = "line_up_id")] - pub line_up_id: Option<i32>, - #[serde(rename = "groups")] - pub groups: Option<Vec<Group>>, - #[serde(rename = "members")] - pub members: Option<Vec<Artist>>, -} - -#[derive(Debug, serde::Deserialize)] -pub struct Composer { - #[serde(rename = "id")] - pub id: i32, - #[serde(rename = "names")] - pub names: Vec<String>, - #[serde(rename = "line_up_id")] - pub line_up_id: Option<i32>, - #[serde(rename = "groups")] - pub groups: Option<Vec<Group>>, - #[serde(rename = "members")] - pub members: Option<Vec<Artist>>, -} - -#[derive(Debug, serde::Deserialize)] -pub struct Arranger { - #[serde(rename = "id")] - pub id: i32, - #[serde(rename = "names")] - pub names: Vec<String>, - #[serde(rename = "line_up_id")] - pub line_up_id: Option<i32>, - #[serde(rename = "groups")] - pub groups: Option<Vec<Group>>, - #[serde(rename = "members")] - pub members: Option<Vec<Artist>>, -} - -#[derive(Debug, serde::Deserialize)] -pub struct AnnMetadata { - #[serde(rename = "annId")] - pub ann_id: i32, - #[serde(rename = "annSongId")] - pub ann_song_id: i32, - #[serde(rename = "animeENName")] - pub anime_en_name: String, - #[serde(rename = "animeJPName")] - pub anime_jp_name: String, - #[serde(rename = "animeAltName")] - pub anime_alt_name: Option<String>, - #[serde(rename = "animeVintage")] - pub anime_vintage: String, - #[serde(rename = "animeType")] - pub anime_type: String, - #[serde(rename = "songType")] - pub song_type: String, - #[serde(rename = "songName")] - pub song_name: String, - #[serde(rename = "songArtist")] - pub song_artist: String, - #[serde(rename = "songDifficulty")] - pub song_difficulty: f32, - #[serde(rename = "songCategory")] - pub song_category: String, - #[serde(rename = "HQ")] - pub hq: Option<String>, - #[serde(rename = "MQ")] - pub mq: Option<String>, - #[serde(rename = "audio")] - pub audio: String, - #[serde(rename = "artists")] - pub artists: Vec<Artist>, - #[serde(rename = "composers")] - pub composers: Vec<Composer>, - #[serde(rename = "arrangers")] - pub arrangers: Vec<Arranger>, -} diff --git a/src/structure/anilist/media/struct_autocomplete_media.rs b/src/structure/anilist/media/struct_autocomplete_media.rs deleted file mode 100644 index 4cd6f7ff..00000000 --- a/src/structure/anilist/media/struct_autocomplete_media.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::function::requests::request::make_request_anilist; -use crate::structure::anilist::struct_autocomplete::AutocompleteOption; -use serde::Deserialize; -use serde_json::{json, Value}; -use serenity::client::Context; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; - -#[derive(Debug, Deserialize)] -pub struct AutocompleteTitle { - pub romaji: String, - pub english: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct AutocompleteMedia { - pub id: u32, - pub title: Option<AutocompleteTitle>, -} - -#[derive(Debug, Deserialize)] -pub struct MediaPage { - pub media: Option<Vec<Option<AutocompleteMedia>>>, -} - -#[derive(Debug, Deserialize)] -pub struct MediaPageData { - #[serde(rename = "Page")] - pub page: MediaPage, -} - -#[derive(Debug, Deserialize)] -pub struct MediaPageWrapper { - pub data: MediaPageData, -} - -impl MediaPageWrapper { - pub async fn new_autocomplete_anime( - search: &Value, - count: u32, - search_type: &str, - ) -> MediaPageWrapper { - let query_str = "query ($search: String, $type: MediaType, $count: Int) { - Page(perPage: $count) { - media(search: $search, type: $type) { - id - title { - romaji - english - } - } - } -}"; - let json = json!({"query": query_str, "variables": { - "search": search, - "type": search_type, - "count": count, - }}); - - let res = make_request_anilist(json, true).await; - let data: MediaPageWrapper = serde_json::from_str(&res).unwrap(); - data - } - - pub async fn new_autocomplete_manga( - search: &Value, - count: u32, - search_type: &str, - format: &str, - ) -> MediaPageWrapper { - let query_str = - "query($search: String, $type: MediaType, $count: Int, $format: MediaFormat) { - Page(perPage: $count) { - media(search: $search, type: $type, format_not: $format) { - id - title { - romaji - english - } - } - } - }"; - let json = json!({"query": query_str, "variables": { - "search": search, - "type": search_type, - "count": count, - "format": format - }}); - - let res = make_request_anilist(json, true).await; - let data: MediaPageWrapper = serde_json::from_str(&res).unwrap(); - data - } - - pub async fn new_autocomplete_ln( - search: &Value, - count: u32, - search_type: &str, - format: &str, - ) -> MediaPageWrapper { - let query_str = - "query($search: String, $type: MediaType, $count: Int, $format: MediaFormat) { - Page(perPage: $count) { - media(search: $search, type: $type, format: $format) { - id - title { - romaji - english - } - } - } - }"; - let json = json!({"query": query_str, "variables": { - "search": search, - "type": search_type, - "count": count, - "format": format - }}); - - let res = make_request_anilist(json, true).await; - let data: MediaPageWrapper = serde_json::from_str(&res).unwrap(); - data - } - - pub fn get_choices(&self) -> Value { - if let Some(media) = &self.data.page.media { - let suggestions: Vec<AutocompleteOption> = media - .iter() - .filter_map(|item| { - item.as_ref().map(|item| AutocompleteOption { - name: match &item.title { - Some(name) => { - let english = name.english.clone(); - let romaji = name.romaji.clone(); - english.unwrap_or(romaji) - } - None => String::default(), - }, - value: item.id.to_string(), - }) - }) - .collect(); - let choices = json!(suggestions); - choices - } else { - let choices = json!("Error"); - choices - } - } -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - let search = &command.data.options.first().unwrap().value; - if let Some(search) = search { - let data = MediaPageWrapper::new_autocomplete_anime(search, 8, "ANIME").await; - let choices = data.get_choices(); - // doesn't matter if it errors - _ = command - .create_autocomplete_response(ctx.http, |response| { - response.set_choices(choices.clone()) - }) - .await; - } -} diff --git a/src/structure/anilist/media/struct_media.rs b/src/structure/anilist/media/struct_media.rs deleted file mode 100644 index adfebac0..00000000 --- a/src/structure/anilist/media/struct_media.rs +++ /dev/null @@ -1,769 +0,0 @@ -use crate::function::error_management::error_no::error_no_anime_specified; -use crate::function::general::html_parser::convert_to_discord_markdown; -use crate::function::general::trim::trim; -use crate::function::requests::request::make_request_anilist; -use crate::structure::embed::anilist::struct_lang_anime::AnimeLocalisedText; -use crate::structure::embed::anilist::struct_lang_media::MediaLocalisedText; -use serde::Deserialize; -use serde_json::json; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize)] -pub struct MediaWrapper { - pub data: MediaData, -} - -#[derive(Debug, Deserialize)] -pub struct MediaData { - #[serde(rename = "Media")] - pub media: Media, -} - -#[derive(Debug, Deserialize)] -pub struct Media { - pub id: i64, - pub description: Option<String>, - pub title: Title, - pub r#type: Option<String>, - pub format: Option<String>, - pub source: Option<String>, - #[serde(rename = "isAdult")] - pub is_adult: bool, - #[serde(rename = "startDate")] - pub start_date: StartEndDate, - #[serde(rename = "endDate")] - pub end_date: StartEndDate, - pub chapters: Option<i32>, - pub volumes: Option<i32>, - pub status: Option<String>, - pub season: Option<String>, - #[serde(rename = "isLicensed")] - pub is_licensed: bool, - #[serde(rename = "coverImage")] - pub cover_image: CoverImage, - #[serde(rename = "bannerImage")] - pub banner_image: Option<String>, - pub genres: Vec<Option<String>>, - pub tags: Vec<Tag>, - #[serde(rename = "averageScore")] - pub average_score: Option<i32>, - #[serde(rename = "meanScore")] - pub mean_score: Option<i32>, - pub popularity: Option<i32>, - pub favourites: Option<i32>, - #[serde(rename = "siteUrl")] - pub site_url: Option<String>, - pub staff: Staff, -} - -#[derive(Debug, Deserialize)] -pub struct Title { - pub romaji: Option<String>, - pub english: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct StartEndDate { - pub year: Option<i32>, - pub month: Option<i32>, - pub day: Option<i32>, -} - -#[derive(Debug, Deserialize)] -pub struct CoverImage { - #[serde(rename = "extraLarge")] - pub extra_large: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct Tag { - pub name: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct Staff { - pub edges: Vec<Edge>, -} - -#[derive(Debug, Deserialize)] -pub struct Edge { - pub node: Node, - pub id: Option<u32>, - pub role: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct Node { - pub id: Option<u32>, - pub name: Name, -} - -#[derive(Debug, Deserialize)] -pub struct Name { - pub full: Option<String>, - #[serde(rename = "userPreferred")] - pub user_preferred: Option<String>, -} - -impl MediaWrapper { - pub async fn new_anime_by_id( - search: String, - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<MediaWrapper, String> { - let query_id: &str = " - query ($search: Int, $limit: Int = 5) { - Media (id: $search, type: ANIME){ - id - description - title{ - romaji - english - } - type - format - source - isAdult - startDate { - year - month - day - } - endDate { - year - month - day - } - chapters - volumes - status - season - isLicensed - coverImage { - extraLarge - } - bannerImage - genres - tags { - name - } - averageScore - meanScore - popularity - favourites - siteUrl - staff(perPage: $limit) { - edges { - node { - id - name { - full - userPreferred - } - } - id - role - } - } - } -} -"; - - let json = json!({"query": query_id, "variables": {"search": search}}); - let resp = make_request_anilist(json, false).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => Ok(data), - Err(error) => { - println!("Error: {}", error); - error_no_anime_specified(ctx, command).await; - Err("not found".to_string()) - } - } - } - - pub async fn new_anime_by_search( - search: &String, - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<MediaWrapper, String> { - let query_string: &str = " - query ($search: String, $limit: Int = 5) { - Media (search: $search, type: ANIME){ - id - description - title{ - romaji - english - } - type - format - source - isAdult - startDate { - year - month - day - } - endDate { - year - month - day - } - chapters - volumes - status - season - isLicensed - coverImage { - extraLarge - } - bannerImage - genres - tags { - name - } - averageScore - meanScore - popularity - favourites - siteUrl - staff(perPage: $limit) { - edges { - node { - id - name { - full - userPreferred - } - } - id - role - } - } - } -} -"; - let json = json!({"query": query_string, "variables": {"search": search}}); - let resp = make_request_anilist(json, false).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => Ok(data), - Err(error) => { - println!("Error: {}", error); - error_no_anime_specified(ctx, command).await; - Err("not found".to_string()) - } - } - } - - pub async fn new_manga_by_id( - search: String, - localised_text: MediaLocalisedText, - ) -> Result<MediaWrapper, String> { - let query_id: &str = " - query ($search: Int, $limit: Int = 5, $format: MediaFormat = NOVEL) { - Media (id: $search, type: MANGA, format_not: $format){ - id - description - title{ - romaji - english - } - type - format - source - isAdult - startDate { - year - month - day - } - endDate { - year - month - day - } - chapters - volumes - status - season - isLicensed - coverImage { - extraLarge - } - bannerImage - genres - tags { - name - } - averageScore - meanScore - popularity - favourites - siteUrl - staff(perPage: $limit) { - edges { - node { - id - name { - full - userPreferred - } - } - id - role - } - } - } -} -"; - - let json = json!({"query": query_id, "variables": {"search": search}}); - let resp = make_request_anilist(json, false).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => Ok(data), - Err(error) => { - println!("Error: {}", error); - Err(localised_text.error_no_media.clone()) - } - } - } - - pub async fn new_manga_by_search( - search: String, - localised_text: MediaLocalisedText, - ) -> Result<MediaWrapper, String> { - let query_string: &str = " - query ($search: String, $limit: Int = 5, $format: MediaFormat = NOVEL) { - Media (search: $search, type: MANGA, format_not: $format){ - id - description - title{ - romaji - english - } - type - format - source - isAdult - startDate { - year - month - day - } - endDate { - year - month - day - } - chapters - volumes - status - season - isLicensed - coverImage { - extraLarge - } - bannerImage - genres - tags { - name - } - averageScore - meanScore - popularity - favourites - siteUrl - staff(perPage: $limit) { - edges { - node { - id - name { - full - userPreferred - } - } - id - role - } - } - } -} -"; - let json = json!({"query": query_string, "variables": {"search": search}}); - let resp = make_request_anilist(json, false).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => Ok(data), - Err(error) => { - println!("Error: {}", error); - Err(localised_text.error_no_media.clone()) - } - } - } - - pub async fn new_ln_by_id( - search: String, - localised_text: MediaLocalisedText, - ) -> Result<MediaWrapper, String> { - let query_id: &str = " - query ($search: Int, $limit: Int = 5, $format: MediaFormat = NOVEL) { - Media (id: $search, type: MANGA, format: $format){ - id - description - title{ - romaji - english - } - type - format - source - isAdult - startDate { - year - month - day - } - endDate { - year - month - day - } - chapters - volumes - status - season - isLicensed - coverImage { - extraLarge - } - bannerImage - genres - tags { - name - } - averageScore - meanScore - popularity - favourites - siteUrl - staff(perPage: $limit) { - edges { - node { - id - name { - full - userPreferred - } - } - id - role - } - } - } -} -"; - - let json = json!({"query": query_id, "variables": {"search": search}}); - let resp = make_request_anilist(json, false).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => Ok(data), - Err(error) => { - println!("Error: {}", error); - Err(localised_text.error_no_media.clone()) - } - } - } - - pub async fn new_ln_by_search( - search: String, - localised_text: MediaLocalisedText, - ) -> Result<MediaWrapper, String> { - let query_string: &str = " - query ($search: String, $limit: Int = 5, $format: MediaFormat = NOVEL) { - Media (search: $search, type: MANGA, format: $format){ - id - description - title{ - romaji - english - } - type - format - source - isAdult - startDate { - year - month - day - } - endDate { - year - month - day - } - chapters - volumes - status - season - isLicensed - coverImage { - extraLarge - } - bannerImage - genres - tags { - name - } - averageScore - meanScore - popularity - favourites - siteUrl - staff(perPage: $limit) { - edges { - node { - id - name { - full - userPreferred - } - } - id - role - } - } - } -} -"; - - let json = json!({"query": query_string, "variables": {"search": search}}); - let resp = make_request_anilist(json, false).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => Ok(data), - Err(error) => { - println!("Error: {}", error); - Err(localised_text.error_no_media.clone()) - } - } - } - - pub fn get_nsfw(&self) -> bool { - self.data.media.is_adult - } - - pub fn get_banner(&self) -> String { - format!("https://img.anili.st/media/{}", self.data.media.id) - } - - pub fn get_desc(&self) -> String { - let mut desc = self - .data - .media - .description - .clone() - .unwrap_or_else(|| "NA".to_string()); - desc = convert_to_discord_markdown(desc); - let lenght_diff = 4096 - desc.len() as i32; - if lenght_diff <= 0 { - desc = trim(desc, lenght_diff) - } - desc - } - - pub fn get_en_title(&self) -> String { - self.data - .media - .title - .english - .clone() - .unwrap_or("NA".to_string()) - } - - pub fn get_rj_title(&self) -> String { - self.data - .media - .title - .romaji - .clone() - .unwrap_or("NA".to_string()) - } - - pub fn get_thumbnail(&self) -> String { - self.data.media.cover_image.extra_large.clone().unwrap_or("https://imgs.search.brave.com/CYnhSvdQcm9aZe3wG84YY0B19zT2wlAuAkiAGu0mcLc/rs:fit:640:400:1/g:ce/aHR0cDovL3d3dy5m/cmVtb250Z3VyZHdh/cmEub3JnL3dwLWNv/bnRlbnQvdXBsb2Fk/cy8yMDIwLzA2L25v/LWltYWdlLWljb24t/Mi5wbmc".to_string()) - } - - pub fn get_url(&self) -> String { - self.data - .media - .site_url - .clone() - .unwrap_or("https://example.com".to_string()) - } - - pub fn get_name(&self) -> String { - format!("{} / {}", self.get_en_title(), self.get_rj_title()) - } - - pub fn get_format(&self) -> String { - self.data.media.format.clone().unwrap_or("N/A".to_string()) - } - - pub fn get_source(&self) -> String { - self.data.media.source.clone().unwrap_or("N/A".to_string()) - } - - pub fn get_start_date(&self) -> String { - let start_y = self.data.media.start_date.year.unwrap_or(0); - let start_d = self.data.media.start_date.day.unwrap_or(0); - let start_m = self.data.media.start_date.month.unwrap_or(0); - if start_y == 0 && start_d == 0 && start_m == 0 { - "N/A".to_string() - } else { - format!("{}/{}/{}", start_d, start_m, start_y) - } - } - - pub fn get_end_date(&self) -> String { - let end_y = self.data.media.end_date.year.unwrap_or(0); - let end_d = self.data.media.end_date.day.unwrap_or(0); - let end_m = self.data.media.end_date.month.unwrap_or(0); - if end_y == 0 && end_d == 0 && end_m == 0 { - "N/A".to_string() - } else { - format!("{}/{}/{}", end_y, end_d, end_m) - } - } - - pub fn get_anime_staff(&self, localised_text: AnimeLocalisedText) -> String { - let mut staff = "".to_string(); - let staffs = &self.data.media.staff.edges; - for s in staffs { - let full = s - .node - .name - .full - .clone() - .unwrap_or_else(|| "N/A".to_string()); - let user = s - .node - .name - .user_preferred - .clone() - .unwrap_or_else(|| "N/A".to_string()); - let role = s.role.clone().unwrap_or_else(|| "N/A".to_string()); - staff.push_str(&format!( - "{}{}{}{}{}{} | \n", - &localised_text.desc_part_5, - full, - localised_text.desc_part_6, - user, - localised_text.desc_part_7, - role - )); - } - staff - } - - pub fn get_media_staff(&self, localised_text: MediaLocalisedText) -> String { - let mut staff = "".to_string(); - let staffs = &self.data.media.staff.edges; - for s in staffs { - let full = s - .node - .name - .full - .clone() - .unwrap_or_else(|| "N/A".to_string()); - let user = s - .node - .name - .user_preferred - .clone() - .unwrap_or_else(|| "N/A".to_string()); - let role = s.role.clone().unwrap_or_else(|| "N/A".to_string()); - staff.push_str(&format!( - "{}{}{}{}{}{}\n", - &localised_text.full_name, - full, - &localised_text.user_pref, - user, - &localised_text.role, - role - )); - } - staff - } - - pub fn get_anime_info(&self, localised_text: AnimeLocalisedText) -> String { - let format = self.get_format(); - let source = self.get_source(); - let start_date = self.get_start_date(); - let end_date = self.get_end_date(); - let staff = self.get_anime_staff(localised_text.clone()); - - format!( - "{}{}{}{}{}{}{}{} \n {}", - &localised_text.desc_part_1, - format, - &localised_text.desc_part_2, - source, - &localised_text.desc_part_3, - start_date, - &localised_text.desc_part_4, - end_date, - staff - ) - } - - pub fn get_media_info(&self, localised_text: MediaLocalisedText) -> String { - let format = self.get_format(); - let source = self.get_source(); - let start_date = self.get_start_date(); - let end_date = self.get_end_date(); - let staff = self.get_media_staff(localised_text.clone()); - format!( - "id: {}\n{}{}{}{}{}{}{}{} \n {}", - self.get_id(), - &localised_text.format, - format, - &localised_text.source, - source, - &localised_text.start_date, - start_date, - &localised_text.end_date, - end_date, - staff - ) - } - - pub fn get_genres(&self) -> String { - let mut genre = "".to_string(); - let genre_list = &self.data.media.genres; - for g in genre_list { - genre += &g.clone().unwrap_or_else(|| "N/A".to_string()); - genre += "\n" - } - genre - } - - pub fn get_tags(&self) -> String { - let mut tag = "".to_string(); - let tag_list = &self.data.media.tags; - for t in tag_list.iter().take(5) { - let tag_name: String = t.name.as_ref().map_or("N/A".to_string(), |s| s.to_string()); - tag += &tag_name; - tag += "\n"; - } - tag - } - - pub fn get_id(&self) -> i64 { - self.data.media.id - } -} diff --git a/src/structure/anilist/random/mod.rs b/src/structure/anilist/random/mod.rs deleted file mode 100644 index 02c8f586..00000000 --- a/src/structure/anilist/random/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod struct_random; -pub mod struct_site_statistic_anime; -pub mod struct_site_statistic_manga; diff --git a/src/structure/anilist/staff/mod.rs b/src/structure/anilist/staff/mod.rs deleted file mode 100644 index 5421eb49..00000000 --- a/src/structure/anilist/staff/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod struct_autocomplete_staff; -pub mod struct_staff; diff --git a/src/structure/anilist/staff/struct_staff.rs b/src/structure/anilist/staff/struct_staff.rs deleted file mode 100644 index 189c42c4..00000000 --- a/src/structure/anilist/staff/struct_staff.rs +++ /dev/null @@ -1,398 +0,0 @@ -use crate::function::general::html_parser::convert_to_discord_markdown; -use crate::function::general::trim::trim; -use crate::function::requests::request::make_request_anilist; -use crate::structure::embed::anilist::struct_lang_staff::StaffLocalisedText; -use serde::Deserialize; -use serde_json::json; - -#[derive(Debug, Deserialize)] -pub struct Name { - pub full: Option<String>, - pub native: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct Image { - pub large: String, -} - -#[derive(Debug, Deserialize)] -pub struct Date { - pub year: Option<i32>, - pub month: Option<i32>, - pub day: Option<i32>, -} - -#[derive(Debug, Deserialize)] -pub struct Title { - pub romaji: Option<String>, - pub english: Option<String>, -} - -#[derive(Debug, Deserialize)] -pub struct Node { - pub title: Title, -} - -#[derive(Debug, Deserialize)] -pub struct StaffMedia { - pub edges: Vec<Edge>, -} - -#[derive(Debug, Deserialize)] -pub struct Edge { - pub node: Node, - #[serde(rename = "roleNotes")] - pub role_notes: Option<String>, - #[serde(rename = "relationType")] - pub relation_type: Option<String>, - #[serde(rename = "staffRole")] - pub staff_role: String, -} - -#[derive(Debug, Deserialize)] -pub struct Character { - pub name: Name, - pub image: Image, -} - -#[derive(Debug, Deserialize)] -pub struct Characters { - pub nodes: Vec<Character>, -} - -#[derive(Debug, Deserialize)] -pub struct Staff { - pub name: Name, - pub id: i32, - #[serde(rename = "languageV2")] - pub language_v2: String, - pub image: Image, - pub description: String, - #[serde(rename = "primaryOccupations")] - pub primary_occupations: Vec<String>, - pub gender: Option<String>, - #[serde(rename = "dateOfBirth")] - pub date_of_birth: Date, - #[serde(rename = "dateOfDeath")] - pub date_of_death: Date, - pub age: Option<i32>, - #[serde(rename = "yearsActive")] - pub years_active: Vec<i32>, - #[serde(rename = "homeTown")] - pub home_town: Option<String>, - #[serde(rename = "siteUrl")] - pub site_url: String, - #[serde(rename = "staffMedia")] - pub staff_media: StaffMedia, - pub characters: Characters, -} - -#[derive(Debug, Deserialize)] -pub struct StaffData { - #[serde(rename = "Staff")] - pub staff: Staff, -} - -#[derive(Debug, Deserialize)] -pub struct StaffWrapper { - pub data: StaffData, -} - -impl StaffWrapper { - pub async fn new_staff_by_id(id: i32) -> Result<StaffWrapper, String> { - let query_id: &str = " -query ($name: Int, $limit1: Int = 5, $limit2: Int = 15) { - Staff(id: $name){ - name { - full - native - } - id - languageV2 - image { - large - } - description - primaryOccupations - gender - dateOfBirth { - year - month - day - } - dateOfDeath { - year - month - day - } - age - yearsActive - homeTown - siteUrl - staffMedia(perPage: $limit1){ - edges{ - node { - title { - romaji - english - } - } - roleNotes - relationType - staffRole - } - } - characters(perPage: $limit2) { - nodes { - name { - full - } - image { - large - } - } - } - } -} -"; - let json = json!({"query": query_id, "variables": {"name": id}}); - let resp = make_request_anilist(json, false).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(String::from("Error: Failed to retrieve user data")) - } - } - } - - pub async fn new_staff_by_search(search: &String) -> Result<StaffWrapper, String> { - let query_string: &str = " -query ($name: String, $limit1: Int = 5, $limit2: Int = 15) { - Staff(search: $name){ - name { - full - native - } - id - languageV2 - image { - large - } - description - primaryOccupations - gender - dateOfBirth { - year - month - day - } - dateOfDeath { - year - month - day - } - age - yearsActive - homeTown - siteUrl - staffMedia(perPage: $limit1){ - edges{ - node { - title { - romaji - english - } - } - roleNotes - relationType - staffRole - } - } - characters(perPage: $limit2) { - nodes { - name { - full - } - image { - large - } - } - } - } -} -"; - let json = json!({"query": query_string, "variables": {"name": search}}); - let resp = make_request_anilist(json, false).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(String::from("Error: Failed to retrieve user data")) - } - } - } - - pub fn format_va(&self) -> String { - let formatted_nodes_va: Vec<String> = self - .data - .staff - .characters - .nodes - .iter() - .map(|character| { - let name_natif = character - .name - .native - .clone() - .unwrap_or_else(|| "N/A".to_string()); - let name_full = character - .name - .full - .clone() - .unwrap_or_else(|| "N/A".to_string()); - let name = format!("{} / {}", name_natif, name_full); - name.to_string() - }) - .collect(); - - formatted_nodes_va.join(",\n") - } - - pub fn format_role(&self) -> String { - let formatted_edges_role: Vec<String> = self - .data - .staff - .staff_media - .edges - .iter() - .map(|edge| { - let title = match &edge.node.title.romaji { - Some(romaji) => romaji.clone(), - None => match &edge.node.title.english { - Some(english) => english.clone(), - None => "".to_string(), - }, - }; - let staff_role = &edge.staff_role; - format!("{} ({})", title, staff_role) - }) - .collect(); - formatted_edges_role.join("\n") - } - - pub fn get_url(&self) -> String { - format!("https://anilist.co/staff/{}", &self.data.staff.id) - } - - pub fn get_name(&self) -> String { - format!( - "{}/{}", - self.data - .staff - .name - .native - .clone() - .unwrap_or("N/A".to_string()), - self.data - .staff - .name - .full - .clone() - .unwrap_or("N/A".to_string()) - ) - } - - pub fn get_desc(&self, localised_text: &StaffLocalisedText) -> String { - let lang = self.get_lang(); - let hometown = self.get_hometown(); - let occupations_string = self.get_occupation(); - let birth = self.get_birth(); - let death = self.get_death(); - - let mut desc = self.data.staff.description.clone(); - desc = convert_to_discord_markdown(desc); - let mut full_description = format!( - "{}{}{}{}{}{}{}{}{}{}", - &localised_text.date_of_birth, - birth, - &localised_text.date_of_death, - death, - &localised_text.hometown, - hometown, - &localised_text.primary_language, - lang, - &localised_text.primary_occupation, - occupations_string - ); - let lenght_diff = 4096 - full_description.len() as i32; - if lenght_diff <= 0 { - desc = trim(desc, lenght_diff); - - full_description = format!( - "{}{}{}{}{}{}{}{}{}{}\n\n{}", - &localised_text.date_of_birth, - birth, - &localised_text.date_of_death, - death, - &localised_text.hometown, - hometown, - &localised_text.primary_language, - lang, - &localised_text.primary_occupation, - occupations_string, - desc - ); - } - - full_description - } - - pub fn get_birth(&self) -> String { - format!( - "{}/{}/{}", - &self.data.staff.date_of_birth.month.unwrap_or(0), - &self.data.staff.date_of_birth.day.unwrap_or(0), - &self.data.staff.date_of_birth.year.unwrap_or(0) - ) - } - - pub fn get_death(&self) -> String { - format!( - "{}/{}/{}", - &self.data.staff.date_of_death.month.unwrap_or(0), - &self.data.staff.date_of_death.day.unwrap_or(0), - &self.data.staff.date_of_death.year.unwrap_or(0) - ) - } - - pub fn get_lang(&self) -> String { - self.data.staff.language_v2.clone() - } - - pub fn get_image(&self) -> String { - self.data.staff.image.large.clone() - } - - pub fn get_hometown(&self) -> String { - self.data - .staff - .home_town - .clone() - .unwrap_or("N/A".to_string()) - } - - pub fn get_occupation(&self) -> String { - let max_limit = 5; - let limited_occupations: Vec<String> = self - .data - .staff - .primary_occupations - .iter() - .take(max_limit) - .cloned() - .collect(); - limited_occupations.join(", ") - } -} diff --git a/src/structure/anilist/struct_autocomplete.rs b/src/structure/anilist/struct_autocomplete.rs deleted file mode 100644 index 2bee9d70..00000000 --- a/src/structure/anilist/struct_autocomplete.rs +++ /dev/null @@ -1,7 +0,0 @@ -use serde::Serialize; - -#[derive(Serialize, Debug)] -pub struct AutocompleteOption { - pub name: String, - pub value: String, -} diff --git a/src/structure/anilist/struct_minimal_anime.rs b/src/structure/anilist/struct_minimal_anime.rs deleted file mode 100644 index 6e16fc61..00000000 --- a/src/structure/anilist/struct_minimal_anime.rs +++ /dev/null @@ -1,199 +0,0 @@ -use crate::constant::N_A; -use crate::function::requests::request::make_request_anilist; -use crate::structure::embed::anilist::struct_lang_add_activity::AddActivityLocalisedText; -use serde::{Deserialize, Serialize}; -use serde_json::json; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct NextAiringEpisode { - #[serde(rename = "airingAt")] - pub airing_at: Option<i64>, - #[serde(rename = "timeUntilAiring")] - pub time_until_airing: Option<i64>, - pub episode: Option<i32>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Title { - pub romaji: Option<String>, - pub english: Option<String>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct MinimalAnime { - pub id: i32, - pub title: Option<Title>, - #[serde(rename = "nextAiringEpisode")] - pub next_airing_episode: Option<NextAiringEpisode>, - #[serde(rename = "coverImage")] - pub cover_image: Option<CoverImage>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct MinimalAnimeWrapper { - pub data: MinimalAnimeData, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct MinimalAnimeData { - #[serde(rename = "Media")] - pub media: MinimalAnime, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct CoverImage { - #[serde(rename = "extraLarge")] - pub extra_large: Option<String>, -} - -impl MinimalAnimeWrapper { - pub async fn new_minimal_anime_by_id( - localised_text: AddActivityLocalisedText, - search: String, - ) -> Result<MinimalAnimeWrapper, String> { - let query = " - query ($name: Int) { - Media(type: ANIME, id: $name) { - id - coverImage { - extraLarge - } - title { - romaji - english - } - nextAiringEpisode { - airingAt - timeUntilAiring - episode - } - } - } - "; - let json = json!({"query": query, "variables": {"name": search}}); - let resp = make_request_anilist(json, true).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => data, - Err(error) => { - println!("Error: {}", error); - Err(localised_text.error_no_media.clone()) - } - } - } - - pub async fn new_minimal_anime_by_id_no_error(search: String) -> MinimalAnimeWrapper { - let query = " - query ($name: Int) { - Media(type: ANIME, search: $name) { - id - coverImage { - extraLarge - } - title { - romaji - english - } - nextAiringEpisode { - airingAt - timeUntilAiring - episode - } - } - "; - let json = json!({"query": query, "variables": {"name": search}}); - let resp = make_request_anilist(json, true).await; - serde_json::from_str(&resp).unwrap() - } - - pub async fn new_minimal_anime_by_search( - localised_text: AddActivityLocalisedText, - search: String, - ) -> Result<MinimalAnimeWrapper, String> { - let query = " - query ($name: String) { - Media(type: ANIME, search: $name) { - id - coverImage { - extraLarge - } - title { - romaji - english - } - nextAiringEpisode { - airingAt - timeUntilAiring - episode - } - } - } - "; - let json = json!({"query": query, "variables": {"name": search}}); - let resp = make_request_anilist(json, true).await; - // Get json - match serde_json::from_str(&resp) { - Ok(data) => data, - Err(error) => { - println!("Error: {}", error); - Err(localised_text.error_no_media.clone()) - } - } - } - - pub fn get_id(&self) -> i32 { - self.data.media.id - } - - pub fn get_timestamp(&self) -> i64 { - let media = self.data.media.next_airing_episode.clone().unwrap(); - media.airing_at.unwrap_or(0) - } - - pub fn get_name(&self) -> String { - format!("{} / {}", self.get_en_title(), self.get_rj_title()) - } - - pub fn get_en_title(&self) -> String { - self.data - .media - .title - .clone() - .unwrap() - .english - .clone() - .unwrap_or(N_A.to_string()) - } - - pub fn get_rj_title(&self) -> String { - self.data - .media - .title - .clone() - .unwrap() - .romaji - .clone() - .unwrap_or(N_A.to_string()) - } - - pub fn get_episode(&self) -> i32 { - self.data - .media - .next_airing_episode - .clone() - .unwrap() - .episode - .unwrap() - } - - pub fn get_image(&self) -> String { - self.data - .media - .cover_image - .clone() - .unwrap() - .extra_large - .clone() - .unwrap() - } -} diff --git a/src/structure/anilist/studio/mod.rs b/src/structure/anilist/studio/mod.rs deleted file mode 100644 index d0c98c2f..00000000 --- a/src/structure/anilist/studio/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod struct_autocomplete_studio; -pub mod struct_studio; diff --git a/src/structure/anilist/studio/struct_autocomplete_studio.rs b/src/structure/anilist/studio/struct_autocomplete_studio.rs deleted file mode 100644 index a31c955e..00000000 --- a/src/structure/anilist/studio/struct_autocomplete_studio.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::function::requests::request::make_request_anilist; -use crate::structure::anilist::struct_autocomplete::AutocompleteOption; -use serde::Deserialize; -use serde_json::{json, Value}; - -#[derive(Debug, Deserialize)] -pub struct AutocompleteStudio { - pub id: u32, - pub name: String, -} - -#[derive(Debug, Deserialize)] -pub struct StudioPage { - pub studios: Option<Vec<Option<AutocompleteStudio>>>, -} - -#[derive(Debug, Deserialize)] -pub struct StudioPageData { - #[serde(rename = "Page")] - pub page: StudioPage, -} - -#[derive(Debug, Deserialize)] -pub struct StudioPageWrapper { - pub data: StudioPageData, -} - -impl StudioPageWrapper { - pub async fn new_autocomplete_staff(search: &Value, count: i32) -> StudioPageWrapper { - let query_str = "query ($search: String, $count: Int) { - Page(perPage: $count) { - studios(search: $search) { - id - name - } - } - }"; - let json = json!({"query": query_str, "variables": { - "search": search, - "count": count, - }}); - - let res = make_request_anilist(json, true).await; - serde_json::from_str(&res).unwrap() - } - - pub fn get_choice(&self) -> Vec<AutocompleteOption> { - if let Some(studios) = &self.data.page.studios { - studios - .iter() - .filter_map(|item| { - item.as_ref().map(|item| AutocompleteOption { - name: item.name.clone(), - value: item.id.to_string(), - }) - }) - .collect::<Vec<AutocompleteOption>>() - } else { - vec![] - } - } -} diff --git a/src/structure/anilist/user/mod.rs b/src/structure/anilist/user/mod.rs deleted file mode 100644 index c5a0928c..00000000 --- a/src/structure/anilist/user/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod struct_autocomplete_user; -pub mod struct_user; diff --git a/src/structure/anilist/user/struct_autocomplete_user.rs b/src/structure/anilist/user/struct_autocomplete_user.rs deleted file mode 100644 index f620cb6e..00000000 --- a/src/structure/anilist/user/struct_autocomplete_user.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::function::requests::request::make_request_anilist; -use crate::structure::anilist::struct_autocomplete::AutocompleteOption; -use serde::Deserialize; -use serde_json::json; -use serenity::client::Context; -use serenity::json::Value; -use serenity::model::prelude::autocomplete::AutocompleteInteraction; - -#[derive(Debug, Deserialize)] -pub struct AutocompleteUser { - pub id: u32, - pub name: String, -} - -#[derive(Debug, Deserialize)] -pub struct UserPage { - pub users: Option<Vec<Option<AutocompleteUser>>>, -} - -#[derive(Debug, Deserialize)] -pub struct UserPageData { - #[serde(rename = "Page")] - pub page: UserPage, -} - -#[derive(Debug, Deserialize)] -pub struct UserPageWrapper { - pub data: UserPageData, -} - -impl UserPageWrapper { - pub async fn new_autocomplete_user(search: &Value, count: i32) -> UserPageWrapper { - let query_str = "query ($search: String, $count: Int) { - Page(perPage: $count) { - users(search: $search) { - id - name - } - } - }"; - let json = json!({"query": query_str, "variables": { - "search": search, - "count": count, - }}); - - let res = make_request_anilist(json, true).await; - let data: UserPageWrapper = serde_json::from_str(&res).unwrap(); - data - } - - pub fn get_choice(&self) -> Vec<AutocompleteOption> { - if let Some(users) = &self.data.page.users { - users - .iter() - .filter_map(|item| { - item.as_ref().map(|item| AutocompleteOption { - name: item.name.clone(), - value: item.id.to_string(), - }) - }) - .collect::<Vec<AutocompleteOption>>() - } else { - vec![] - } - } -} - -pub async fn autocomplete(ctx: Context, command: AutocompleteInteraction) { - let search = &command.data.options.first().unwrap().value; - if let Some(search) = search { - let data = UserPageWrapper::new_autocomplete_user(search, 8).await; - let choices = data.get_choice(); - // doesn't matter if it errors - let choices_json = json!(choices); - _ = command - .create_autocomplete_response(ctx.http.clone(), |response| { - response.set_choices(choices_json) - }) - .await; - } -} diff --git a/src/structure/anilist/user/struct_user.rs b/src/structure/anilist/user/struct_user.rs deleted file mode 100644 index 2f690222..00000000 --- a/src/structure/anilist/user/struct_user.rs +++ /dev/null @@ -1,491 +0,0 @@ -use crate::function::requests::request::make_request_anilist; -use crate::structure::embed::anilist::struct_lang_user::UserLocalisedText; -use serde::Deserialize; -use serde_json::json; -use serenity::utils::Colour; - -#[derive(Debug, Deserialize, Clone)] -pub struct UserWrapper { - pub data: UserData, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct UserData { - #[serde(rename = "User")] - pub user: User, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct User { - pub id: Option<i32>, - pub name: Option<String>, - pub avatar: Avatar, - pub statistics: Statistics, - pub options: Options, - #[serde(rename = "bannerImage")] - pub banner_image: Option<String>, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Options { - #[serde(rename = "profileColor")] - pub profile_color: Option<String>, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Avatar { - pub large: Option<String>, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Statistics { - pub anime: Anime, - pub manga: Manga, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Anime { - pub count: Option<i32>, - #[serde(rename = "meanScore")] - pub mean_score: Option<f64>, - #[serde(rename = "standardDeviation")] - pub standard_deviation: Option<f64>, - #[serde(rename = "minutesWatched")] - pub minutes_watched: Option<i32>, - pub tags: Vec<Tag>, - pub genres: Vec<Genre>, - pub statuses: Vec<Statuses>, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Manga { - pub count: Option<i32>, - #[serde(rename = "meanScore")] - pub mean_score: Option<f64>, - #[serde(rename = "standardDeviation")] - pub standard_deviation: Option<f64>, - #[serde(rename = "chaptersRead")] - pub chapters_read: Option<i32>, - pub tags: Vec<Tag>, - pub genres: Vec<Genre>, - pub statuses: Vec<Statuses>, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Statuses { - pub count: i32, - pub status: String, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Tag { - pub tag: TagData, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct TagData { - pub name: Option<String>, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Genre { - pub genre: Option<String>, -} - -impl UserWrapper { - pub fn get_one_anime_genre(&self, i: usize) -> String { - if let Some(genre) = self - .data - .user - .statistics - .anime - .genres - .get(i) - .and_then(|g| g.genre.as_ref()) - { - genre.clone() - } else { - "N/A".to_string() - } - } - - pub fn get_anime_genre(&self) -> String { - let mut anime_genre = String::new(); - for i in 0..3 { - let genre = self.get_one_anime_genre(i); - anime_genre.push_str(&format!("{} / ", genre)); - } - anime_genre.pop(); - anime_genre.pop(); - anime_genre - } - - pub fn get_one_anime_tag(&self, i: usize) -> String { - if let Some(tag) = self - .data - .user - .statistics - .anime - .tags - .get(i) - .and_then(|g| g.tag.name.as_ref()) - { - tag.clone() - } else { - "N/A".to_string() - } - } - - pub fn get_anime_tag(&self) -> String { - let mut anime_tag_name = String::new(); - for i in 0..3 { - let tags = self.get_one_anime_tag(i); - anime_tag_name.push_str(&format!("{} / ", tags)); - } - anime_tag_name.pop(); - anime_tag_name.pop(); - anime_tag_name - } - - pub fn get_anime_minute(&self) -> i32 { - self.data.user.statistics.anime.minutes_watched.unwrap_or(0) - } - - pub fn time_anime_watched(&self, localised_text: UserLocalisedText) -> String { - let mut min = self.get_anime_minute(); - - let mut hour = 0; - let mut days = 0; - let mut week = 0; - - if min >= 60 { - hour = min / 60; - min %= 60; - } - - if hour >= 24 { - days = hour / 24; - hour %= 24; - } - - if days >= 7 { - week = days / 7; - days %= 7; - } - - format!( - "{}{}{}{}{}{}{}{}", - week, - &localised_text.week, - days, - &localised_text.day, - hour, - &localised_text.hour, - min, - &localised_text.minute - ) - } - - pub fn get_anime_count(&self) -> i32 { - self.data.user.statistics.anime.count.unwrap_or(0) - } - - pub fn get_anime_score(&self) -> f64 { - self.data.user.statistics.anime.mean_score.unwrap_or(0f64) - } - - pub fn get_anime_standard_deviation(&self) -> f64 { - self.data - .user - .statistics - .anime - .standard_deviation - .unwrap_or(0f64) - } - - pub fn get_anime_completed(&self) -> i32 { - let anime_statuses = &self.data.user.statistics.anime.statuses; - let mut anime_completed = 0; - for i in anime_statuses { - if i.status == *"COMPLETED" { - anime_completed = i.count; - } - } - anime_completed - } - - pub fn get_color(&self) -> Colour { - let mut _color = Colour::FABLED_PINK; - match self - .data - .user - .options - .profile_color - .clone() - .unwrap_or_else(|| "#FF00FF".to_string()) - .as_str() - { - "blue" => _color = Colour::BLUE, - "purple" => _color = Colour::PURPLE, - "pink" => _color = Colour::MEIBE_PINK, - "orange" => _color = Colour::ORANGE, - "red" => _color = Colour::RED, - "green" => _color = Colour::DARK_GREEN, - "gray" => _color = Colour::LIGHT_GREY, - _ => { - _color = { - let hex_code = "#0D966D"; - let color_code = u32::from_str_radix(&hex_code[1..], 16).unwrap(); - Colour::new(color_code) - } - } - } - _color - } - - pub fn get_username(&self) -> String { - self.data - .user - .name - .clone() - .unwrap_or_else(|| "N/A".to_string()) - } - - pub fn get_pfp(&self) -> String { - self.data.user.avatar.large.clone().unwrap_or_else(|| - "https://imgs.search.brave.com/CYnhSvdQcm9aZe3wG84YY0B19zT2wlAuAkiAGu0mcLc/rs:fit:640:400:1/g:ce/aHR0cDovL3d3dy5m/cmVtb250Z3VyZHdh/cmEub3JnL3dwLWNv/bnRlbnQvdXBsb2Fk/cy8yMDIwLzA2L25v/LWltYWdlLWljb24t/Mi5wbmc" - .to_string()) - } - - pub fn get_banner(&self) -> String { - format!("https://img.anili.st/user/{}", self.data.user.id.unwrap()) - } - - pub fn get_one_manga_genre(&self, i: usize) -> String { - if let Some(genre) = self - .data - .user - .statistics - .manga - .genres - .get(i) - .and_then(|g| g.genre.as_ref()) - { - genre.clone() - } else { - "N/A".to_string() - } - } - - pub fn get_manga_genre(&self) -> String { - let mut manga_genre = String::new(); - for i in 0..3 { - let genre = self.get_one_manga_genre(i); - manga_genre.push_str(&format!("{} / ", genre)); - } - manga_genre.pop(); - manga_genre.pop(); - manga_genre - } - - pub fn get_one_manga_tag(&self, i: usize) -> String { - if let Some(tag) = self - .data - .user - .statistics - .manga - .tags - .get(i) - .and_then(|g| g.tag.name.as_ref()) - { - tag.clone() - } else { - "N/A".to_string() - } - } - - pub fn get_manga_tag(&self) -> String { - let mut manga_tag_name = String::new(); - for i in 0..3 { - let tags = self.get_one_manga_tag(i); - manga_tag_name.push_str(&format!("{} / ", tags)); - } - manga_tag_name.pop(); - manga_tag_name.pop(); - manga_tag_name - } - - pub fn get_manga_chapter(&self) -> i32 { - self.data.user.statistics.manga.chapters_read.unwrap_or(0) - } - - pub fn get_manga_count(&self) -> i32 { - self.data.user.statistics.manga.count.unwrap_or(0) - } - - pub fn get_manga_score(&self) -> f64 { - self.data.user.statistics.manga.mean_score.unwrap_or(0f64) - } - - pub fn get_manga_standard_deviation(&self) -> f64 { - self.data - .user - .statistics - .manga - .standard_deviation - .unwrap_or(0f64) - } - - pub fn get_manga_completed(&self) -> i32 { - let manga_statuses = &self.data.user.statistics.manga.statuses; - let mut manga_completed = 0; - for i in manga_statuses { - if i.status == *"COMPLETED" { - manga_completed = i.count; - } - } - manga_completed - } - - pub fn get_user_url(&self) -> String { - format!("https://anilist.co/user/{}", self.data.user.id.unwrap_or(1)) - } - - pub fn get_user_anime_url(&self) -> String { - format!("{}/animelist", self.get_user_url()) - } - - pub fn get_user_manga_url(&self) -> String { - format!("{}/mangalist", self.get_user_url()) - } - - pub async fn new_user_by_id(id: i32) -> Result<UserWrapper, String> { - let query_id: &str = " -query ($name: Int, $limit: Int = 5) { - User(id: $name) { - id - name - avatar { - large - } - statistics { - anime { - count - meanScore - standardDeviation - minutesWatched - tags(limit: $limit, sort: MEAN_SCORE_DESC) { - tag { - name - } - } - genres(limit: $limit, sort: MEAN_SCORE_DESC) { - genre - } - statuses(sort: COUNT_DESC){ - count - status - } - } - manga { - count - meanScore - standardDeviation - chaptersRead - tags(limit: $limit, sort: MEAN_SCORE_DESC) { - tag { - name - } - } - genres(limit: $limit, sort: MEAN_SCORE_DESC) { - genre - } - statuses(sort: COUNT_DESC){ - count - status - } - } - } -options{ - profileColor - } - bannerImage - } -} -"; - let json = json!({"query": query_id, "variables": {"name": id}}); - let resp = make_request_anilist(json, true).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(String::from("Error: Failed to retrieve user data")) - } - } - } - - pub async fn new_user_by_search(search: &String) -> Result<UserWrapper, String> { - let query_string: &str = " -query ($name: String, $limit: Int = 5) { - User(name: $name) { - id - name - avatar { - large - } - statistics { - anime { - count - meanScore - standardDeviation - minutesWatched - tags(limit: $limit, sort: MEAN_SCORE_DESC) { - tag { - name - } - } - genres(limit: $limit, sort: MEAN_SCORE_DESC) { - genre - } - statuses(sort: COUNT_DESC){ - count - status - } - } - manga { - count - meanScore - standardDeviation - chaptersRead - tags(limit: $limit, sort: MEAN_SCORE_DESC) { - tag { - name - } - } - genres(limit: $limit, sort: MEAN_SCORE_DESC) { - genre - } - statuses(sort: COUNT_DESC){ - count - status - } - } - } -options{ - profileColor - } - bannerImage - } -} -"; - let json = json!({"query": query_string, "variables": {"name": search}}); - let resp = make_request_anilist(json, true).await; - match serde_json::from_str(&resp) { - Ok(result) => Ok(result), - Err(e) => { - println!("Failed to parse JSON: {}", e); - Err(String::from("Error: Failed to retrieve user data")) - } - } - } -} diff --git a/src/structure/embed/ai/mod.rs b/src/structure/embed/ai/mod.rs deleted file mode 100644 index 613708a3..00000000 --- a/src/structure/embed/ai/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod struct_lang_image; -pub mod struct_lang_transcript; -pub mod struct_lang_translation; diff --git a/src/structure/embed/ai/struct_lang_image.rs b/src/structure/embed/ai/struct_lang_image.rs deleted file mode 100644 index 40e47d45..00000000 --- a/src/structure/embed/ai/struct_lang_image.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ImageLocalisedText { - pub title: String, -} - -impl ImageLocalisedText { - pub async fn get_image_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<ImageLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/ai/image.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, ImageLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - } - } -} diff --git a/src/structure/embed/ai/struct_lang_transcript.rs b/src/structure/embed/ai/struct_lang_transcript.rs deleted file mode 100644 index a95804a0..00000000 --- a/src/structure/embed/ai/struct_lang_transcript.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct TranscriptLocalisedText { - pub title: String, -} - -impl TranscriptLocalisedText { - pub async fn get_transcript_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<TranscriptLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/ai/transcript.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, TranscriptLocalisedText> = match serde_json::from_str(&json) - { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - } - } -} diff --git a/src/structure/embed/ai/struct_lang_translation.rs b/src/structure/embed/ai/struct_lang_translation.rs deleted file mode 100644 index 8efd603a..00000000 --- a/src/structure/embed/ai/struct_lang_translation.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct TranslationLocalisedText { - pub title: String, -} - -impl TranslationLocalisedText { - pub async fn get_translation_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<TranslationLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/ai/translation.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, TranslationLocalisedText> = match serde_json::from_str(&json) - { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - } - } -} diff --git a/src/structure/embed/anilist/mod.rs b/src/structure/embed/anilist/mod.rs deleted file mode 100644 index 36d22172..00000000 --- a/src/structure/embed/anilist/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod struct_lang_add_activity; -pub mod struct_lang_anime; -pub mod struct_lang_character; -pub mod struct_lang_level; -pub mod struct_lang_media; -pub mod struct_lang_register; -pub mod struct_lang_send_activity; -pub mod struct_lang_staff; -pub mod struct_lang_studio; -pub mod struct_lang_user; diff --git a/src/structure/embed/anilist/struct_lang_add_activity.rs b/src/structure/embed/anilist/struct_lang_add_activity.rs deleted file mode 100644 index 7463b669..00000000 --- a/src/structure/embed/anilist/struct_lang_add_activity.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct AddActivityLocalisedText { - pub error_no_media: String, - pub title1: String, - pub title2: String, - pub already_added: String, - pub adding: String, - pub error_slash_command: String, -} - -impl AddActivityLocalisedText { - pub async fn get_add_activity_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<AddActivityLocalisedText, &'static str> { - let mut file = - match File::open("./lang_file/embed/anilist/anime_activity/add_activity.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, AddActivityLocalisedText> = match serde_json::from_str(&json) - { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_anime.rs b/src/structure/embed/anilist/struct_lang_anime.rs deleted file mode 100644 index 1db09f68..00000000 --- a/src/structure/embed/anilist/struct_lang_anime.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct AnimeLocalisedText { - pub desc_title: String, - pub desc_part_1: String, - pub desc_part_2: String, - pub desc_part_3: String, - pub desc_part_4: String, - pub desc_part_5: String, - pub desc_part_6: String, - pub desc_part_7: String, - pub fields_name_1: String, - pub fields_name_2: String, - pub error_anime_not_found: String, -} - -impl AnimeLocalisedText { - pub async fn get_anime_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<AnimeLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/anime.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, AnimeLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_character.rs b/src/structure/embed/anilist/struct_lang_character.rs deleted file mode 100644 index e1b7fd0e..00000000 --- a/src/structure/embed/anilist/struct_lang_character.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct CharacterLocalisedText { - pub age: String, - pub gender: String, - pub date_of_birth: String, - pub favourite: String, - pub desc: String, - pub error_no_character: String, - pub info: String, -} - -impl CharacterLocalisedText { - pub async fn get_character_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<CharacterLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/character.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, CharacterLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_level.rs b/src/structure/embed/anilist/struct_lang_level.rs deleted file mode 100644 index 1774064b..00000000 --- a/src/structure/embed/anilist/struct_lang_level.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct LevelLocalisedText { - pub level: String, - pub xp: String, - pub progression_1: String, - pub progression_2: String, -} - -impl LevelLocalisedText { - pub async fn get_level_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<LevelLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/level.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, LevelLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_media.rs b/src/structure/embed/anilist/struct_lang_media.rs deleted file mode 100644 index 5936784f..00000000 --- a/src/structure/embed/anilist/struct_lang_media.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct MediaLocalisedText { - pub full_name: String, - pub user_pref: String, - pub role: String, - pub format: String, - pub source: String, - pub start_date: String, - pub end_date: String, - pub fields_name_1: String, - pub fields_name_2: String, - pub desc_title: String, - pub error_no_media: String, -} - -impl MediaLocalisedText { - pub async fn get_media_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<MediaLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/media.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, MediaLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_register.rs b/src/structure/embed/anilist/struct_lang_register.rs deleted file mode 100644 index 10f42cd7..00000000 --- a/src/structure/embed/anilist/struct_lang_register.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedText { - pub part_1: String, - pub part_2: String, - pub part_3: String, - pub error_slash_command: String, -} - -impl RegisterLocalisedText { - pub async fn get_register_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<RegisterLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/register.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, RegisterLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_send_activity.rs b/src/structure/embed/anilist/struct_lang_send_activity.rs deleted file mode 100644 index eecab005..00000000 --- a/src/structure/embed/anilist/struct_lang_send_activity.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct SendActivityLocalisedText { - pub title: String, - pub ep: String, - pub of: String, - pub end: String, -} - -impl SendActivityLocalisedText { - pub async fn get_send_activity_localised( - guild_id: String, - ) -> Result<SendActivityLocalisedText, &'static str> { - let mut file = - match File::open("./lang_file/embed/anilist/anime_activity/send_activity.json") { - Ok(file) => file, - Err(_) => { - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - return Err("not found"); - } - } - - let json_data: HashMap<String, SendActivityLocalisedText> = - match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - return Err("not found"); - } - }; - - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_staff.rs b/src/structure/embed/anilist/struct_lang_staff.rs deleted file mode 100644 index 42b8d607..00000000 --- a/src/structure/embed/anilist/struct_lang_staff.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct StaffLocalisedText { - pub desc_title: String, - pub date_of_birth: String, - pub date_of_death: String, - pub hometown: String, - pub primary_language: String, - pub primary_occupation: String, - pub media: String, - pub va: String, - pub error_slash_command: String, -} - -impl StaffLocalisedText { - pub async fn get_staff_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<StaffLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/staff.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, StaffLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_studio.rs b/src/structure/embed/anilist/struct_lang_studio.rs deleted file mode 100644 index 0779896a..00000000 --- a/src/structure/embed/anilist/struct_lang_studio.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct StudioLocalisedText { - pub anime_or_manga: String, - pub error_slash_command: String, - pub favorite: String, -} - -impl StudioLocalisedText { - pub async fn get_studio_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<StudioLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/studio.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, StudioLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/anilist/struct_lang_user.rs b/src/structure/embed/anilist/struct_lang_user.rs deleted file mode 100644 index 1bc07094..00000000 --- a/src/structure/embed/anilist/struct_lang_user.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct UserLocalisedText { - pub manga_title: String, - pub manga_count: String, - pub manga_completed: String, - pub manga_chapter_read: String, - pub manga_mean_score: String, - pub manga_standard_deviation: String, - pub manga_pref_tag: String, - pub manga_pref_genre: String, - pub anime_title: String, - pub anime_count: String, - pub anime_completed: String, - pub anime_time_watch: String, - pub anime_mean_score: String, - pub anime_standard_deviation: String, - pub anime_pref_tag: String, - pub anime_pref_genre: String, - pub week: String, - pub day: String, - pub hour: String, - pub minute: String, - pub error_slash_command: String, -} - -impl UserLocalisedText { - pub async fn get_user_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<UserLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/anilist/user.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, UserLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - return if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - }; - } -} diff --git a/src/structure/embed/error.rs b/src/structure/embed/error.rs deleted file mode 100644 index 45e07f8f..00000000 --- a/src/structure/embed/error.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ErrorLocalisedText { - pub error_title: String, - pub module_off: String, - pub forgot_module: String, - pub no_token: String, - pub no_base_url: String, - pub not_implemented: String, - pub error_request: String, - pub error_no_avatar: String, - pub error_parsing_json: String, - pub error_url: String, - pub error_resolving_value: String, - pub admin_instance_error: String, - pub error_option: String, - pub error_creating_header: String, - pub error_getting_response_from_url: String, - pub error_getting_bytes: String, - pub error_writing_file: String, - pub error_file_type: String, - pub error_file_extension: String, - pub error_no_anime_specified: String, - pub error_not_nsfw: String, -} diff --git a/src/structure/embed/general/mod.rs b/src/structure/embed/general/mod.rs deleted file mode 100644 index 02f41958..00000000 --- a/src/structure/embed/general/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod struct_lang_avatar; -pub mod struct_lang_banner; -pub mod struct_lang_credit; -pub mod struct_lang_in_progress; -pub mod struct_lang_info; -pub mod struct_lang_lang; -pub mod struct_lang_level; -pub mod struct_lang_module_activation; -pub mod struct_lang_ping; -pub mod struct_lang_profile; diff --git a/src/structure/embed/general/struct_lang_avatar.rs b/src/structure/embed/general/struct_lang_avatar.rs deleted file mode 100644 index 35cb4b1a..00000000 --- a/src/structure/embed/general/struct_lang_avatar.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct AvatarLocalisedText { - pub title: String, - pub no_banner_title: String, - pub error_no_user: String, -} - -impl AvatarLocalisedText { - pub async fn get_avatar_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<AvatarLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/avatar.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, AvatarLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_banner.rs b/src/structure/embed/general/struct_lang_banner.rs deleted file mode 100644 index da38b501..00000000 --- a/src/structure/embed/general/struct_lang_banner.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct BannerLocalisedText { - pub error_slash_command: String, - pub title: String, - pub description: String, - pub no_banner_title: String, - pub error_no_user: String, -} - -impl BannerLocalisedText { - pub async fn get_banner_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<BannerLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/banner.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, BannerLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_credit.rs b/src/structure/embed/general/struct_lang_credit.rs deleted file mode 100644 index 6d0085ee..00000000 --- a/src/structure/embed/general/struct_lang_credit.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct CreditLocalisedText { - pub title: String, - pub list: Vec<Credit>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Credit { - pub text: String, -} - -impl CreditLocalisedText { - pub async fn get_credit_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<CreditLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/credit.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, CreditLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_in_progress.rs b/src/structure/embed/general/struct_lang_in_progress.rs deleted file mode 100644 index d2a9f78a..00000000 --- a/src/structure/embed/general/struct_lang_in_progress.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct InProgressLocalisedText { - pub title: String, - pub description: String, -} - -impl InProgressLocalisedText { - pub async fn get_in_progress_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<InProgressLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/in_progress.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, InProgressLocalisedText> = match serde_json::from_str(&json) - { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_info.rs b/src/structure/embed/general/struct_lang_info.rs deleted file mode 100644 index c35ed945..00000000 --- a/src/structure/embed/general/struct_lang_info.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct InfoLocalisedText { - pub title: String, - pub description: String, - pub footer: String, - pub button_see_on_github: String, - pub button_official_website: String, - pub button_official_discord: String, - pub button_add_the_bot: String, - pub server_specific_info: String, - pub on: String, - pub off: String, -} - -impl InfoLocalisedText { - pub async fn get_info_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<InfoLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/info.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, InfoLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_lang.rs b/src/structure/embed/general/struct_lang_lang.rs deleted file mode 100644 index 7e2cf5a9..00000000 --- a/src/structure/embed/general/struct_lang_lang.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use serenity::utils::Colour; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct LangLocalisedText { - pub title: String, - pub description: String, - pub error_perm: String, -} - -impl LangLocalisedText { - pub async fn get_ping_localised( - _color: Colour, - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<LangLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/lang.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, LangLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_level.rs b/src/structure/embed/general/struct_lang_level.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/structure/embed/general/struct_lang_level.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/structure/embed/general/struct_lang_module_activation.rs b/src/structure/embed/general/struct_lang_module_activation.rs deleted file mode 100644 index 37ec8371..00000000 --- a/src/structure/embed/general/struct_lang_module_activation.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, no_langage_error, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ModuleLocalisedText { - pub on: String, - pub off: String, -} - -impl ModuleLocalisedText { - pub async fn get_module_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<ModuleLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/module_activation.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, ModuleLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - no_langage_error(ctx, command).await; - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_ping.rs b/src/structure/embed/general/struct_lang_ping.rs deleted file mode 100644 index f52cfa2d..00000000 --- a/src/structure/embed/general/struct_lang_ping.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct PingLocalisedText { - pub title: String, - pub description_part_1: String, - pub description_part_2: String, - pub description_part_3: String, -} - -impl PingLocalisedText { - pub async fn get_ping_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<PingLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/ping.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, PingLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - } - } -} diff --git a/src/structure/embed/general/struct_lang_profile.rs b/src/structure/embed/general/struct_lang_profile.rs deleted file mode 100644 index dd0525e0..00000000 --- a/src/structure/embed/general/struct_lang_profile.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use crate::function::error_management::no_lang_error::{ - error_cant_read_langage_file, error_langage_file_not_found, error_no_langage_guild_id, - error_parsing_langage_json, -}; -use crate::function::general::get_guild_langage::get_guild_langage; -use serde::{Deserialize, Serialize}; -use serenity::client::Context; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ProfileLocalisedText { - pub title: String, - pub error_no_user: String, - pub user_id: String, - pub is_bot: String, - pub public_flag: String, - pub joined_at: String, - pub created_at: String, -} - -impl ProfileLocalisedText { - pub async fn get_profile_localised( - ctx: &Context, - command: &ApplicationCommandInteraction, - ) -> Result<ProfileLocalisedText, &'static str> { - let mut file = match File::open("./lang_file/embed/general/profile.json") { - Ok(file) => file, - Err(_) => { - error_langage_file_not_found(ctx, command).await; - return Err("not found"); - } - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => { - error_cant_read_langage_file(ctx, command).await; - return Err("not found"); - } - } - - let json_data: HashMap<String, ProfileLocalisedText> = match serde_json::from_str(&json) { - Ok(data) => data, - Err(_) => { - error_parsing_langage_json(ctx, command).await; - return Err("not found"); - } - }; - - let guild_id = match command.guild_id { - Some(id) => id.0.to_string(), - None => { - error_no_langage_guild_id(ctx, command).await; - return Err("not found"); - } - }; - let lang_choice = get_guild_langage(guild_id).await; - - if let Some(localised_text) = json_data.get(lang_choice.as_str()) { - Ok(localised_text.clone()) - } else { - Err("not found") - } - } -} diff --git a/src/structure/mod.rs b/src/structure/mod.rs deleted file mode 100644 index ff30b3c7..00000000 --- a/src/structure/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod anilist; -pub mod embed; -pub mod register; -pub mod struct_shard_manager; diff --git a/src/structure/register/ai/mod.rs b/src/structure/register/ai/mod.rs deleted file mode 100644 index 9e36793d..00000000 --- a/src/structure/register/ai/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod struct_image_register; -pub mod struct_transcript_register; -pub mod struct_translation_register; diff --git a/src/structure/register/ai/struct_image_register.rs b/src/structure/register/ai/struct_image_register.rs deleted file mode 100644 index 55a98d2f..00000000 --- a/src/structure/register/ai/struct_image_register.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ImageRegister { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedImageList = HashMap<String, ImageRegister>; - -impl ImageRegister { - pub fn get_image_register_localised() -> Result<RegisterLocalisedImageList, &'static str> { - let mut file = match File::open("./lang_file/command_register/ai/image.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/ai/struct_transcript_register.rs b/src/structure/register/ai/struct_transcript_register.rs deleted file mode 100644 index da8591bb..00000000 --- a/src/structure/register/ai/struct_transcript_register.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedTranscript { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, - pub option2: String, - pub option2_desc: String, - pub option3: String, - pub option3_desc: String, -} - -type RegisterLocalisedTranscriptList = HashMap<String, RegisterLocalisedTranscript>; - -impl RegisterLocalisedTranscript { - pub fn get_transcript_register_localised( - ) -> Result<RegisterLocalisedTranscriptList, &'static str> { - let mut file = match File::open("./lang_file/command_register/ai/transcript.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/ai/struct_translation_register.rs b/src/structure/register/ai/struct_translation_register.rs deleted file mode 100644 index b9cc33cc..00000000 --- a/src/structure/register/ai/struct_translation_register.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedTranslation { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, - pub option2: String, - pub option2_desc: String, -} - -type RegisterLocalisedTranslationList = HashMap<String, RegisterLocalisedTranslation>; - -impl RegisterLocalisedTranslation { - pub fn get_translation_register_localised( - ) -> Result<RegisterLocalisedTranslationList, &'static str> { - let mut file = match File::open("./lang_file/command_register/ai/translation.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/mod.rs b/src/structure/register/anilist/mod.rs deleted file mode 100644 index b9191d53..00000000 --- a/src/structure/register/anilist/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod struct_add_activity_register; -pub mod struct_anime_register; -pub mod struct_character_register; -pub mod struct_compare_register; -pub mod struct_level_register; -pub mod struct_ln_register; -pub mod struct_manga_register; -pub mod struct_register_register; -pub mod struct_staff_register; -pub mod struct_studio_register; -pub mod struct_user_register; -pub mod struct_waifu_register; diff --git a/src/structure/register/anilist/struct_add_activity_register.rs b/src/structure/register/anilist/struct_add_activity_register.rs deleted file mode 100644 index c81d44db..00000000 --- a/src/structure/register/anilist/struct_add_activity_register.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedAddActivity { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, - pub option2: String, - pub option2_desc: String, -} - -type RegisterLocalisedAddActivityList = HashMap<String, RegisterLocalisedAddActivity>; - -impl RegisterLocalisedAddActivity { - /// This function is used to read a local language JSON file (`add_activity.json`) and convert it to - /// `RegisterLocalisedAddActivityList` type. It is located in the path `./lang_file/command_register/anilist/anime_activity/add_activity.json`. - /// - /// The purpose of this function is to facilitate internationalization/localization by providing - /// a way to read localized strings from a configuration file. - /// - /// # Returns - /// - /// On Success: A `Result` wrapping `RegisterLocalisedAddActivityList`. This contains a list of localized - /// strings for adding activities. - /// - /// On Failure: A `Result` wrapping a static string, indicating the error reason. - /// - /// - Fails when the file could not be opened ("Failed to open file"). - /// - Fails when the file could not be read into a string ("Failed to read file"). - /// - Fails when the string could not be parsed into json ("Failed to parse json"). - /// - /// # Example - /// - /// ```Rust - /// let result = get_add_activity_register_localised(); - /// match result { - /// Ok(data) => println!("{:?}", data), - /// Err(e) => println!("Error: {}", e), - /// } - /// ``` - /// - /// # Note - /// - /// The error messages are static and not particularly descriptive. As an enhancement, you might want to - /// return more descriptive error types, such as using `std::io::Error` or your own custom error type. - pub fn get_add_activity_register_localised( - ) -> Result<RegisterLocalisedAddActivityList, &'static str> { - let mut file = match File::open( - "./lang_file/command_register/anilist/anime_activity/add_activity.json", - ) { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_anime_register.rs b/src/structure/register/anilist/struct_anime_register.rs deleted file mode 100644 index a8854b9d..00000000 --- a/src/structure/register/anilist/struct_anime_register.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedAnime { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedAnimeList = HashMap<String, RegisterLocalisedAnime>; - -impl RegisterLocalisedAnime { - /// `get_anime_register_localised` is a function that returns a `Result` - /// with either `RegisterLocalisedAnimeList` type data on success or an - /// &'static str error string on failure. - /// - /// # Functionality - /// - /// The function attempts to open and read a json file located at - /// './lang_file/command_register/anilist/anime.json'. It reads the file - /// content into a `String` type variable `json`. - /// - /// The function then attempts to parse the `json` string into a - /// `RegisterLocalisedAnimeList` type variable `data`. - /// - /// # Errors - /// - /// The function returns an error if it fails to open the file, read - /// the file, or parse the json string. The specific actions that - /// trigger these errors are encapsulated in `Result` returning - /// expressions with the `?` operator. - /// - /// # Returns - /// - /// If the function is successful, it returns `Ok(data)` where `data` - /// is of type `RegisterLocalisedAnimeList`. If an error occurs, it - /// returns `Err("Failed to open file")` or `Err("Failed to read file")` - /// or `Err("Failed to parse json.")` depending on the error. - /// - /// # Example - /// - /// ``` - /// use crate::RegisterLocalisedAnimeList; // Assume this is the right path - /// - /// let result = get_anime_register_localised(); - /// match result { - /// Ok(data) => println!("Successfully got data: {:?}", data), - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// ``` - pub fn get_anime_register_localised() -> Result<RegisterLocalisedAnimeList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/anime.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_character_register.rs b/src/structure/register/anilist/struct_character_register.rs deleted file mode 100644 index 6279f455..00000000 --- a/src/structure/register/anilist/struct_character_register.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedCharacter { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedCharacterList = HashMap<String, RegisterLocalisedCharacter>; - -impl RegisterLocalisedCharacter { - /// `get_character_register_localised` is a function that returns a Result containing either a RegisterLocalisedCharacterList or an error message. - /// - /// This function attempts to open a language file for character registration commands from AniList. - /// The function will return an early error message if it fails to open the file. - /// - /// After successfully opening the language file, it reads the content of the file to a string. If it fails while reading the content, it will return an early error message. - /// - /// The content of the file is then parsed as JSON and converted into a RegisterLocalisedCharacterList structure. If it fails during the conversion, it will return an error message. - /// - /// ## Returns - /// A `Result<RegisterLocalisedCharacterList, &'static str>` where - /// * `Ok(RegisterLocalisedCharacterList)` means the function successfully read and parsed the file, returning the data as RegisterLocalisedCharacterList - /// * `Err(&'static str)` means the function encountered an error at some point. The error message indicates at which point the function failed. - /// - /// ## Errors - /// This function will return `Err("Failed to open file")` if it fails to open the file. - /// It will return `Err("Failed to read file")` if it fails to read the content of the file. - /// It will return `Err("Failed to parse json.")` if it cannot parse the file content as JSON. - /// - /// # Examples - /// To use `get_character_register_localised`, you can match on the Result like so: - /// ```rust - /// match get_character_register_localised() { - /// Ok(data) => println!("Data parsed successfully: {:?}", data), - /// Err(e) => println!("Error occurred: {}", e), - /// } - /// ``` - pub fn get_character_register_localised() -> Result<RegisterLocalisedCharacterList, &'static str> - { - let mut file = match File::open("./lang_file/command_register/anilist/character.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_compare_register.rs b/src/structure/register/anilist/struct_compare_register.rs deleted file mode 100644 index c2187121..00000000 --- a/src/structure/register/anilist/struct_compare_register.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedCompare { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, - pub option2: String, - pub option2_desc: String, -} - -type RegisterLocalisedCompareList = HashMap<String, RegisterLocalisedCompare>; - -impl RegisterLocalisedCompare { - /// # get_compare_register_localised - /// - /// This function attempts to open a JSON file located at `./lang_file/command_register/anilist/compare.json`. - /// After successfully opening the file, it reads the content into a string, then parses the JSON content - /// into a `Result` of a `RegisterLocalisedCompareList` object or a static string error message. - /// - /// ## Return - /// - /// If successful, the function returns `Result::Ok(RegisterLocalisedCompareList)` where `RegisterLocalisedCompareList` - /// is the object representation of the parsed JSON content. - /// If an error occurs during any operation (open, read, or parse), the function returns `Result::Err(&'static str)`, - /// with the error message explaining the failed operation. - /// - /// ## Errors - /// - /// The function can return three possible error messages: - /// * "Failed to open file" - If the system cannot open the JSON file. - /// * "Failed to read file" - If the system cannot read the content of the file. - /// * "Failed to parse json." - If the content of the file could not be correctly parsed to a `RegisterLocalisedCompareList` object. - /// - /// ## Example - /// - /// ```rust - /// let result = get_compare_register_localised(); - /// match result { - /// Ok(data) => println!("{:?}", data), - /// Err(e) => println!("Error: {}", e), - /// } - /// ``` - pub fn get_compare_register_localised() -> Result<RegisterLocalisedCompareList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/compare.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_level_register.rs b/src/structure/register/anilist/struct_level_register.rs deleted file mode 100644 index 0c08c834..00000000 --- a/src/structure/register/anilist/struct_level_register.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedLevel { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedLevelList = HashMap<String, RegisterLocalisedLevel>; - -impl RegisterLocalisedLevel { - /// This function handles the process of localizing the register by fetching - /// the localised version from a local json file. - /// - /// # Errors - /// - /// The function can encounter the following types of errors: - /// 1. It fails to open the local json file (`./lang_file/command_register/anilist/ln.json`). - /// 2. It fails to read the file contents into a string. - /// 3. It fails to parse the string content into a `RegisterLocalisedLNList` object. - /// In each of these cases, the function will terminate and return an appropriate error message as a string. - /// - /// # Returns - /// - /// The function returns a `Result` object. This object contains a `RegisterLocalisedLNList` struct - /// when the operation is successful or an error message when the operation fails. - /// - /// The function is designed to be used in the context of a registration process. - /// It supports localization, thereby providing flexibility and a better user experience. - /// - /// It is a public interface, meaning it can be accessed from modules outside the one it is defined in. - /// - /// Note: You need to make sure `"./lang_file/command_register/anilist/ln.json"` file should be available - /// and in correct format. - /// - /// # Example usage: - /// - /// ``` - /// let result = get_ln_register_localised(); - /// - /// match result { - /// Ok(localised_data) => { - /// // use localised_data - /// } - /// Err(err_msg) => { - /// // handle error - /// } - /// } - /// ``` - pub fn get_level_register_localised() -> Result<RegisterLocalisedLevelList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/ln.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_ln_register.rs b/src/structure/register/anilist/struct_ln_register.rs deleted file mode 100644 index 4106d41a..00000000 --- a/src/structure/register/anilist/struct_ln_register.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedLN { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedLNList = HashMap<String, RegisterLocalisedLN>; - -impl RegisterLocalisedLN { - /// This function handles the process of localizing the register by fetching - /// the localised version from a local json file. - /// - /// # Errors - /// - /// The function can encounter the following types of errors: - /// 1. It fails to open the local json file (`./lang_file/command_register/anilist/ln.json`). - /// 2. It fails to read the file contents into a string. - /// 3. It fails to parse the string content into a `RegisterLocalisedLNList` object. - /// In each of these cases, the function will terminate and return an appropriate error message as a string. - /// - /// # Returns - /// - /// The function returns a `Result` object. This object contains a `RegisterLocalisedLNList` struct - /// when the operation is successful or an error message when the operation fails. - /// - /// The function is designed to be used in the context of a registration process. - /// It supports localization, thereby providing flexibility and a better user experience. - /// - /// It is a public interface, meaning it can be accessed from modules outside the one it is defined in. - /// - /// Note: You need to make sure `"./lang_file/command_register/anilist/ln.json"` file should be available - /// and in correct format. - /// - /// # Example usage: - /// - /// ``` - /// let result = get_ln_register_localised(); - /// - /// match result { - /// Ok(localised_data) => { - /// // use localised_data - /// } - /// Err(err_msg) => { - /// // handle error - /// } - /// } - /// ``` - pub fn get_ln_register_localised() -> Result<RegisterLocalisedLNList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/ln.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_manga_register.rs b/src/structure/register/anilist/struct_manga_register.rs deleted file mode 100644 index dd4d8f0e..00000000 --- a/src/structure/register/anilist/struct_manga_register.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedManga { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedMangaList = HashMap<String, RegisterLocalisedManga>; - -impl RegisterLocalisedManga { - /// `get_manga_register_localised` is a function in the Rust programming language. - /// - /// This function is used to read and parse a JSON file that contains localised manga registration information. - /// - /// # Returns - /// - /// This function returns a `Result` variant. The `Ok` variant contains a `RegisterLocalisedMangaList` representing the parsed information from the JSON file. If there is an error when trying to open, read, or parse the file, the function returns an `Err` variant containing a static string detailing the error that occurred. - /// - /// # Errors - /// - /// This function will return an error in the following situations: - /// - /// * The file cannot be opened (returns "Failed to open file"). - /// * The file cannot be read (returns "Failed to read file"). - /// * The JSON in the file cannot be parsed (returns "Failed to parse json."). - /// - /// # Example - /// - /// ```Rust - /// let result = get_manga_register_localised(); - /// match result { - /// Ok(register) => /* process the register */, - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// ``` - /// - pub fn get_manga_register_localised() -> Result<RegisterLocalisedMangaList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/manga.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_register_register.rs b/src/structure/register/anilist/struct_register_register.rs deleted file mode 100644 index 7bef5a8b..00000000 --- a/src/structure/register/anilist/struct_register_register.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedRegister { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedRegisterList = HashMap<String, RegisterLocalisedRegister>; - -impl RegisterLocalisedRegister { - /// Function: `get_register_register_localised` - /// - /// This is a public function that reads a JSON file from the disk, parses it into a - /// `RegisterLocalisedRegisterList` and returns the result or an error string if any of - /// the operations fails. - /// - /// # Return - /// - /// The function returns a `Result` holding either a: - /// * `RegisterLocalisedRegisterList` - parsed successfully from the JSON file. - /// * `&'static str` - static string representing an error message. - /// - /// # Errors - /// - /// The function can fail on the following cases: - /// * If the file is not found, cannot be accessed or opened for some reason, - /// it will return the string "Failed to open file". - /// * If the file cannot be read into a string, it will return "Failed to read file". - /// * If the JSON string cannot be parsed into a `RegisterLocalisedRegisterList`, - /// it will return "Failed to parse json." - /// - /// # Example - /// - /// ``` - /// use your_module_name::get_register_register_localised; - /// - /// let result = get_register_register_localised(); - /// - /// match result { - /// Ok(data) => println!("Data: {:?}", data), - /// Err(e) => println!("Error happened: {}", e), - /// } - /// ``` - /// - /// # Panics - /// - /// This function doesn't panic. - pub fn get_register_register_localised() -> Result<RegisterLocalisedRegisterList, &'static str> - { - let mut file = match File::open("./lang_file/command_register/anilist/register.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_staff_register.rs b/src/structure/register/anilist/struct_staff_register.rs deleted file mode 100644 index 63f40a16..00000000 --- a/src/structure/register/anilist/struct_staff_register.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedStaff { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedStaffList = HashMap<String, RegisterLocalisedStaff>; - -impl RegisterLocalisedStaff { - /// Opens and reads from a localized staff registration file in JSON format. - /// - /// This function performs the following steps: - /// 1. Open the file "./lang_file/command_register/anilist/staff.json". - /// 2. Read the contents of the file into a string. - /// 3. Parse the string as JSON using Serde. - /// - /// # Returns - /// - /// * On success, a `std::result::Result::Ok` value is returned containing an instance of `RegisterLocalisedStaffList`, - /// constructed from the parsed JSON. - /// * If the file cannot be opened, read, or the JSON cannot be parsed, a `std::result::Result::Err` value is returned with an error message. - /// - /// # Errors - /// - /// This function will return an error if the file cannot be opened, the file cannot be read into a string, or the string cannot be parsed as JSON. - /// - /// # Example - /// - /// ```Rust - /// let result = get_staff_register_localised(); - /// match result { - /// Ok(data) => println!("Staff register: {:?}", data), - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// ``` - pub fn get_staff_register_localised() -> Result<RegisterLocalisedStaffList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/staff.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_studio_register.rs b/src/structure/register/anilist/struct_studio_register.rs deleted file mode 100644 index 6c0db593..00000000 --- a/src/structure/register/anilist/struct_studio_register.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedStudio { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedStudioList = HashMap<String, RegisterLocalisedStudio>; - -impl RegisterLocalisedStudio { - /// # get_studio_register_localised - /// - /// This function fetches the localised studio information from a JSON file. - /// - /// ## Returns - /// - /// This function returns a `Result<RegisterLocalisedStudioList, &'static str>`. If the function is successful in reading - /// and parsing the JSON file, it will return `Ok(RegisterLocalisedStudioList)`. In case of any error while opening, reading - /// or parsing the JSON file, it will return an `Err(&'static str)`. The error message describes what caused the failure. - /// - /// ## Errors - /// - /// This function will return an error in the following circumstances: - /// - /// - If the JSON file fails to open, the error message will be "Failed to open file". - /// - /// - If the JSON file fails to be read, the error message will be "Failed to read file". - /// - /// - If the JSON string from the file can not be parsed, the error message will be "Failed to parse json". - /// - /// ## Examples - /// - /// ```Rust - /// extern crate your_crate; - /// - /// use your_crate::get_studio_register_localised; - /// - /// let result = get_studio_register_localised(); - /// - /// match result { - /// Ok(data) => println!("Successfully fetched data."), - /// Err(e) => { - /// println!("Failed with error: {}: {}", e); - /// } - /// } - /// ``` - pub fn get_studio_register_localised() -> Result<RegisterLocalisedStudioList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/studio.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_user_register.rs b/src/structure/register/anilist/struct_user_register.rs deleted file mode 100644 index a61b3ee5..00000000 --- a/src/structure/register/anilist/struct_user_register.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedUser { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedUserList = HashMap<String, RegisterLocalisedUser>; - -impl RegisterLocalisedUser { - /// Reads and deserializes the JSON data from a specific language file containing user registration information. - /// - /// The file path is hard-coded and points to `'./lang_file/command_register/anilist/user.json'`. - /// - /// # Errors - /// This function will return an error if: - /// * The file doesn't exist or cannot be opened due to a permission error or any other reason (error message: `"Failed to open file"`) - /// * The file cannot be read (error message: `"Failed to read file"`) - /// * The JSON content from the file cannot be parsed into the `RegisterLocalisedUserList` struct (error message: `"Failed to parse json."`) - /// - /// # Return - /// The function returns a `Result<RegisterLocalisedUserList, &'static str>`. On success, it provides a `RegisterLocalisedUserList` instance. If any error occurs during file opening, reading, or deserializing, it returns an `Err` variant with a static string describing the error. - /// - /// # Example - /// - /// ```no_run - /// match get_user_register_localised() { - /// Ok(user_list) => println!("Successfully retrieved user list: {:?}", user_list), - /// Err(err) => eprintln!("Error: {}", err), - /// } - /// ``` - /// - pub fn get_user_register_localised() -> Result<RegisterLocalisedUserList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/user.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/anilist/struct_waifu_register.rs b/src/structure/register/anilist/struct_waifu_register.rs deleted file mode 100644 index 3d0f18b7..00000000 --- a/src/structure/register/anilist/struct_waifu_register.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedWaifu { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedWaifuList = HashMap<String, RegisterLocalisedWaifu>; - -impl RegisterLocalisedWaifu { - /// Function name: get_waifu_register_localised - /// - /// # Description - /// This function is responsible for reading the localised Waifu list from the JSON file located at './lang_file/command_register/anilist/waifu.json'. - /// The functions attempts to open the specified file, read its content into a string then attempts to parse the JSON content of string into 'RegisterLocalisedWaifuList' type data. - /// - /// # Errors - /// This function will return an error if the file can't be opened, can't be read, or if the JSON parsing fails. - /// - /// # Returns - /// It returns a Result. If successful, it returns a type of 'RegisterLocalisedWaifuList'. If unsuccessful, it returns a static string indicating the type of error. - /// - /// # Examples - /// ``` - /// # use your_package_name::get_waifu_register_localised; - /// - /// let result = get_waifu_register_localised(); - /// - /// match result { - /// Ok(waifu_list) => println!("Waifu list retrieved successfully"), - /// Err(err) => println!("There was an error: {}", err), - /// } - /// ``` - pub fn get_waifu_register_localised() -> Result<RegisterLocalisedWaifuList, &'static str> { - let mut file = match File::open("./lang_file/command_register/anilist/waifu.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/mod.rs b/src/structure/register/general/mod.rs deleted file mode 100644 index c7733910..00000000 --- a/src/structure/register/general/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod struct_avatar_register; -pub mod struct_banner_register; -pub mod struct_credit_register; -pub mod struct_info_register; -pub mod struct_lang_register; -pub mod struct_modules_register; -pub mod struct_ping_register; -pub mod struct_profile_register; diff --git a/src/structure/register/general/struct_avatar_register.rs b/src/structure/register/general/struct_avatar_register.rs deleted file mode 100644 index 0ff7260d..00000000 --- a/src/structure/register/general/struct_avatar_register.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedAvatar { - pub code: String, - pub name: String, - pub description: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedAvatarList = HashMap<String, RegisterLocalisedAvatar>; - -impl RegisterLocalisedAvatar { - /// Function `get_avatar_register_localised` attempts to read a json configuration file and parse it into a RegisterLocalisedAvatarList. - /// - /// The function tries to open a file "lang_file/command_register/general/avatar.json", read it into a string and then, by using `serde_json` crate, parse the JSON string into RegisterLocalisedAvatarList. - /// - /// # Errors - /// - /// The function can return an error in three possible cases: - /// - If the file cannot be opened. In this case, it returns a static string error "Failed to open file". - /// - If the file cannot be read to a string. Then it returns "Failed to read file". - /// - If the parsed string cannot be converted to RegisterLocalisedAvatarList. In this case it returns "Failed to parse json." - /// - /// # Returns - /// - /// A `Result` type is returned. If successful, it will contain `Ok(RegisterLocalisedAvatarList)` where RegisterLocalisedAvatarList is the parsed JSON data; - /// If it fails at any point, it would contain `Err(&'static str)` that tells the user about the specific error. - /// - /// # Example - /// - /// ``` - /// - /// match get_avatar_register_localised() { - /// Ok(data) => println!("Data: {:?}", data), - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// - /// ``` - /// - pub fn get_avatar_register_localised() -> Result<RegisterLocalisedAvatarList, &'static str> { - let mut file = match File::open("./lang_file/command_register/general/avatar.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/struct_banner_register.rs b/src/structure/register/general/struct_banner_register.rs deleted file mode 100644 index 6fb4b1b8..00000000 --- a/src/structure/register/general/struct_banner_register.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedBanner { - pub code: String, - pub name: String, - pub description: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedBannerList = HashMap<String, RegisterLocalisedBanner>; - -impl RegisterLocalisedBanner { - /// This function reads and parses a JSON file located at "lang_file/command_register/general/banner.json". - /// - /// It uses `File::open()` to open the file and `read_to_string()` to read the contents of the file into a `String`. - /// It uses `serde_json::from_str()` to parse it into a `RegisterLocalisedBannerList` object. - /// - /// # Returns - /// - /// * `Ok(RegisterLocalisedBannerList)`: If the file was opened, read, and parsed successfully into `RegisterLocalisedBannerList`. - /// * `Err(&'static str)`: If there is any failure in opening/reading the file or parsing the JSON, the appropriate error message is returned. - /// - /// # Errors - /// - /// This function will return an error in the following situations, but is not limited to just these cases: - /// - /// * The file could not be opened. - /// * The file could not be read. - /// * The JSON could not be parsed. - /// - /// # Examples - /// - /// ```no_run - /// use crate::your_module::get_banner_register_localised; - /// - /// let result = get_banner_register_localised(); - /// match result { - /// Ok(banner) => { - /// // Do something with `banner` - /// } - /// Err(err) => { - /// eprintln!("Error: {}", err); - /// } - /// } - /// ``` - pub fn get_banner_register_localised() -> Result<RegisterLocalisedBannerList, &'static str> { - let mut file = match File::open("./lang_file/command_register/general/banner.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/struct_credit_register.rs b/src/structure/register/general/struct_credit_register.rs deleted file mode 100644 index 1d0fcc37..00000000 --- a/src/structure/register/general/struct_credit_register.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedCredit { - pub code: String, - pub name: String, - pub desc: String, -} - -type RegisterLocalisedCreditList = HashMap<String, RegisterLocalisedCredit>; - -impl RegisterLocalisedCredit { - /// Opens a file containing credit register information, - /// reads the content, and parses it from JSON into a `RegisterLocalisedCreditList` data structure. - /// - /// # Errors - /// - /// This function will return an error if it encounters these situations: - /// * it fails to open the specified file - /// * it fails to read the file content - /// * it fails to parse the JSON content in the file - /// - /// # Examples - /// - /// ```no_run - /// use your_module_name::get_credit_register_localised; - /// - /// match get_credit_register_localised() { - /// Ok(data) => println!("{:?}", data), - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// ``` - /// - /// # Returns - /// - /// A `Result` with `RegisterLocalisedCreditList` when successful, or a static string describing the error otherwise. - pub fn get_credit_register_localised() -> Result<RegisterLocalisedCreditList, &'static str> { - let mut file = match File::open("./lang_file/command_register/general/credit.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/struct_info_register.rs b/src/structure/register/general/struct_info_register.rs deleted file mode 100644 index 7d39e036..00000000 --- a/src/structure/register/general/struct_info_register.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedInfo { - pub code: String, - pub name: String, - pub desc: String, -} - -type RegisterLocalisedInfoList = HashMap<String, RegisterLocalisedInfo>; - -impl RegisterLocalisedInfo { - /// Retrieves a list of localised languages for profile registration from a file. - /// - /// This function opens a "lang_file/command_register/general/lang.json" file and reads it into a string. - /// It then parses the JSON string into a series of `RegisterLocalisedLangList` representations. - /// - /// # Errors - /// - /// The function will return an `Err` variant of `Result` when: - /// - It fails to open the file (e.g., the file does not exist, or the application does not have sufficient permissions to open it). - /// - It fails to read the file content. - /// - It fails to parse the JSON content into `RegisterLocalisedLangList`. - /// - /// Each error variant returns a static string detailing the cause of the failure. - /// - /// # Returns - /// - /// On success, the function returns a `RegisterLocalisedLangList` object parsed from JSON content. - /// - /// # Example - /// - /// ``` - /// let result = get_profile_register_localised(); - /// match result { - /// Ok(data) => println!("{:?}", data), - /// Err(e) => eprintln!("An error occurred: {}", e), - /// } - /// ``` - /// - /// # Note - /// - /// Depending on the context where you use this function, error handling mechanisms other than matching result and printing error may be more appropriate. - pub fn get_info_register_localised() -> Result<RegisterLocalisedInfoList, &'static str> { - let mut file = match File::open("./lang_file/command_register/general/info.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/struct_lang_register.rs b/src/structure/register/general/struct_lang_register.rs deleted file mode 100644 index d422d9cc..00000000 --- a/src/structure/register/general/struct_lang_register.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct LangRegister { - pub code: String, - pub name: String, - pub description: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedLangList = HashMap<String, LangRegister>; - -impl LangRegister { - /// Retrieves a list of localised languages for profile registration from a file. - /// - /// This function opens a "lang_file/command_register/general/lang.json" file and reads it into a string. - /// It then parses the JSON string into a series of `RegisterLocalisedLangList` representations. - /// - /// # Errors - /// - /// The function will return an `Err` variant of `Result` when: - /// - It fails to open the file (e.g., the file does not exist, or the application does not have sufficient permissions to open it). - /// - It fails to read the file content. - /// - It fails to parse the JSON content into `RegisterLocalisedLangList`. - /// - /// Each error variant returns a static string detailing the cause of the failure. - /// - /// # Returns - /// - /// On success, the function returns a `RegisterLocalisedLangList` object parsed from JSON content. - /// - /// # Example - /// - /// ``` - /// let result = get_profile_register_localised(); - /// match result { - /// Ok(data) => println!("{:?}", data), - /// Err(e) => eprintln!("An error occurred: {}", e), - /// } - /// ``` - /// - /// # Note - /// - /// Depending on the context where you use this function, error handling mechanisms other than matching result and printing error may be more appropriate. - pub fn get_profile_register_localised() -> Result<RegisterLocalisedLangList, &'static str> { - let mut file = match File::open("./lang_file/command_register/general/lang.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/struct_modules_register.rs b/src/structure/register/general/struct_modules_register.rs deleted file mode 100644 index e35bcc22..00000000 --- a/src/structure/register/general/struct_modules_register.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedModule { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, - pub option2: String, - pub option2_desc: String, -} - -type RegisterLocalisedModuleList = HashMap<String, RegisterLocalisedModule>; - -impl RegisterLocalisedModule { - /// `get_module_register_localised` is a function that reads a localisation file - /// and parses it into a `RegisterLocalisedModuleList`. - /// - /// The function attempts to open a JSON file, read the file's contents into a `String`, - /// and then parse that `String` into a `RegisterLocalisedModuleList` using the `serde_json::from_str` function. - /// - /// # Returns - /// - /// Returns a `Result`. If successful, the function returns `Ok(RegisterLocalisedModuleList)`. - /// If it fails at any point (opening, reading, or parsing the file), it will return `Err(&'static str)` containing a relevant error message. - /// - /// # Errors - /// - /// This function will return an error if: - /// - /// * The file fails to open (returns `Err("Failed to open file")`) - /// * The file fails to read (returns `Err("Failed to read file")`) - /// * The JSON fails to parse (returns `Err("Failed to parse json.")`) - /// - /// # Example - /// - /// ``` - /// let result = get_module_register_localised(); - /// match result { - /// Ok(data) => println!("Data: {:?}", data), - /// Err(e) => println!("Error: {:?}", e), - /// } - /// ``` - pub fn get_module_register_localised() -> Result<RegisterLocalisedModuleList, &'static str> { - let mut file = - match File::open("./lang_file/command_register/general/module_activation.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/struct_ping_register.rs b/src/structure/register/general/struct_ping_register.rs deleted file mode 100644 index cbed847b..00000000 --- a/src/structure/register/general/struct_ping_register.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedPing { - pub code: String, - pub name: String, - pub desc: String, -} - -type RegisterLocalisedPingList = HashMap<String, RegisterLocalisedPing>; - -impl RegisterLocalisedPing { - /// `get_ping_register_localised` is a function that attempts to open and parse a JSON file located at "lang_file/command_register/general/ping.json". - /// - /// # Returns - /// - /// This function returns a `Result` with either a successfully parsed data in `RegisterLocalisedPingList` or an error message of type `&'static str` that indicating the kind of error that occurred. - /// Available error messages are: - /// * "Failed to open file": It could not locate or access the file at the specified path. - /// * "Failed to read file": It could open the file, but could not read it. - /// * "Failed to parse json.": It could read the file, but could not parse the JSON. - /// - /// # Errors - /// - /// This function will return an error if it can't open the JSON file, read its contents or parse the JSON string. - /// - /// # Examples - /// - /// Below is a hypothetical usage example of this function: - /// - /// ```rust - /// let ping_list = get_ping_register_localised(); - /// match ping_list { - /// Ok(list) => { - /// println!("{:?}", list); - /// }, - /// Err(message) => { - /// println!("Error: {}", message); - /// } - /// } - /// ``` - pub fn get_ping_register_localised() -> Result<RegisterLocalisedPingList, &'static str> { - let mut file = match File::open("./lang_file/command_register/general/ping.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/structure/register/general/struct_profile_register.rs b/src/structure/register/general/struct_profile_register.rs deleted file mode 100644 index 39785f09..00000000 --- a/src/structure/register/general/struct_profile_register.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RegisterLocalisedProfile { - pub code: String, - pub name: String, - pub desc: String, - pub option1: String, - pub option1_desc: String, -} - -type RegisterLocalisedProfileList = HashMap<String, RegisterLocalisedProfile>; - -impl RegisterLocalisedProfile { - /// # get_profile_register_localised - /// - /// Opens a file named "lang_file/command_register/general/profile.json", reads its contents and deserialises it into a `RegisterLocalisedProfileList`. Returns a `Result` indicating the success or failure of these operations. - /// - /// # Returns - /// - /// If successful, this function returns a `Result::Ok` containing the deserialised `RegisterLocalisedProfileList` from the JSON file. - /// - /// If the function fails at any point, it returns a `Result::Err` with an appropriate message indicating what part of the operation failed: - /// - "Failed to open file" - /// - "Failed to read file" - /// - "Failed to parse json." - /// - /// # Errors - /// - /// This function returns `&'static str` error in form of a `Result::Err` when: - /// - The JSON file could not be opened (for any reason) - /// - The file contents could not be read (for any reason) - /// - The JSON data could not be deserialised into a `RegisterLocalisedProfileList` (for any reason) - /// - /// # Example - /// - /// ```rust - /// let profile = get_profile_register_localised(); - /// match profile { - /// Ok(data) => println!("Data: {:?}", data), - /// Err(err) => println!("Error: {}", err), - /// } - /// ``` - /// - pub fn get_profile_register_localised() -> Result<RegisterLocalisedProfileList, &'static str> { - let mut file = match File::open("./lang_file/command_register/general/profile.json") { - Ok(file) => file, - Err(_) => return Err("Failed to open file"), - }; - let mut json = String::new(); - match file.read_to_string(&mut json) { - Ok(_) => {} - Err(_) => return Err("Failed to read file"), - }; - match serde_json::from_str(&json) { - Ok(data) => Ok(data), - Err(_) => Err("Failed to parse json."), - } - } -} diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 68997bad..00000000 --- a/src/tests.rs +++ /dev/null @@ -1,156 +0,0 @@ -#[cfg(test)] -mod tests { - use std::any::{Any, TypeId}; - - use crate::function::general::get_guild_langage::get_guild_langage; - use crate::function::general::html_parser::{ - add_anti_slash, convert_bold, convert_html_entity_to_real_char, - convert_html_line_break_to_line_break, convert_italic, convert_link_to_discord_markdown, - convert_spoiler, convert_to_discord_markdown, - }; - use crate::function::general::trim::{trim, trim_webhook}; - use crate::function::requests::request::make_request_anilist; - use crate::function::sql::sqlite::pool::get_sqlite_pool; - use serde_json::json; - use sqlx::{Pool, Sqlite}; - - #[test] - fn test_parser_mdash() { - assert_eq!( - convert_html_entity_to_real_char("— test —".to_string()), - "— test —" - ); - } - - #[test] - fn test_parser_italic() { - assert_eq!(convert_italic("<i>test</i>".to_string()), "_test_"); - } - - #[test] - fn test_parser_href() { - assert_eq!( - convert_link_to_discord_markdown( - "<a href=\"https://anilist.co/character/138101/Loid-Forger\">Loid Forger</a>" - .to_string() - ), - "[Loid Forger](https://anilist.co/character/138101/Loid-Forger)" - ); - } - - #[test] - fn test_parser_anti_slash() { - assert_eq!(add_anti_slash("Brother`s".to_string()), "Brother\\`s"); - } - - #[test] - fn test_parser_line_break() { - assert_eq!( - convert_html_line_break_to_line_break("<br> test".to_string()), - "\n test" - ); - } - - #[test] - fn test_parser_bold() { - assert_eq!(convert_bold("<b>test</b>".to_string()), "**test**"); - } - - #[test] - fn test_parser_spoiler1() { - assert_eq!(convert_spoiler("~!test!~".to_string()), "||test||"); - } - - #[test] - fn test_parser_spoiler2() { - assert_eq!( - convert_spoiler("~!test!~\n ~!test!~ ~!test!~".to_string()), - "||test||\n ||test|| ||test||" - ); - } - - #[test] - fn test_parser_complete() { - assert_eq!(convert_to_discord_markdown("~!test!~\n ~!test!~ ~!test!~ <b>test</b> <br> test Brother`s <a href=\"https://anilist.co/character/138101/Loid-Forger\">Loid Forger</a> <i>test</i> — test —" - .to_string()), "||test||\n ||test|| ||test|| **test** \n test Brother\\`s [Loid Forger](https://anilist.co/character/138101/Loid-Forger) _test_ — test —"); - } - - #[test] - fn test_trim_less() { - let desc = "In the serene forest, the rustling leaves and chirping birds created a peaceful melody. The sun gently kissed the earth, painting the sky with hues of orange and pink, as nature embraced its tranquil symphony.".to_string(); - let lenght_diff = 4096 - desc.len() as i32; - let result_len; - if lenght_diff <= 0 { - result_len = trim(desc, lenght_diff).len() - } else { - result_len = desc.len(); - } - assert!(result_len < 4096) - } - - #[test] - fn test_trim_more() { - let desc = "In the serene forest, the rustling leaves and chirping birds created a peaceful melody. The sun gently kissed the earth, painting the sky with hues of orange and pink, as nature embraced its tranquil symphony. The fragrance of wildflowers filled the air, and a gentle breeze caressed the leaves, carrying the whispers of the ancient trees. As the day unfolded, the forest awakened with life. Squirrels darted through the branches, and deer gracefully danced in the clearings. The harmonious chorus of crickets and cicadas added to the symphony of nature, captivating all who listened. Beyond the forest's edge, a meandering river sparkled under the warm rays of the sun. Dragonflies flitted over the water, while fish swam gracefully beneath the surface. The river's gentle flow seemed to echo the rhythm of the forest, blending in perfect harmony. As the evening approached, the forest transformed into a magical realm. Fireflies emerged, their ethereal glow illuminating the darkening woods. The stars appeared one by one, painting the night sky with their celestial beauty. In this enchanted world, time seemed to slow down, and worries faded away. The forest was a sanctuary of tranquility, a place where one could connect with the essence of life itself. It reminded all who wandered through its depths of the profound interconnectedness of all living beings. Under the moon's gentle gaze, the forest exuded an aura of mystery and wonder. Legends whispered in the wind, tales of ancient spirits and mystical creatures. Each rustle of leaves seemed to carry a secret, inviting the curious to explore the unknown. The forest's embrace was a balm for the soul, a source of solace and inspiration. It reminded humanity of its humble place in the grand tapestry of the universe, urging reverence for the natural world. As night turned to dawn, the forest prepared for a new day. The first rays of sunlight gently filtered through the canopy, casting a soft glow on the forest floor. Creatures big and small stirred from their slumber, greeting the dawn with anticipation. And so, the timeless dance of life continued, day after day, season after season. The serene forest remained an eternal witness to the ever-changing cycle of existence, a sanctuary of beauty and wisdom for all who sought its embrace. In the serene forest, the rustling leaves and chirping birds created a peaceful melody. The sun gently kissed the earth, painting the sky with hues of orange and pink, as nature embraced its tranquil symphony. The fragrance of wildflowers filled the air, and a gentle breeze caressed the leaves, carrying the whispers of the ancient trees. As the day unfolded, the forest awakened with life. Squirrels darted through the branches, and deer gracefully danced in the clearings. The harmonious chorus of crickets and cicadas added to the symphony of nature, captivating all who listened. Beyond the forest's edge, a meandering river sparkled under the warm rays of the sun. Dragonflies flitted over the water, while fish swam gracefully beneath the surface. The river's gentle flow seemed to echo the rhythm of the forest, blending in perfect harmony. As the evening approached, the forest transformed into a magical realm. Fireflies emerged, their ethereal glow illuminating the darkening woods. The stars appeared one by one, painting the night sky with their celestial beauty. In this enchanted world, time seemed to slow down, and worries faded away. The forest was a sanctuary of tranquility, a place where one could connect with the essence of life itself. It reminded all who wandered through its depths of the profound interconnectedness of all living beings. Under the moon's gentle gaze, the forest exuded an aura of mystery and wonder. Legends whispered in the wind, tales of ancient spirits and mystical creatures. Each rustle of leaves seemed to carry a secret, inviting the curious to explore the unknown. The forest's embrace was a balm for the soul, a source of solace and inspiration. It reminded humanity of its humble place in the grand tapestry of the universe, urging reverence for the natural world. As night turned to dawn, the forest prepared for a new day. The first rays of sunlight gently filtered through the canopy, casting a soft glow on the forest floor. Creatures big and small stirred from their slumber, greeting the dawn with anticipation. And so, the timeless dance of life continued, day after day, season after season. The serene forest remained an eternal witness to the ever-changing cycle of existence, a sanctuary of beauty and wisdom for all who sought its embrace.".to_string(); - let lenght_diff = 4096 - desc.len() as i32; - let result_len; - if lenght_diff <= 0 { - result_len = trim(desc, lenght_diff).len() - } else { - result_len = desc.len(); - } - assert_eq!(result_len, 4096) - } - - #[tokio::test] - async fn test_guild_langage() { - assert_eq!( - get_guild_langage("1117152661620408531".to_string()).await, - "En".to_string() - ); - } - - #[tokio::test] - async fn test_get_pool() { - let pool = get_sqlite_pool("./cache.db").await; - assert_eq!(TypeId::of::<Pool<Sqlite>>(), pool.type_id()); - } - - #[tokio::test] - async fn test_make_request() { - let query: &str = "query ($search: Int = 5399974) { - User(id: $search){ - id - } - }"; - let json = json!({"query": query,}); - let resp = make_request_anilist(json, true).await; - let good_resp = r#"{"data":{"User":{"id":5399974}}}"#; - assert_eq!(resp, good_resp) - } - - #[test] - fn trim_webhook_more() { - let desc = "In the serene forest, the rustling leaves and chirping birds created a peaceful melody. The sun gently kissed the earth, painting the sky with hues of orange and pink, as nature embraced its tranquil symphony.".to_string(); - let lenght_diff = 50 - desc.len() as i32; - let result_len; - if lenght_diff <= 0 { - result_len = trim_webhook(desc, lenght_diff).len() - } else { - result_len = desc.len(); - } - assert!(result_len <= 50) - } - - #[test] - fn trim_webhook_less() { - let desc = "The cat purred contentedly on my lap.".to_string(); - let lenght_diff = 50 - desc.len() as i32; - let result_len; - if lenght_diff <= 0 { - result_len = trim_webhook(desc, lenght_diff).len() - } else { - result_len = desc.len(); - } - assert!(result_len < 50) - } -}