├── .cargo └── config.toml ├── .dockerignore ├── .env.example ├── .github └── workflows │ ├── binary.yml │ ├── dockerfile.yml │ └── linters.yml ├── .gitignore ├── .python-version ├── .sqlx ├── query-00668fa18ca090b0a5478725b43404d8d3be40005ef4327b52135b4fd51eef44.json ├── query-00785ad758eb025ae58825e7337215ce42a9f1295be1ade608a5dca8e1cba1db.json ├── query-07b363d149ec563f4dbb48155cfbaf6bea5b11f72f773ff088a6cc11ab9e0fba.json ├── query-11fd4f50cfb79809d07429d7f28a2393ff8d181da61abc682895601e968c5655.json ├── query-13b1e5d17c14aac48d3a7d931a658d68e88405b1ac8ea7c761243755a955290b.json ├── query-1650fe04a458b90240649a530f67ce2849b7df9b9da49b85f8d3b53bddfb7da5.json ├── query-1c612dbf7d2c8b0c54bd74178eedaf27161231fd39c77e1166247cfe20f47694.json ├── query-1e97a4efe59b3243df8529421c4c1227f4a232c43c1e478aafd448c250ffa3a2.json ├── query-1edc3c2d8f00fee9397aeeab97cb799269d8b31fc82b27c1c81dd2a14c8f7a9b.json ├── query-22768a2f355a842e9f609a897425324de4c212c1eec688ca5a75708f764635ac.json ├── query-232d3431ed72c399490e35240a043e21089d344c4628eff00d33afad3cd1984b.json ├── query-25599ea197f5c6204f94aad86f77e79a5e81089883bc9af1c618a3db0e0d128b.json ├── query-25845c5698c7cdc6647af1b63e60455459f53981689429062f556cd356af4cd7.json ├── query-26876cce9d0bf60e05178f227423ee522648b2204457ff7a73fec4e87311bfdf.json ├── query-28df5cecf132f47eb3eb18cada4c08d0c44f863c691939fdc19eb0e2f9edd016.json ├── query-2908c451e2f179410b4eb54ae6e9027d311a4a0528705043993f18179599f29c.json ├── query-2a6d0bd9e502f359d5b3b15298e1caf8d378e9a5eb8cbf44e4033192e5251642.json ├── query-2d2cb73ba3afc97d626f14bfe088d7d35359331378d0b93cf58aca1318ee5fb9.json ├── query-2de65310d2d027b37d33d3862ee993c46cd5bff71059298bb8e73b672fe48d77.json ├── query-305ccf668cafb08dbc693909679a3fecea341b96ae8fd79d155d8981d7b38bc0.json ├── query-34313c87878e50cc7949cc3894b66af169e83820e3fa61bb8f3a6a49f9c1fc01.json ├── query-343e3787a742ae6ed8bcb167d2a5ebe8ccd6b29a5d52eca4cb4382391eca39f8.json ├── query-35ae6e890e43bd712a5fcbab383cf0f84b295784587d25301f8d23dbce6a3524.json ├── query-36531a7bbb599e7cb5023020373f126f5d892211e8a28122c24f1f6a5bddfba0.json ├── query-3bbdfb879d54c4721345f459ac595463c856c8506235e89cc90e334682f50913.json ├── query-3eeba2e6b37d8c469dedecded74692bc5933621ebbeff3397f6dab98402209ed.json ├── query-41bc5b9e1b49822c841bf923b635efa85b610aa16a603c7abaf6908e89e1d231.json ├── query-44e187a92daa9453a5465cd057fdeaa7237eb96c13395d7d099b714d85793eff.json ├── query-4960a52935c26aa885840fd942467a65297d10321ae6424781b2ee3ffa4f0875.json ├── query-4b41f197184d8aaa3c397617f4111eb3f77db7015bf9b75693fffd1207e9a00f.json ├── query-4e13af608f227e4690755f01dead21bed002225d12237cf75c3bc6e80906fcbe.json ├── query-5369c8101c779a94d469be24ab5e593f29247d7cc8ad92f39f971d0dc223f875.json ├── query-597e1b1256bc783fb85af110b378fcdd652b9b0314d5d2f3d6a18aa42a6f1592.json ├── query-5b5a0c282654e3770655e69f6aa80d54ad9dc492f19ca81fc779b2a21a3ef5d2.json ├── query-5d29e7b84d7f07dbae3aa1050e29f8118590a1f14c6cff076d89b14fd2e17247.json ├── query-5e3245cf2ff5bbcc8443a0237fcc5a50d558e6c15313fb33595b49e65477570a.json ├── query-5f535bd03724f832cb2c4ad0e5204ef633b789fa8443a5c9d1a9029a4576ac03.json ├── query-6368dd70667549f0c0b0b9bf33cce5a62eadc15ffd0ea4fb5437a995a153deb0.json ├── query-705f7b60be453ee60099070b5fc5788991e34209f8c0e52f05ed562a7eb52766.json ├── query-73b99d629e0672d4a609f37bf4ed07ca8164cc4e3d80b0bbaa35dc73d8002d36.json ├── query-7aa75622d12ee64a87a749e930bfa042f5e7f770d6eb42938fe6ca4397fdc268.json ├── query-7fa4c774189522476a72220b95c4001f89547189eb2aed97067b2688049e8dbf.json ├── query-8079d6b95d5eed89a7ce43e14a25bf9331357013f2f0a9dfbe477916cd22f18d.json ├── query-81c8b76554d6304dea692ac6d7bb95db6b95f381881f7ccdd85d310087e9a46a.json ├── query-82c04e99eefafa6df9e39ae70f4de81f7c0d9d5325cb8baa53f08c477c70acdf.json ├── query-85c6d9a2308e670def0903647e7b2f1f41ab934e742b828e839698aea8dfa891.json ├── query-8ad4efd6021caa4fddc8a622623dd02864162e8ae7d0f2d45600a7d0aadaa1fb.json ├── query-8fdff3b23bf87171d6501c2f7a9a66db50f4f9adeb5f0130b415f08f2e30c310.json ├── query-9218ad57bf0ef416cd2b700e771fe38924e159ecdc833a99cd716c772e31be34.json ├── query-a2abf8a0f150945b1b627443345b17ca90067b1fdc085602e205292f09731457.json ├── query-a55baeff8a9d29d3f4bf93bdce5a8516107f98b21ef7eaf913e1d73cafbad1a1.json ├── query-a61af56279669945475e1bd6c07314ddcc6b8dd48641b187bcf3f92516b680e0.json ├── query-a70c68916ccaa238c333c30cc7aad03bc9dd4d6045e8701b20c0b184ef43d855.json ├── query-a79cb39778f193bc5d0f987f046f32e9c5558510474cced79325769001f0ae68.json ├── query-a87cecbf5594813de9ae09f64bf587c1351b38780227baedb26c86422e2bdaa6.json ├── query-a90b58705c2aa90bd947676b09f5c1bd7cf8de9ac78a57ae007ba1e233e23ff5.json ├── query-ab63ab3a8a49e4c632a3e8c65b352f129d1b8cf94214bc9cb993084615a7c16f.json ├── query-b16a45673e483ada9bfe1a8abbb87d4a340ead03b9eabe78831c57260f4e5c1b.json ├── query-b6c6c3af88d80513ae83e8f6423b54f0eb8b7344a765f5205b2ae35347e0ee00.json ├── query-bf767562a1fcd3e989d5d7800f8feb19959cb78d9d68850bd4d18bf3f5d6855f.json ├── query-bfca9396251c01fbf9510069699b07a287ac149e0598f5056d25b8582481a44d.json ├── query-c582ba49e685230ff575ffeb988d5e400b32202ccdad7f1394ae0a4f534bebd4.json ├── query-c59f56d07fec4f68c5c8640e55d12d91d21841bc52f977418f0ac2ca44f95da4.json ├── query-d18d09060c52e884da8d3118a9506589b1c597eae4e9f1294758cf97513a0655.json ├── query-d61f09ce4f51ba7952643cb0f3a73b0e82ad46fffcbee1c80097bf5d4999ba56.json ├── query-d87f633a372b2d8a6b22cd2f472d6ede1dad106dc17122e9bc1ee4c6aeda1003.json ├── query-d95a944ff53eac4be1375577edabda691086a1475563fbb23c80128f44fc0ede.json ├── query-da69ed66364b8d8e5d0a70a8e789a1bfffa6fe7e7d1380ebe6547f485421a7e0.json ├── query-da83b806c17fe16468b9c70136eaf3ded1d1079761919c7a9f2c9f8b53e72f0d.json ├── query-dfa520877c017cd5808d02c24ef2d71938b68093974f335a4d89df91874fdaa2.json ├── query-e12aad9daf3df4c789502c6b54d770f6f4141273f107981255b36b67ffd131b3.json ├── query-e1758cf7ba1424941e3c07f12c0b28f338781ab348743979c57d8a8e8336a9b7.json ├── query-e20129046ffbefeacec498611c65ecaa944eae3ca76f87b02c93118aa663791b.json ├── query-eb1135121d7f409c041eff3645dec66471e99d4cc0c0e689fe137bad90ee70f0.json ├── query-ebb2c791e92fec2e5b8f0ccbf10b9e0da345e40a2054c74652c4eb7842f88ed5.json ├── query-ed9c186510d2e3d31b1634a1db068e786996dcb6c0dc741dc207df9f73ce4b7a.json ├── query-f1127d19808b04e64dbb74fddccd72146bb25d96661bf4ce473d0be6fddcbd23.json ├── query-f2b76182252d68d938737226b537ddff4930ff59af6dd60bfbb15e11236cdfdb.json ├── query-f2f065836ccd89c512070fad43b04c5e0a842c6cb7ba09dac4439239db761f74.json ├── query-f3edc5bc2652e4dd2e9ea1bf9dcdeb7a98fe957e4898da60413c356dd82e7c29.json ├── query-f4a7c95fdaba6a38ffe1bd3177687ff90e52c8592310914c876d668e8dd30ec1.json ├── query-f89f8a631ebf9bd1c26cfff8d7323ac758f64807e49c9e3179979efb78875c71.json ├── query-fa9bd7bdaa7ecd515b79993101eacfb3c3b0394fc7e6a75c5bec43c239a9ef90.json ├── query-fd57884a830694a5fc149d1ef72983eb444c0c15371e8771e0136ca848afa105.json └── query-fe0b5b46dc49b47641b4c7811fc7bdf7966bad815cbf02d6f3c2e5721445f256.json ├── .weblate ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── config.example.toml ├── deny.toml ├── doc ├── selfhosting-from-source.md ├── selfhosting-podman.md └── selfhosting-prebuilt.md ├── migrations ├── 20220205060135_initial_migration.sql ├── 20220206034602_blocked_entities.sql ├── 20220208005540_convert_to_not_null.sql ├── 20220213083816_more_user_fields.sql ├── 20220323050213_add_user_language.sql ├── 20220324042751_change_user_language_column_type.sql ├── 20220324044030_change_defaults.sql ├── 20220326005718_message_and_audio_storage.sql ├── 20220422014155_convert_userids_to_hashed.sql ├── 20220619210730_default_for_audio_store_ID.sql ├── 20220707194906_remove_author_id_and_message_id_from_message_store.sql ├── 20220707232119_switch_type_of_message_content.sql ├── 20220707232647_not_null_message_content.sql ├── 20220708045546_save_nonce_with_message_content.sql ├── 20220801052859_alter_premium_layout.sql ├── 20220801061346_add_is_trialing_to_user.sql ├── 20220801063251_add_timezone_to_timestamps_because_sqlx_doesn't_support_without.sql ├── 20220801222122_add_timezone_to_timestamps_pt_2.sql ├── 20220824213251_remove_channels_table.sql ├── 20221122161618_automod_tables.sql ├── 20221124000934_add_unique_constraint_for_automod.sql ├── 20221127221641_fix_table_types.sql ├── 20230607172816_voice_message_enable.sql ├── 20230814023409_remove_target_channel_and_add_agreed_tos.sql ├── 20231014010314_guild_all_audio_file_transcription.sql ├── 20231014175034_guild_all_video_file_transcription.sql ├── 20231129024221_guild_auto_detect_lang.sql ├── 20231202032922_guild_transcript_only_role_members.sql ├── 20231211021825_translate_to_english.sql ├── 20231211023309_translate_to_english_enabled_by_default.sql ├── 20231216214434_vote_reminder_table.sql ├── 20231217034034_vote_reminder_opt_out.sql ├── 20231229163655_vote_reminder_enabled.sql ├── 20240115205731_kiai_integration_enabled.sql ├── 20240415174617_remove_tos_agree.sql ├── 20240920165244_default_translate_to_false.sql ├── 20250119180948_add_default_command_parameter_options.sql ├── 20250222001334_move_auto_join_to_config.sql ├── 20250302041748_fix_fkey_relations.sql ├── 20250302043001_move_prefixes_into_guilds_table.sql ├── 20250525065734_per_voice_channel_target_channel.sql └── 20250526220035_per_voice_channel_auto_join_settings.sql ├── prometheus.yml ├── rust-toolchain.toml ├── rustfmt.toml ├── scripty_audio_handler ├── Cargo.toml └── src │ ├── audio_handler.rs │ ├── connect.rs │ ├── consts.rs │ ├── disconnect.rs │ ├── events │ ├── client_disconnect.rs │ ├── driver_connect.rs │ ├── driver_disconnect.rs │ ├── mod.rs │ ├── rtp_packet.rs │ ├── speaking_state_update.rs │ └── voice_tick.rs │ ├── lib.rs │ └── types.rs ├── scripty_automod ├── Cargo.toml └── src │ ├── db.rs │ ├── lib.rs │ ├── types.rs │ └── utils.rs ├── scripty_bot ├── Cargo.toml └── src │ ├── framework_opts.rs │ └── lib.rs ├── scripty_bot_utils ├── Cargo.toml └── src │ ├── available_language_autocomplete.rs │ ├── background_tasks │ ├── core.rs │ ├── mod.rs │ └── tasks │ │ ├── basic_stats_update.rs │ │ ├── bot_list_poster.rs │ │ ├── bot_vote_reminder.rs │ │ ├── cmd_latency_clear.rs │ │ ├── mod.rs │ │ ├── prometheus_latency_update.rs │ │ └── status_update.rs │ ├── dm_support.rs │ ├── entity_block.rs │ ├── error │ ├── error_type.rs │ ├── handler.rs │ ├── message.rs │ └── mod.rs │ ├── extern_utils.rs │ ├── file_transcripts │ ├── consts.rs │ ├── handler.rs │ ├── message_updater.rs │ ├── mod.rs │ ├── raw_pcm_conversions.rs │ └── state.rs │ ├── globals.rs │ ├── handler │ ├── mod.rs │ ├── normal │ │ ├── cache.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ ├── ready.rs │ │ ├── resume.rs │ │ ├── shards.rs │ │ └── voice_state_update.rs │ ├── post_command.rs │ ├── pre_command.rs │ ├── ratelimit.rs │ └── raw │ │ └── mod.rs │ ├── lib.rs │ ├── prefix_handling.rs │ └── types │ ├── ctx.rs │ ├── invocation_data.rs │ ├── lang.rs │ └── mod.rs ├── scripty_botlists ├── Cargo.toml └── src │ ├── common.rs │ ├── lib.rs │ ├── lists │ ├── botlist_me │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── discord_bots_gg │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── discordbotlist_com │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── discordextremelist_xyz │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── discords_com │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── discordservices_net │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── disforge_com │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── infinitybots_gg │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── mod.rs │ ├── top_gg │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ ├── voidbots_net │ │ ├── mod.rs │ │ ├── models.rs │ │ └── trait_impl.rs │ └── wumpus_store │ │ ├── mod.rs │ │ └── models.rs │ └── post_stats.http ├── scripty_build_checks ├── Cargo.toml └── src │ └── lib.rs ├── scripty_commands ├── Cargo.toml ├── src │ ├── cmds │ │ ├── admin │ │ │ ├── cache_info.rs │ │ │ ├── guild_check.rs │ │ │ ├── hash_user_id.rs │ │ │ ├── mod.rs │ │ │ └── shutdown.rs │ │ ├── automod │ │ │ ├── add_rule.rs │ │ │ ├── list_rules.rs │ │ │ ├── mod.rs │ │ │ ├── remove_rule.rs │ │ │ └── setup.rs │ │ ├── config │ │ │ ├── auto_detect_lang.rs │ │ │ ├── auto_join.rs │ │ │ ├── default_settings │ │ │ │ ├── ephemeral.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── new_thread.rs │ │ │ │ ├── record_transcriptions.rs │ │ │ │ └── target_channel.rs │ │ │ ├── kiai_enabled.rs │ │ │ ├── language.rs │ │ │ ├── mod.rs │ │ │ ├── prefix.rs │ │ │ ├── transcribe_audio.rs │ │ │ ├── transcribe_only_role.rs │ │ │ ├── transcribe_video.rs │ │ │ ├── transcribe_voice_messages.rs │ │ │ ├── translate.rs │ │ │ └── verbose.rs │ │ ├── data_storage.rs │ │ ├── debug.rs │ │ ├── dm_support.rs │ │ ├── entity_block.rs │ │ ├── help.rs │ │ ├── join.rs │ │ ├── language.rs │ │ ├── leave.rs │ │ ├── mod.rs │ │ ├── ping.rs │ │ ├── premium │ │ │ ├── claim.rs │ │ │ ├── info.rs │ │ │ ├── mod.rs │ │ │ └── remove.rs │ │ ├── register_cmds.rs │ │ ├── throw_error.rs │ │ ├── transcribe_message.rs │ │ └── vote_reminders.rs │ ├── i18n.rs │ └── lib.rs └── target │ └── sqlx │ └── scripty_commands │ └── query-d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.json ├── scripty_config ├── Cargo.toml └── src │ ├── cfg.rs │ ├── lib.rs │ └── load.rs ├── scripty_core ├── Cargo.toml └── src │ └── main.rs ├── scripty_data_storage ├── Cargo.toml └── src │ ├── cache │ ├── mod.rs │ ├── text.rs │ └── voice.rs │ ├── crypto.rs │ ├── ingest │ ├── mod.rs │ ├── text.rs │ └── voice.rs │ ├── lib.rs │ └── main.rs ├── scripty_data_type ├── Cargo.toml └── src │ ├── call_death_struct.rs │ └── lib.rs ├── scripty_db ├── Cargo.toml └── src │ ├── init.rs │ ├── lib.rs │ └── store.rs ├── scripty_error ├── Cargo.toml └── src │ ├── ffprobe.rs │ ├── file_transcript.rs │ ├── global.rs │ ├── internal.rs │ ├── lib.rs │ └── model.rs ├── scripty_i18n ├── Cargo.toml ├── locales │ ├── af.ftl │ ├── ar.ftl │ ├── be.ftl │ ├── bg.ftl │ ├── ca.ftl │ ├── codes.json │ ├── cs.ftl │ ├── cy.ftl │ ├── da.ftl │ ├── de.ftl │ ├── el.ftl │ ├── en.ftl │ ├── en@uwu.ftl │ ├── en_devel.ftl │ ├── es.ftl │ ├── et.ftl │ ├── fa.ftl │ ├── fi.ftl │ ├── fr.ftl │ ├── gl.ftl │ ├── he.ftl │ ├── hi.ftl │ ├── hr.ftl │ ├── hu.ftl │ ├── hy.ftl │ ├── id.ftl │ ├── is.ftl │ ├── it.ftl │ ├── ja.ftl │ ├── kk.ftl │ ├── kn.ftl │ ├── ko.ftl │ ├── la.ftl │ ├── lt.ftl │ ├── lv.ftl │ ├── mi.ftl │ ├── mk.ftl │ ├── mr.ftl │ ├── ms.ftl │ ├── nb_NO.ftl │ ├── ne.ftl │ ├── nl.ftl │ ├── pl.ftl │ ├── pt.ftl │ ├── pt_BR.ftl │ ├── ro.ftl │ ├── ru.ftl │ ├── sk.ftl │ ├── sl.ftl │ ├── sr.ftl │ ├── sv.ftl │ ├── sw.ftl │ ├── ta.ftl │ ├── th.ftl │ ├── tl.ftl │ ├── tr.ftl │ ├── uk.ftl │ ├── ur.ftl │ ├── vi.ftl │ ├── zh_Hans.ftl │ └── zh_Hant.ftl └── src │ ├── bundles.rs │ ├── cache.rs │ ├── init.rs │ ├── lib.rs │ ├── main.rs │ ├── pretty.rs │ ├── store.rs │ └── strings.rs ├── scripty_integrations ├── Cargo.toml └── src │ ├── kiai │ ├── api_models.rs │ ├── error.rs │ ├── http_client.rs │ └── mod.rs │ └── lib.rs ├── scripty_metrics ├── Cargo.toml └── src │ ├── cmd_handler.rs │ ├── cmd_latency.rs │ ├── get_metrics.rs │ ├── lib.rs │ ├── metrics.rs │ └── rt_metrics.rs ├── scripty_premium ├── Cargo.toml └── src │ └── lib.rs ├── scripty_redis ├── Cargo.toml └── src │ ├── init.rs │ ├── lib.rs │ └── transaction.rs ├── scripty_stt ├── Cargo.toml └── src │ ├── decode_ogg_opus.rs │ ├── ffprobe.rs │ ├── init.rs │ ├── lib.rs │ ├── load_balancer.rs │ ├── models.rs │ └── process_audio.rs ├── scripty_tts ├── Cargo.toml └── src │ ├── engine_trait.rs │ ├── lib.rs │ └── tts_engines │ ├── espeak_ng.rs │ └── mod.rs ├── scripty_utils ├── Cargo.toml └── src │ ├── embed_pagination.rs │ ├── hash_user_id.rs │ ├── latency │ ├── db.rs │ ├── http.rs │ ├── mod.rs │ └── ws.rs │ ├── lib.rs │ └── separate_num.rs ├── scripty_webserver ├── Cargo.toml └── src │ ├── auth.rs │ ├── endpoints │ ├── bot_stats │ │ ├── advanced.rs │ │ └── mod.rs │ ├── languages.rs │ ├── metrics.rs │ ├── mod.rs │ ├── premium │ │ └── mod.rs │ └── webhooks │ │ ├── discordservices_net.rs │ │ ├── mod.rs │ │ ├── top_gg.rs │ │ └── wumpus_store.rs │ ├── entrypoint.rs │ ├── errors.rs │ ├── lib.rs │ └── models.rs └── speech_commands.md /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | linker = "clang" 3 | rustflags = ["-Ctarget-cpu=native", "--cfg", "tokio_unstable", "-C", "link-arg=-fuse-ld=mold", "-Zshare-generics=y"] 4 | 5 | [build] 6 | rustdocflags = ["--cfg", "tokio_unstable"] 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /.github 2 | /target 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # format postgresql://username[:password]@host/db_name 2 | DATABASE_URL=postgresql://scripty:scripty@localhost/scripty 3 | -------------------------------------------------------------------------------- /.github/workflows/binary.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths-ignore: 4 | - "**.md" 5 | - "**.ftl" 6 | branches: 7 | - master 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang 12 | RUSTFLAGS: "--cfg tokio_unstable -Clink-arg=-fuse-ld=mold -Zshare-generics=y" 13 | 14 | name: Build executable 15 | 16 | jobs: 17 | build: 18 | name: Build binary 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: rui314/setup-mold@v1 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | target: x86_64-unknown-linux-gnu 26 | toolchain: nightly 27 | 28 | - name: Build 29 | run: cargo build --release --target x86_64-unknown-linux-gnu 30 | 31 | - name: Upload artifact 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: scripty_v2-x86_64-unknown-linux-gnu 35 | path: target/x86_64-unknown-linux-gnu/release/scripty_v2 36 | -------------------------------------------------------------------------------- /.github/workflows/dockerfile.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths-ignore: 4 | - "**.md" 5 | - "**.ftl" 6 | branches: 7 | - master 8 | 9 | env: 10 | REGISTRY: ghcr.io 11 | IMAGE_NAME: ${{ github.repository }} 12 | 13 | name: Build Docker image 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-22.04 18 | 19 | permissions: 20 | contents: read 21 | packages: write 22 | attestations: write 23 | id-token: write 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Log in to the Container registry 29 | uses: docker/login-action@v3.4.0 30 | with: 31 | registry: ${{ env.REGISTRY }} 32 | username: ${{ github.actor }} 33 | password: ${{ secrets.GITHUB_TOKEN }} 34 | 35 | - name: Extract metadata (tags, labels) for Docker 36 | id: meta 37 | uses: docker/metadata-action@v5.7.0 38 | with: 39 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 40 | 41 | - name: Build and push Docker image 42 | id: push 43 | uses: docker/build-push-action@v6.15.0 44 | with: 45 | context: . 46 | push: true 47 | tags: ${{ steps.meta.outputs.tags }} 48 | labels: ${{ steps.meta.outputs.labels }} 49 | 50 | - name: Generate artifact attestation 51 | uses: actions/attest-build-provenance@v2.2.3 52 | with: 53 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} 54 | subject-digest: ${{ steps.push.outputs.digest }} 55 | push-to-registry: true 56 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths-ignore: 4 | - "**.md" 5 | - "**.ftl" 6 | branches: 7 | - master 8 | 9 | name: Lint code 10 | env: 11 | RUSTFLAGS: "--cfg tokio_unstable" 12 | 13 | jobs: 14 | fmt: 15 | name: Rustfmt 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: actions-rust-lang/setup-rust-toolchain@v1 21 | with: 22 | components: rustfmt 23 | toolchain: nightly 24 | override: true 25 | 26 | - uses: actions-rs/cargo@v1 27 | with: 28 | command: fmt 29 | args: -- --check 30 | 31 | clippy: 32 | name: Clippy 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: rui314/setup-mold@v1 37 | - uses: actions-rust-lang/setup-rust-toolchain@v1 38 | with: 39 | toolchain: nightly 40 | components: clippy 41 | override: true 42 | 43 | - run: cargo clippy 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | /.env 3 | /.idea 4 | /.vscode 5 | /config.toml 6 | /config.local.toml 7 | /config.dev.toml 8 | /output.log 9 | # not ignored by **/target for some reason 10 | scripty_commands/target/* 11 | fetch-voice-message.http 12 | scripty_botlists/src/http-client.private.env.json -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.9.17 2 | -------------------------------------------------------------------------------- /.sqlx/query-00668fa18ca090b0a5478725b43404d8d3be40005ef4327b52135b4fd51eef44.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT store_audio FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "store_audio", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "00668fa18ca090b0a5478725b43404d8d3be40005ef4327b52135b4fd51eef44" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-00785ad758eb025ae58825e7337215ce42a9f1295be1ade608a5dca8e1cba1db.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO blocked_users (user_id, reason, blocked_since) VALUES ($1, $2, localtimestamp)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Text" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "00785ad758eb025ae58825e7337215ce42a9f1295be1ade608a5dca8e1cba1db" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-07b363d149ec563f4dbb48155cfbaf6bea5b11f72f773ff088a6cc11ab9e0fba.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE users SET store_msgs = NOT store_msgs WHERE user_id = $1 RETURNING store_msgs", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "store_msgs", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "07b363d149ec563f4dbb48155cfbaf6bea5b11f72f773ff088a6cc11ab9e0fba" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-11fd4f50cfb79809d07429d7f28a2393ff8d181da61abc682895601e968c5655.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT * FROM automod_rules WHERE source_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "item_id", 9 | "type_info": "Int4" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "source_id", 14 | "type_info": "Int4" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "rule_type", 19 | "type_info": "Int2" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "rule_data", 24 | "type_info": "Text" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "rule_action", 29 | "type_info": "Int2" 30 | } 31 | ], 32 | "parameters": { 33 | "Left": [ 34 | "Int4" 35 | ] 36 | }, 37 | "nullable": [ 38 | false, 39 | false, 40 | false, 41 | false, 42 | false 43 | ] 44 | }, 45 | "hash": "11fd4f50cfb79809d07429d7f28a2393ff8d181da61abc682895601e968c5655" 46 | } 47 | -------------------------------------------------------------------------------- /.sqlx/query-13b1e5d17c14aac48d3a7d931a658d68e88405b1ac8ea7c761243755a955290b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "DELETE FROM automod_rules WHERE source_id = $1 AND item_id = $2 RETURNING item_id", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "item_id", 9 | "type_info": "Int4" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int4", 15 | "Int4" 16 | ] 17 | }, 18 | "nullable": [ 19 | false 20 | ] 21 | }, 22 | "hash": "13b1e5d17c14aac48d3a7d931a658d68e88405b1ac8ea7c761243755a955290b" 23 | } 24 | -------------------------------------------------------------------------------- /.sqlx/query-1650fe04a458b90240649a530f67ce2849b7df9b9da49b85f8d3b53bddfb7da5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO automod_config \n (guild_id, enabled, log_channel_id, log_recording)\n VALUES \n ($1, true, $2, $3)\n\t\tON CONFLICT (guild_id) DO UPDATE SET\n\t\t\tlog_channel_id = $2,\n\t\t\tlog_recording = $3\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Int8", 10 | "Bool" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "1650fe04a458b90240649a530f67ce2849b7df9b9da49b85f8d3b53bddfb7da5" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-1c612dbf7d2c8b0c54bd74178eedaf27161231fd39c77e1166247cfe20f47694.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, transcribe_audio_files) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET transcribe_audio_files = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "1c612dbf7d2c8b0c54bd74178eedaf27161231fd39c77e1166247cfe20f47694" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-1e97a4efe59b3243df8529421c4c1227f4a232c43c1e478aafd448c250ffa3a2.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users\n (user_id, premium_level, premium_expiry)\nVALUES\n ($1, 1, now() + INTERVAL '1 day')\nON CONFLICT\n ON CONSTRAINT users_pkey\n DO UPDATE\n SET\n premium_level = 1,\n premium_expiry = now() + INTERVAL '1 day'\n", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "1e97a4efe59b3243df8529421c4c1227f4a232c43c1e478aafd448c250ffa3a2" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-1edc3c2d8f00fee9397aeeab97cb799269d8b31fc82b27c1c81dd2a14c8f7a9b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT target_channel AS \"target_channel!\"\n\t\t\t\t\tFROM default_join_settings\n\t\t\t\t\tWHERE guild_id = $1 AND target_channel IS NOT NULL", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "target_channel!", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | true 19 | ] 20 | }, 21 | "hash": "1edc3c2d8f00fee9397aeeab97cb799269d8b31fc82b27c1c81dd2a14c8f7a9b" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-22768a2f355a842e9f609a897425324de4c212c1eec688ca5a75708f764635ac.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT auto_join_enabled, target_channel\n\t\t\tFROM per_voice_channel_settings\n\t\t\tWHERE channel_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "auto_join_enabled", 9 | "type_info": "Bool" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "target_channel", 14 | "type_info": "Int8" 15 | } 16 | ], 17 | "parameters": { 18 | "Left": [ 19 | "Int8" 20 | ] 21 | }, 22 | "nullable": [ 23 | false, 24 | true 25 | ] 26 | }, 27 | "hash": "22768a2f355a842e9f609a897425324de4c212c1eec688ca5a75708f764635ac" 28 | } 29 | -------------------------------------------------------------------------------- /.sqlx/query-232d3431ed72c399490e35240a043e21089d344c4628eff00d33afad3cd1984b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users\n (user_id, premium_level, premium_expiry, is_trialing)\nVALUES\n ($1, $2, $3, false)\nON CONFLICT\n ON CONSTRAINT users_pkey\n DO UPDATE\n SET\n premium_level = $2,\n premium_expiry = $3,\n is_trialing = false\n", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Int2", 10 | "Timestamptz" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "232d3431ed72c399490e35240a043e21089d344c4628eff00d33afad3cd1984b" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-25599ea197f5c6204f94aad86f77e79a5e81089883bc9af1c618a3db0e0d128b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, transcribe_video_files) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET transcribe_video_files = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "25599ea197f5c6204f94aad86f77e79a5e81089883bc9af1c618a3db0e0d128b" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-25845c5698c7cdc6647af1b63e60455459f53981689429062f556cd356af4cd7.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT reason FROM blocked_guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "reason", 9 | "type_info": "Text" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | true 19 | ] 20 | }, 21 | "hash": "25845c5698c7cdc6647af1b63e60455459f53981689429062f556cd356af4cd7" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-26876cce9d0bf60e05178f227423ee522648b2204457ff7a73fec4e87311bfdf.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT transcribe_audio_files, transcribe_video_files, transcribe_voice_messages FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "transcribe_audio_files", 9 | "type_info": "Bool" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "transcribe_video_files", 14 | "type_info": "Bool" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "transcribe_voice_messages", 19 | "type_info": "Bool" 20 | } 21 | ], 22 | "parameters": { 23 | "Left": [ 24 | "Int8" 25 | ] 26 | }, 27 | "nullable": [ 28 | false, 29 | false, 30 | false 31 | ] 32 | }, 33 | "hash": "26876cce9d0bf60e05178f227423ee522648b2204457ff7a73fec4e87311bfdf" 34 | } 35 | -------------------------------------------------------------------------------- /.sqlx/query-28df5cecf132f47eb3eb18cada4c08d0c44f863c691939fdc19eb0e2f9edd016.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE users SET store_msgs = $1 WHERE user_id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bool", 9 | "Bytea" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "28df5cecf132f47eb3eb18cada4c08d0c44f863c691939fdc19eb0e2f9edd016" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-2908c451e2f179410b4eb54ae6e9027d311a4a0528705043993f18179599f29c.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO message_store (message_content, nonce) VALUES ($1, $2)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Bytea" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "2908c451e2f179410b4eb54ae6e9027d311a4a0528705043993f18179599f29c" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-2a6d0bd9e502f359d5b3b15298e1caf8d378e9a5eb8cbf44e4033192e5251642.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE guilds SET premium_owner_id = nullif(premium_owner_id, $2) WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bytea" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "2a6d0bd9e502f359d5b3b15298e1caf8d378e9a5eb8cbf44e4033192e5251642" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-2d2cb73ba3afc97d626f14bfe088d7d35359331378d0b93cf58aca1318ee5fb9.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT * FROM automod_config WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "item_id", 9 | "type_info": "Int4" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "guild_id", 14 | "type_info": "Int8" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "enabled", 19 | "type_info": "Bool" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "log_channel_id", 24 | "type_info": "Int8" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "log_recording", 29 | "type_info": "Bool" 30 | } 31 | ], 32 | "parameters": { 33 | "Left": [ 34 | "Int8" 35 | ] 36 | }, 37 | "nullable": [ 38 | false, 39 | false, 40 | false, 41 | false, 42 | false 43 | ] 44 | }, 45 | "hash": "2d2cb73ba3afc97d626f14bfe088d7d35359331378d0b93cf58aca1318ee5fb9" 46 | } 47 | -------------------------------------------------------------------------------- /.sqlx/query-2de65310d2d027b37d33d3862ee993c46cd5bff71059298bb8e73b672fe48d77.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT trial_used, premium_owner_id IS NULL AS \"premium_owner_id_is_null!\" FROM \n\t\t\t guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "trial_used", 9 | "type_info": "Bool" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "premium_owner_id_is_null!", 14 | "type_info": "Bool" 15 | } 16 | ], 17 | "parameters": { 18 | "Left": [ 19 | "Int8" 20 | ] 21 | }, 22 | "nullable": [ 23 | false, 24 | null 25 | ] 26 | }, 27 | "hash": "2de65310d2d027b37d33d3862ee993c46cd5bff71059298bb8e73b672fe48d77" 28 | } 29 | -------------------------------------------------------------------------------- /.sqlx/query-305ccf668cafb08dbc693909679a3fecea341b96ae8fd79d155d8981d7b38bc0.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT target_channel FROM default_join_settings WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "target_channel", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | true 19 | ] 20 | }, 21 | "hash": "305ccf668cafb08dbc693909679a3fecea341b96ae8fd79d155d8981d7b38bc0" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-34313c87878e50cc7949cc3894b66af169e83820e3fa61bb8f3a6a49f9c1fc01.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO default_join_settings (guild_id, record_transcriptions)\n\t\t\tVALUES ($1, $2)\n\t\t\tON CONFLICT ON CONSTRAINT default_join_settings_pkey\n\t\t\t DO UPDATE SET record_transcriptions = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "34313c87878e50cc7949cc3894b66af169e83820e3fa61bb8f3a6a49f9c1fc01" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-343e3787a742ae6ed8bcb167d2a5ebe8ccd6b29a5d52eca4cb4382391eca39f8.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO vote_reminders (user_id, site_id, next_reminder)\n VALUES ($1, 1, NOW() + INTERVAL '12 hours')\n ON CONFLICT (user_id, site_id)\n DO UPDATE SET next_reminder = NOW() + INTERVAL '12 hours'", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "343e3787a742ae6ed8bcb167d2a5ebe8ccd6b29a5d52eca4cb4382391eca39f8" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-35ae6e890e43bd712a5fcbab383cf0f84b295784587d25301f8d23dbce6a3524.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "DELETE FROM vote_reminders WHERE next_reminder < NOW() RETURNING user_id, site_id, next_reminder", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "user_id", 9 | "type_info": "Int8" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "site_id", 14 | "type_info": "Int2" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "next_reminder", 19 | "type_info": "Timestamp" 20 | } 21 | ], 22 | "parameters": { 23 | "Left": [] 24 | }, 25 | "nullable": [ 26 | false, 27 | false, 28 | false 29 | ] 30 | }, 31 | "hash": "35ae6e890e43bd712a5fcbab383cf0f84b295784587d25301f8d23dbce6a3524" 32 | } 33 | -------------------------------------------------------------------------------- /.sqlx/query-36531a7bbb599e7cb5023020373f126f5d892211e8a28122c24f1f6a5bddfba0.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, auto_detect_lang) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET auto_detect_lang = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "36531a7bbb599e7cb5023020373f126f5d892211e8a28122c24f1f6a5bddfba0" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-3bbdfb879d54c4721345f459ac595463c856c8506235e89cc90e334682f50913.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO users (user_id, language) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE SET language = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Text" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "3bbdfb879d54c4721345f459ac595463c856c8506235e89cc90e334682f50913" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-3eeba2e6b37d8c469dedecded74692bc5933621ebbeff3397f6dab98402209ed.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT store_msgs FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "store_msgs", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "3eeba2e6b37d8c469dedecded74692bc5933621ebbeff3397f6dab98402209ed" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-41bc5b9e1b49822c841bf923b635efa85b610aa16a603c7abaf6908e89e1d231.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT reason FROM blocked_users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "reason", 9 | "type_info": "Text" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | true 19 | ] 20 | }, 21 | "hash": "41bc5b9e1b49822c841bf923b635efa85b610aa16a603c7abaf6908e89e1d231" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-44e187a92daa9453a5465cd057fdeaa7237eb96c13395d7d099b714d85793eff.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT translate FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "translate", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "44e187a92daa9453a5465cd057fdeaa7237eb96c13395d7d099b714d85793eff" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-4960a52935c26aa885840fd942467a65297d10321ae6424781b2ee3ffa4f0875.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT trial_used FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "trial_used", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "4960a52935c26aa885840fd942467a65297d10321ae6424781b2ee3ffa4f0875" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-4b41f197184d8aaa3c397617f4111eb3f77db7015bf9b75693fffd1207e9a00f.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT language FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "language", 9 | "type_info": "Text" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "4b41f197184d8aaa3c397617f4111eb3f77db7015bf9b75693fffd1207e9a00f" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-4e13af608f227e4690755f01dead21bed002225d12237cf75c3bc6e80906fcbe.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT coalesce(premium_level != 0, false) AS \"has_premium!\" FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "has_premium!", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | null 19 | ] 20 | }, 21 | "hash": "4e13af608f227e4690755f01dead21bed002225d12237cf75c3bc6e80906fcbe" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-5369c8101c779a94d469be24ab5e593f29247d7cc8ad92f39f971d0dc223f875.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT prefix FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "prefix", 9 | "type_info": "Varchar" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | true 19 | ] 20 | }, 21 | "hash": "5369c8101c779a94d469be24ab5e593f29247d7cc8ad92f39f971d0dc223f875" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-597e1b1256bc783fb85af110b378fcdd652b9b0314d5d2f3d6a18aa42a6f1592.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO vote_reminders (user_id, site_id, next_reminder)\n VALUES ($1, 2, NOW() + INTERVAL '20 hours')\n ON CONFLICT (user_id, site_id)\n DO UPDATE SET next_reminder = NOW() + INTERVAL '20 hours'", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "597e1b1256bc783fb85af110b378fcdd652b9b0314d5d2f3d6a18aa42a6f1592" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-5b5a0c282654e3770655e69f6aa80d54ad9dc492f19ca81fc779b2a21a3ef5d2.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds\n \t\t(guild_id)\n\t\t\tVALUES ($1)\n\t\t\tON CONFLICT\n\t\t\t ON CONSTRAINT guilds_pkey\n\t\t\t DO NOTHING", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "5b5a0c282654e3770655e69f6aa80d54ad9dc492f19ca81fc779b2a21a3ef5d2" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-5d29e7b84d7f07dbae3aa1050e29f8118590a1f14c6cff076d89b14fd2e17247.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT COUNT(*) AS \"count!\" FROM automod_rules WHERE source_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "count!", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int4" 15 | ] 16 | }, 17 | "nullable": [ 18 | null 19 | ] 20 | }, 21 | "hash": "5d29e7b84d7f07dbae3aa1050e29f8118590a1f14c6cff076d89b14fd2e17247" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-5e3245cf2ff5bbcc8443a0237fcc5a50d558e6c15313fb33595b49e65477570a.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO blocked_guilds (guild_id, reason, blocked_since) VALUES ($1, $2, $3)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Text", 10 | "Timestamptz" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "5e3245cf2ff5bbcc8443a0237fcc5a50d558e6c15313fb33595b49e65477570a" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-5f535bd03724f832cb2c4ad0e5204ef633b789fa8443a5c9d1a9029a4576ac03.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE users SET premium_level = 0, premium_expiry = NULL WHERE user_id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "5f535bd03724f832cb2c4ad0e5204ef633b789fa8443a5c9d1a9029a4576ac03" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-6368dd70667549f0c0b0b9bf33cce5a62eadc15ffd0ea4fb5437a995a153deb0.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT be_verbose FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "be_verbose", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "6368dd70667549f0c0b0b9bf33cce5a62eadc15ffd0ea4fb5437a995a153deb0" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-705f7b60be453ee60099070b5fc5788991e34209f8c0e52f05ed562a7eb52766.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT count(*) AS \"guild_count!\" FROM guilds WHERE premium_owner_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "guild_count!", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | null 19 | ] 20 | }, 21 | "hash": "705f7b60be453ee60099070b5fc5788991e34209f8c0e52f05ed562a7eb52766" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-73b99d629e0672d4a609f37bf4ed07ca8164cc4e3d80b0bbaa35dc73d8002d36.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id) VALUES ($1)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "73b99d629e0672d4a609f37bf4ed07ca8164cc4e3d80b0bbaa35dc73d8002d36" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-7aa75622d12ee64a87a749e930bfa042f5e7f770d6eb42938fe6ca4397fdc268.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE users SET store_audio = $1 WHERE user_id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bool", 9 | "Bytea" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "7aa75622d12ee64a87a749e930bfa042f5e7f770d6eb42938fe6ca4397fdc268" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-7fa4c774189522476a72220b95c4001f89547189eb2aed97067b2688049e8dbf.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE users SET store_audio = NOT store_audio WHERE user_id = $1 RETURNING store_audio", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "store_audio", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "7fa4c774189522476a72220b95c4001f89547189eb2aed97067b2688049e8dbf" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-8079d6b95d5eed89a7ce43e14a25bf9331357013f2f0a9dfbe477916cd22f18d.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "DELETE FROM vote_reminders WHERE user_id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "8079d6b95d5eed89a7ce43e14a25bf9331357013f2f0a9dfbe477916cd22f18d" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-81c8b76554d6304dea692ac6d7bb95db6b95f381881f7ccdd85d310087e9a46a.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT language FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "language", 9 | "type_info": "Text" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "81c8b76554d6304dea692ac6d7bb95db6b95f381881f7ccdd85d310087e9a46a" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-82c04e99eefafa6df9e39ae70f4de81f7c0d9d5325cb8baa53f08c477c70acdf.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, be_verbose) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET be_verbose = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "82c04e99eefafa6df9e39ae70f4de81f7c0d9d5325cb8baa53f08c477c70acdf" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-85c6d9a2308e670def0903647e7b2f1f41ab934e742b828e839698aea8dfa891.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT premium_level, premium_expiry, is_trialing FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "premium_level", 9 | "type_info": "Int2" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "premium_expiry", 14 | "type_info": "Timestamptz" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "is_trialing", 19 | "type_info": "Bool" 20 | } 21 | ], 22 | "parameters": { 23 | "Left": [ 24 | "Bytea" 25 | ] 26 | }, 27 | "nullable": [ 28 | false, 29 | true, 30 | false 31 | ] 32 | }, 33 | "hash": "85c6d9a2308e670def0903647e7b2f1f41ab934e742b828e839698aea8dfa891" 34 | } 35 | -------------------------------------------------------------------------------- /.sqlx/query-8ad4efd6021caa4fddc8a622623dd02864162e8ae7d0f2d45600a7d0aadaa1fb.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO automod_rules (source_id, rule_type, rule_data, rule_action) VALUES ($1, $2, $3, $4) RETURNING item_id", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "item_id", 9 | "type_info": "Int4" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int4", 15 | "Int2", 16 | "Text", 17 | "Int2" 18 | ] 19 | }, 20 | "nullable": [ 21 | false 22 | ] 23 | }, 24 | "hash": "8ad4efd6021caa4fddc8a622623dd02864162e8ae7d0f2d45600a7d0aadaa1fb" 25 | } 26 | -------------------------------------------------------------------------------- /.sqlx/query-8fdff3b23bf87171d6501c2f7a9a66db50f4f9adeb5f0130b415f08f2e30c310.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE users SET vote_reminder_enabled = $1 WHERE user_id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bool", 9 | "Bytea" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "8fdff3b23bf87171d6501c2f7a9a66db50f4f9adeb5f0130b415f08f2e30c310" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-9218ad57bf0ef416cd2b700e771fe38924e159ecdc833a99cd716c772e31be34.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO blocked_users\n\t(user_id, reason, blocked_since)\nVALUES\n\t($1, 'disputed payment', now())\nON CONFLICT\n\tDO NOTHING\n\t\t\t\t", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "9218ad57bf0ef416cd2b700e771fe38924e159ecdc833a99cd716c772e31be34" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-a2abf8a0f150945b1b627443345b17ca90067b1fdc085602e205292f09731457.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, transcript_only_role) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET transcript_only_role = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Int8" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "a2abf8a0f150945b1b627443345b17ca90067b1fdc085602e205292f09731457" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-a55baeff8a9d29d3f4bf93bdce5a8516107f98b21ef7eaf913e1d73cafbad1a1.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO vote_reminders (user_id, site_id, next_reminder)\n VALUES ($1, 3, NOW() + INTERVAL '12 hours')\n ON CONFLICT (user_id, site_id)\n DO UPDATE SET next_reminder = NOW() + INTERVAL '12 hours'", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "a55baeff8a9d29d3f4bf93bdce5a8516107f98b21ef7eaf913e1d73cafbad1a1" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-a61af56279669945475e1bd6c07314ddcc6b8dd48641b187bcf3f92516b680e0.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT vote_reminder_enabled FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "vote_reminder_enabled", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "a61af56279669945475e1bd6c07314ddcc6b8dd48641b187bcf3f92516b680e0" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-a70c68916ccaa238c333c30cc7aad03bc9dd4d6045e8701b20c0b184ef43d855.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT auto_join FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "auto_join", 9 | "type_info": "Bool" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "a70c68916ccaa238c333c30cc7aad03bc9dd4d6045e8701b20c0b184ef43d855" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-a79cb39778f193bc5d0f987f046f32e9c5558510474cced79325769001f0ae68.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO audio_store (source_id, audio_data, transcript, transcript_language) VALUES ($1, $2, $3, $4)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Bytea", 10 | "Text", 11 | "Text" 12 | ] 13 | }, 14 | "nullable": [] 15 | }, 16 | "hash": "a79cb39778f193bc5d0f987f046f32e9c5558510474cced79325769001f0ae68" 17 | } 18 | -------------------------------------------------------------------------------- /.sqlx/query-a87cecbf5594813de9ae09f64bf587c1351b38780227baedb26c86422e2bdaa6.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, language) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET language = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Text" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "a87cecbf5594813de9ae09f64bf587c1351b38780227baedb26c86422e2bdaa6" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-a90b58705c2aa90bd947676b09f5c1bd7cf8de9ac78a57ae007ba1e233e23ff5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, translate) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET translate = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "a90b58705c2aa90bd947676b09f5c1bd7cf8de9ac78a57ae007ba1e233e23ff5" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-ab63ab3a8a49e4c632a3e8c65b352f129d1b8cf94214bc9cb993084615a7c16f.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO blocked_users (user_id, reason, blocked_since) VALUES ($1, $2, $3)", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Text", 10 | "Timestamptz" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "ab63ab3a8a49e4c632a3e8c65b352f129d1b8cf94214bc9cb993084615a7c16f" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-b16a45673e483ada9bfe1a8abbb87d4a340ead03b9eabe78831c57260f4e5c1b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT item_id FROM automod_config WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "item_id", 9 | "type_info": "Int4" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "b16a45673e483ada9bfe1a8abbb87d4a340ead03b9eabe78831c57260f4e5c1b" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-b6c6c3af88d80513ae83e8f6423b54f0eb8b7344a765f5205b2ae35347e0ee00.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users\n (user_id, premium_level, premium_expiry, is_trialing)\nVALUES\n ($1, 0, NULL, false)\nON CONFLICT\n ON CONSTRAINT users_pkey\n DO UPDATE\n SET\n premium_level = 0,\n premium_expiry = NULL,\n is_trialing = false\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "b6c6c3af88d80513ae83e8f6423b54f0eb8b7344a765f5205b2ae35347e0ee00" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-bf767562a1fcd3e989d5d7800f8feb19959cb78d9d68850bd4d18bf3f5d6855f.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT guild_id FROM guilds LIMIT 1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "guild_id", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [] 14 | }, 15 | "nullable": [ 16 | false 17 | ] 18 | }, 19 | "hash": "bf767562a1fcd3e989d5d7800f8feb19959cb78d9d68850bd4d18bf3f5d6855f" 20 | } 21 | -------------------------------------------------------------------------------- /.sqlx/query-bfca9396251c01fbf9510069699b07a287ac149e0598f5056d25b8582481a44d.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT item_id, rule_type, rule_action, rule_data FROM automod_rules WHERE source_id = (SELECT item_id FROM automod_config WHERE guild_id = $1) ORDER BY item_id ASC", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "item_id", 9 | "type_info": "Int4" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "rule_type", 14 | "type_info": "Int2" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "rule_action", 19 | "type_info": "Int2" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "rule_data", 24 | "type_info": "Text" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Int8" 30 | ] 31 | }, 32 | "nullable": [ 33 | false, 34 | false, 35 | false, 36 | false 37 | ] 38 | }, 39 | "hash": "bfca9396251c01fbf9510069699b07a287ac149e0598f5056d25b8582481a44d" 40 | } 41 | -------------------------------------------------------------------------------- /.sqlx/query-c582ba49e685230ff575ffeb988d5e400b32202ccdad7f1394ae0a4f534bebd4.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id) VALUES ($1) ON CONFLICT DO NOTHING", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "c582ba49e685230ff575ffeb988d5e400b32202ccdad7f1394ae0a4f534bebd4" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-c59f56d07fec4f68c5c8640e55d12d91d21841bc52f977418f0ac2ca44f95da4.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO default_join_settings (guild_id, target_channel)\n\t\t\t\tVALUES ($1, $2)\n\t\t\t\tON CONFLICT ON CONSTRAINT default_join_settings_pkey\n\t\t\t\t DO UPDATE SET target_channel = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Int8" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "c59f56d07fec4f68c5c8640e55d12d91d21841bc52f977418f0ac2ca44f95da4" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-d18d09060c52e884da8d3118a9506589b1c597eae4e9f1294758cf97513a0655.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO default_join_settings (guild_id, ephemeral)\n\t\t\tVALUES ($1, $2)\n\t\t\tON CONFLICT ON CONSTRAINT default_join_settings_pkey\n\t\t\t DO UPDATE SET ephemeral = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "d18d09060c52e884da8d3118a9506589b1c597eae4e9f1294758cf97513a0655" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-d61f09ce4f51ba7952643cb0f3a73b0e82ad46fffcbee1c80097bf5d4999ba56.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT target_channel FROM per_voice_channel_settings WHERE channel_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "target_channel", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | true 19 | ] 20 | }, 21 | "hash": "d61f09ce4f51ba7952643cb0f3a73b0e82ad46fffcbee1c80097bf5d4999ba56" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-d87f633a372b2d8a6b22cd2f472d6ede1dad106dc17122e9bc1ee4c6aeda1003.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, transcribe_voice_messages) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET transcribe_voice_messages = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "d87f633a372b2d8a6b22cd2f472d6ede1dad106dc17122e9bc1ee4c6aeda1003" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-d95a944ff53eac4be1375577edabda691086a1475563fbb23c80128f44fc0ede.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users\n(user_id)\nVALUES ($1)\n ON CONFLICT\n ON CONSTRAINT users_pkey\n DO NOTHING\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "d95a944ff53eac4be1375577edabda691086a1475563fbb23c80128f44fc0ede" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-da69ed66364b8d8e5d0a70a8e789a1bfffa6fe7e7d1380ebe6547f485421a7e0.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT COALESCE(\n\t\t\t\t(SELECT language FROM guilds WHERE guild_id = $1),\n\t\t\t\t(SELECT language FROM users WHERE user_id = $2),\n \t'en'\n \t\t) AS \"language!\"", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "language!", 9 | "type_info": "Text" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8", 15 | "Bytea" 16 | ] 17 | }, 18 | "nullable": [ 19 | null 20 | ] 21 | }, 22 | "hash": "da69ed66364b8d8e5d0a70a8e789a1bfffa6fe7e7d1380ebe6547f485421a7e0" 23 | } 24 | -------------------------------------------------------------------------------- /.sqlx/query-da83b806c17fe16468b9c70136eaf3ded1d1079761919c7a9f2c9f8b53e72f0d.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT new_thread, ephemeral FROM default_join_settings WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "new_thread", 9 | "type_info": "Bool" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "ephemeral", 14 | "type_info": "Bool" 15 | } 16 | ], 17 | "parameters": { 18 | "Left": [ 19 | "Int8" 20 | ] 21 | }, 22 | "nullable": [ 23 | false, 24 | false 25 | ] 26 | }, 27 | "hash": "da83b806c17fe16468b9c70136eaf3ded1d1079761919c7a9f2c9f8b53e72f0d" 28 | } 29 | -------------------------------------------------------------------------------- /.sqlx/query-dfa520877c017cd5808d02c24ef2d71938b68093974f335a4d89df91874fdaa2.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "DELETE FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "dfa520877c017cd5808d02c24ef2d71938b68093974f335a4d89df91874fdaa2" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-e12aad9daf3df4c789502c6b54d770f6f4141273f107981255b36b67ffd131b3.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO per_voice_channel_settings (channel_id, auto_join_enabled)\n\t\t\t\tVALUES ($1, $2)\n\t\t\t\tON CONFLICT\n\t\t\t\t ON CONSTRAINT per_voice_channel_settings_pkey\n\t\t\t\t DO UPDATE SET auto_join_enabled = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "e12aad9daf3df4c789502c6b54d770f6f4141273f107981255b36b67ffd131b3" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-e1758cf7ba1424941e3c07f12c0b28f338781ab348743979c57d8a8e8336a9b7.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT premium_level FROM users INNER JOIN guilds g on users.user_id = g.premium_owner_id WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "premium_level", 9 | "type_info": "Int2" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "e1758cf7ba1424941e3c07f12c0b28f338781ab348743979c57d8a8e8336a9b7" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-e20129046ffbefeacec498611c65ecaa944eae3ca76f87b02c93118aa663791b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users\n (user_id, premium_level, premium_expiry, is_trialing)\nVALUES\n ($1, 0, NULL, false)\nON CONFLICT\n ON CONSTRAINT users_pkey\n DO UPDATE\n SET\n premium_level = 0,\n premium_expiry = NULL,\n is_trialing = false\n", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "e20129046ffbefeacec498611c65ecaa944eae3ca76f87b02c93118aa663791b" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-eb1135121d7f409c041eff3645dec66471e99d4cc0c0e689fe137bad90ee70f0.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO default_join_settings (guild_id, new_thread)\n\t\t\tVALUES ($1, $2)\n\t\t\tON CONFLICT ON CONSTRAINT default_join_settings_pkey\n\t\t\t DO UPDATE SET new_thread = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "eb1135121d7f409c041eff3645dec66471e99d4cc0c0e689fe137bad90ee70f0" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-ebb2c791e92fec2e5b8f0ccbf10b9e0da345e40a2054c74652c4eb7842f88ed5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO per_voice_channel_settings (channel_id, target_channel)\n\t\t\t\tVALUES ($1, $2)\n\t\t\t \tON CONFLICT ON CONSTRAINT per_voice_channel_settings_pkey\n\t\t\t \t DO UPDATE SET target_channel = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Int8" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "ebb2c791e92fec2e5b8f0ccbf10b9e0da345e40a2054c74652c4eb7842f88ed5" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-ed9c186510d2e3d31b1634a1db068e786996dcb6c0dc741dc207df9f73ce4b7a.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, auto_join) VALUES ($1, $2) ON CONFLICT ON CONSTRAINT guilds_pkey DO UPDATE SET auto_join = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "ed9c186510d2e3d31b1634a1db068e786996dcb6c0dc741dc207df9f73ce4b7a" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-f1127d19808b04e64dbb74fddccd72146bb25d96661bf4ce473d0be6fddcbd23.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (premium_owner_id, guild_id) VALUES ($1, $2) ON CONFLICT ON CONSTRAINT guilds_pkey DO UPDATE SET premium_owner_id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Int8" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "f1127d19808b04e64dbb74fddccd72146bb25d96661bf4ce473d0be6fddcbd23" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-f2b76182252d68d938737226b537ddff4930ff59af6dd60bfbb15e11236cdfdb.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT be_verbose, language, auto_detect_lang, transcript_only_role, translate, kiai_enabled FROM guilds WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "be_verbose", 9 | "type_info": "Bool" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "language", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "auto_detect_lang", 19 | "type_info": "Bool" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "transcript_only_role", 24 | "type_info": "Int8" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "translate", 29 | "type_info": "Bool" 30 | }, 31 | { 32 | "ordinal": 5, 33 | "name": "kiai_enabled", 34 | "type_info": "Bool" 35 | } 36 | ], 37 | "parameters": { 38 | "Left": [ 39 | "Int8" 40 | ] 41 | }, 42 | "nullable": [ 43 | false, 44 | false, 45 | false, 46 | true, 47 | false, 48 | false 49 | ] 50 | }, 51 | "hash": "f2b76182252d68d938737226b537ddff4930ff59af6dd60bfbb15e11236cdfdb" 52 | } 53 | -------------------------------------------------------------------------------- /.sqlx/query-f2f065836ccd89c512070fad43b04c5e0a842c6cb7ba09dac4439239db761f74.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT premium_level FROM users WHERE user_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "premium_level", 9 | "type_info": "Int2" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "f2f065836ccd89c512070fad43b04c5e0a842c6cb7ba09dac4439239db761f74" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-f3edc5bc2652e4dd2e9ea1bf9dcdeb7a98fe957e4898da60413c356dd82e7c29.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users\n (user_id, premium_level, premium_expiry)\nVALUES\n ($1, 1, to_timestamp($2))\nON CONFLICT\n ON CONSTRAINT users_pkey\n DO UPDATE\n SET\n premium_level = 1,\n premium_expiry = to_timestamp($2)\n", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea", 9 | "Float8" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "f3edc5bc2652e4dd2e9ea1bf9dcdeb7a98fe957e4898da60413c356dd82e7c29" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-f4a7c95fdaba6a38ffe1bd3177687ff90e52c8592310914c876d668e8dd30ec1.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds\n \t\t(guild_id, prefix)\n\t\t\tVALUES ($1, $2)\n\t\t\tON CONFLICT\n\t\t\t ON CONSTRAINT guilds_pkey \n\t\t\t DO UPDATE SET prefix = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Varchar" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "f4a7c95fdaba6a38ffe1bd3177687ff90e52c8592310914c876d668e8dd30ec1" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-f89f8a631ebf9bd1c26cfff8d7323ac758f64807e49c9e3179979efb78875c71.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO users (user_id) VALUES ($1) ON CONFLICT ON CONSTRAINT users_pkey DO NOTHING", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "f89f8a631ebf9bd1c26cfff8d7323ac758f64807e49c9e3179979efb78875c71" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-fa9bd7bdaa7ecd515b79993101eacfb3c3b0394fc7e6a75c5bec43c239a9ef90.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users\n (user_id, premium_level, premium_expiry, is_trialing)\nVALUES\n ($1, 0, NULL, false)\nON CONFLICT\n ON CONSTRAINT users_pkey\n DO UPDATE\n SET\n premium_level = 0,\n premium_expiry = NULL,\n is_trialing = false", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Bytea" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "fa9bd7bdaa7ecd515b79993101eacfb3c3b0394fc7e6a75c5bec43c239a9ef90" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-fd57884a830694a5fc149d1ef72983eb444c0c15371e8771e0136ca848afa105.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO guilds (guild_id, kiai_enabled) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET kiai_enabled = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Bool" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "fd57884a830694a5fc149d1ef72983eb444c0c15371e8771e0136ca848afa105" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-fe0b5b46dc49b47641b4c7811fc7bdf7966bad815cbf02d6f3c2e5721445f256.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT record_transcriptions, target_channel, new_thread, ephemeral FROM default_join_settings WHERE guild_id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "record_transcriptions", 9 | "type_info": "Bool" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "target_channel", 14 | "type_info": "Int8" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "new_thread", 19 | "type_info": "Bool" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "ephemeral", 24 | "type_info": "Bool" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Int8" 30 | ] 31 | }, 32 | "nullable": [ 33 | false, 34 | true, 35 | false, 36 | false 37 | ] 38 | }, 39 | "hash": "fe0b5b46dc49b47641b4c7811fc7bdf7966bad815cbf02d6f3c2e5721445f256" 40 | } 41 | -------------------------------------------------------------------------------- /.weblate: -------------------------------------------------------------------------------- 1 | [weblate] 2 | url = https://hosted.weblate.org/api/ 3 | translation = scripty-bot/scripty-bot 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rustlang/rust:nightly-bookworm as build 2 | 3 | WORKDIR /scripty 4 | 5 | COPY . . 6 | 7 | RUN apt update -y 8 | RUN apt install -y clang mold libopus-dev libopus0 9 | 10 | ENV RUSTFLAGS="--emit=asm --cfg tokio_unstable -Clink-arg=-fuse-ld=mold -Zshare-generics=y" 11 | ENV SQLX_OFFLINE=1 12 | 13 | 14 | RUN cargo build --release 15 | 16 | FROM debian:bookworm-slim 17 | 18 | COPY --from=build /usr/lib/x86_64-linux-gnu/libopus.so.0.8.0 /usr/lib/x86_64-linux-gnu/libopus.so.0.8.0 19 | COPY --from=build /usr/lib/x86_64-linux-gnu/libopus.so.0 /usr/lib/x86_64-linux-gnu/libopus.so.0 20 | 21 | WORKDIR /app 22 | COPY --from=build /scripty/target/release/scripty_v2 . 23 | 24 | RUN adduser --home /nonexistent --no-create-home --disabled-password scripty 25 | USER scripty 26 | 27 | VOLUME "/app/config.toml" 28 | 29 | CMD ["/app/scripty_v2", "/app/config.toml"] 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scripty (v2) 2 | 3 | rewrite of the [old Scripty](https://github.com/tazz4843/scripty) in general 4 | 5 | ## selfhosting 6 | 7 | ### native binary 8 | 9 | this is what scripty is running on in production and is actively tested 10 | 11 | see [the prebuilt binary](./doc/selfhosting-prebuilt.md) 12 | or [build from source](./doc/selfhosting-from-source.md) guides 13 | 14 | ### podman 15 | 16 | much less native support given 17 | 18 | [see the docs](./doc/selfhosting-podman.md) 19 | 20 | ## translations? 21 | 22 | 23 | Translation status 24 | 25 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | targets = [ 2 | { triple = "x86_64-unknown-linux-gnu" }, 3 | { triple = "x86_64-unknown-linux-musl" } 4 | ] 5 | 6 | [advisories] 7 | vulnerability = "deny" 8 | unmaintained = "deny" 9 | notice = "deny" 10 | unsound = "deny" 11 | 12 | [bans] 13 | deny = [ 14 | # we hate OpenSSL 15 | { name = "openssl" }, 16 | { name = "openssl-sys" }, 17 | ] 18 | wildcards = "deny" 19 | allow-wildcard-paths = true 20 | highlight = "simplest-path" 21 | 22 | 23 | [sources] 24 | unknown-registry = "deny" 25 | unknown-git = "deny" 26 | required-git-spec = "branch" 27 | 28 | [sources.allow-org] 29 | github = ["scripty-bot", "tazz4843", "serenity-rs"] 30 | 31 | [licenses] 32 | unlicensed = "deny" 33 | copyleft = "allow" 34 | allow-osi-fsf-free = "either" 35 | 36 | [[licenses.clarify]] 37 | name = "ring" 38 | # SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses 39 | # https://spdx.org/licenses/OpenSSL.html 40 | # ISC - Both BoringSSL and ring use this for their new files 41 | # MIT - "Files in third_party/ have their own licenses, as described therein. The MIT 42 | # license, for third_party/fiat, which, unlike other third_party directories, is 43 | # compiled into non-test libraries, is included below." 44 | # OpenSSL - Obviously 45 | expression = "ISC AND MIT AND OpenSSL" 46 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] 47 | -------------------------------------------------------------------------------- /doc/selfhosting-podman.md: -------------------------------------------------------------------------------- 1 | # selfhosting scripty via podman 2 | 3 | ## notes 4 | 5 | Scripty's pre-built binaries only support x86_64 Linux: 6 | if you're on another system you must [build from source](./selfhosting-from-source.md) 7 | 8 | the podman build is completely untested 9 | 10 | the binary runs but i have not tested if it's actually functional, 11 | so expect broken stuff sometimes 12 | 13 | official support for broken stuff is available, poke me on scripty's discord 14 | 15 | rootful docker will never be officially supported, but it'll likely work anyway. 16 | you should not be using rootful though because it's a massive security hole, 17 | and if you ask for help while using rootful i'll tell you to use rootless or podman 18 | 19 | rootless is essentially equivalent to podman, just substitute `docker` for `podman` in commands 20 | 21 | ## the image 22 | 23 | this repo automatically builds a podman image every commit at 24 | https://github.com/scripty-bot/scripty/pkgs/container/scripty 25 | 26 | pull it with 27 | 28 | ```bash 29 | podman pull ghcr.io/scripty-bot/scripty:master 30 | ``` 31 | 32 | ## running 33 | 34 | it expects `config.toml` mounted at `/app/config.toml`, 35 | and scripty's internal webserver binds on `127.0.0.1:42069` by default. 36 | change as necessary 37 | 38 | a postgres database must be accessible somehow, configure that yourself 39 | 40 | ```bash 41 | podman run -p 42069:42069 -v "./config.toml:/app/config.toml:ro" ghcr.io/scripty-bot/scripty:master 42 | ``` 43 | -------------------------------------------------------------------------------- /doc/selfhosting-prebuilt.md: -------------------------------------------------------------------------------- 1 | # selfhosting scripty - prebuilt executable 2 | 3 | only supports linux, will not support any other OS, especially not windows 4 | 5 | WSL does not count as linux, use a VM at the very least (although good luck getting GPU passthrough) 6 | 7 | Scripty's pre-built binaries only support x86_64 Linux using glibc: 8 | if you're on another system you must [build from source](./selfhosting-from-source.md) 9 | 10 | ## clone the repo 11 | 12 | some files in the repo are required for scripty to run 13 | (mostly internationalization files) 14 | 15 | ```bash 16 | git clone https://github.com/scripty-bot/scripty 17 | cd scripty 18 | ``` 19 | 20 | ## download the binary 21 | 22 | each commit builds a binary 23 | 24 | 1. head to the github web UI and 25 | click the action icon directly to the right of the latest commit title 26 | 2. click "Details" to the right of "Build executable / Build binary" 27 | 3. click summary in the upper left 28 | 4. scroll down to artifacts 29 | 5. hit the download button on `scripty_v2-x86_64-unknown-linux-gnu` 30 | 6. save to a convenient location (most likely directly in the root directory of the repo) 31 | 32 | ## running 33 | 34 | continue from the "migrate database" section of the [build from source page](./selfhosting-from-source.md), 35 | and skip the "build" section 36 | -------------------------------------------------------------------------------- /migrations/20220205060135_initial_migration.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS guilds ( 2 | guild_id BIGINT PRIMARY KEY NOT NULL, 3 | target_channel BIGINT, 4 | -- later scripty will have language cfgs 5 | language CHAR(5), 6 | be_verbose BOOLEAN, 7 | premium_level SMALLINT 8 | ); 9 | 10 | CREATE TABLE IF NOT EXISTS users ( 11 | user_id BIGINT PRIMARY KEY NOT NULL, 12 | github_account TEXT, 13 | premium_level SMALLINT NOT NULL DEFAULT 0, 14 | used_servers SMALLINT NOT NULL DEFAULT 0 15 | ); 16 | 17 | CREATE TABLE IF NOT EXISTS prefixes ( 18 | guild_id BIGINT PRIMARY KEY NOT NULL, 19 | prefix VARCHAR(10) 20 | ); 21 | 22 | CREATE TABLE IF NOT EXISTS channels ( 23 | channel_id BIGINT PRIMARY KEY NOT NULL, 24 | webhook_id BIGINT NOT NULL, 25 | webhook_token TEXT NOT NULL 26 | ); 27 | -------------------------------------------------------------------------------- /migrations/20220206034602_blocked_entities.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS blocked_users ( 2 | user_id BIGINT PRIMARY KEY, 3 | reason TEXT, 4 | blocked_since TIMESTAMP WITHOUT TIME ZONE 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS blocked_guilds ( 8 | guild_id BIGINT PRIMARY KEY, 9 | reason TEXT, 10 | blocked_since TIMESTAMP WITHOUT TIME ZONE 11 | ); 12 | -------------------------------------------------------------------------------- /migrations/20220208005540_convert_to_not_null.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE guilds ALTER COLUMN target_channel SET NOT NULL; 2 | ALTER TABLE guilds ALTER COLUMN language SET NOT NULL; 3 | ALTER TABLE guilds ALTER COLUMN be_verbose SET NOT NULL; 4 | ALTER TABLE guilds ALTER COLUMN premium_level SET NOT NULL; 5 | -------------------------------------------------------------------------------- /migrations/20220213083816_more_user_fields.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN store_audio BOOLEAN NOT NULL DEFAULT false; 2 | ALTER TABLE users ADD COLUMN store_msgs BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /migrations/20220323050213_add_user_language.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN language varchar(5) NOT NULL DEFAULT 'en'; 2 | -------------------------------------------------------------------------------- /migrations/20220324042751_change_user_language_column_type.sql: -------------------------------------------------------------------------------- 1 | -- change the type of column language in users table from varchar(5) to text 2 | ALTER TABLE users ALTER COLUMN language TYPE text; 3 | -- change the type of column language in guilds table from varchar(5) to text 4 | ALTER TABLE guilds ALTER COLUMN language TYPE text; 5 | -- change the default value of column language in guilds table to 'en' 6 | ALTER TABLE guilds ALTER COLUMN language SET DEFAULT 'en'; -------------------------------------------------------------------------------- /migrations/20220324044030_change_defaults.sql: -------------------------------------------------------------------------------- 1 | -- remove not null constraint on column "target_channel" in table "guilds" 2 | ALTER TABLE guilds ALTER COLUMN target_channel DROP NOT NULL; 3 | -- remove default value on column "target_channel" in table "guilds" 4 | ALTER TABLE guilds ALTER COLUMN target_channel DROP DEFAULT; 5 | 6 | -- set default value on column "language" in table "guilds" to "en" 7 | ALTER TABLE guilds ALTER COLUMN language SET DEFAULT 'en'; 8 | 9 | -- set default value on column "be_verbose" in table "guilds" to false 10 | ALTER TABLE guilds ALTER COLUMN be_verbose SET DEFAULT false; 11 | 12 | -- set default value on column "premium_level" in table "guilds" to 0 13 | ALTER TABLE guilds ALTER COLUMN premium_level SET DEFAULT 0; 14 | -------------------------------------------------------------------------------- /migrations/20220422014155_convert_userids_to_hashed.sql: -------------------------------------------------------------------------------- 1 | -- user IDs are now going to be hashed with sha512 2 | -- targets: 3 | -- - audio_store (source_id) 4 | -- - message_store (author_id) 5 | -- - blocked_users (user_id) 6 | -- - users (user_id) 7 | 8 | -- the existing type is bigint, which is difficult to migrate to bytea 9 | -- so just drop and recreate the two columns 10 | ALTER TABLE audio_store DROP COLUMN source_id; 11 | ALTER TABLE message_store DROP COLUMN author_id; 12 | ALTER TABLE blocked_users DROP COLUMN user_id; 13 | ALTER TABLE users DROP COLUMN user_id; 14 | 15 | -- recreate the columns 16 | ALTER TABLE users ADD COLUMN user_id bytea NOT NULL PRIMARY KEY; 17 | ALTER TABLE audio_store ADD COLUMN source_id bytea NOT NULL REFERENCES users(user_id) ON DELETE CASCADE; 18 | ALTER TABLE message_store ADD COLUMN author_id bytea NOT NULL REFERENCES users(user_id) ON DELETE CASCADE; 19 | ALTER TABLE blocked_users ADD COLUMN user_id bytea NOT NULL REFERENCES users(user_id) ON DELETE CASCADE PRIMARY KEY; 20 | -------------------------------------------------------------------------------- /migrations/20220619210730_default_for_audio_store_ID.sql: -------------------------------------------------------------------------------- 1 | -- language: psql 2 | -- alter column id on table audio_store to be auto-incrementing 3 | ALTER TABLE audio_store DROP COLUMN id; 4 | ALTER TABLE audio_store ADD COLUMN id SERIAL PRIMARY KEY; 5 | -------------------------------------------------------------------------------- /migrations/20220707194906_remove_author_id_and_message_id_from_message_store.sql: -------------------------------------------------------------------------------- 1 | -- language: postgres 2 | -- drop two identifying columns 3 | ALTER TABLE message_store DROP COLUMN message_id; 4 | ALTER TABLE message_store DROP COLUMN author_id; 5 | -- replace the message_id column with a primary key that is independent of message IDs 6 | ALTER TABLE message_store ADD COLUMN message_id BIGSERIAL PRIMARY KEY; 7 | -------------------------------------------------------------------------------- /migrations/20220707232119_switch_type_of_message_content.sql: -------------------------------------------------------------------------------- 1 | -- language: postgres 2 | ALTER TABLE message_store DROP COLUMN text; 3 | ALTER TABLE message_store ADD COLUMN message_content bytea; 4 | -------------------------------------------------------------------------------- /migrations/20220707232647_not_null_message_content.sql: -------------------------------------------------------------------------------- 1 | -- language: postgres 2 | ALTER TABLE message_store ALTER COLUMN message_content SET NOT NULL; 3 | ALTER TABLE message_store ALTER COLUMN message_content DROP DEFAULT; 4 | -------------------------------------------------------------------------------- /migrations/20220708045546_save_nonce_with_message_content.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE message_store ADD COLUMN nonce BYTEA NOT NULL; 2 | -------------------------------------------------------------------------------- /migrations/20220801052859_alter_premium_layout.sql: -------------------------------------------------------------------------------- 1 | -- language: postgres 2 | 3 | -- drop unnecessary columns now 4 | ALTER TABLE users DROP COLUMN github_account; 5 | ALTER TABLE users DROP COLUMN used_servers; 6 | 7 | ALTER TABLE guilds DROP COLUMN premium_level; 8 | 9 | 10 | -- add new columns 11 | ALTER TABLE users ADD COLUMN trial_used BOOLEAN DEFAULT FALSE NOT NULL; 12 | ALTER TABLE guilds ADD COLUMN trial_used BOOLEAN DEFAULT FALSE NOT NULL; 13 | 14 | ALTER TABLE users ADD COLUMN premium_expiry TIMESTAMP DEFAULT NULL; 15 | 16 | ALTER TABLE guilds ADD COLUMN premium_owner_id BYTEA DEFAULT NULL REFERENCES users(user_id) ON DELETE SET NULL; 17 | -------------------------------------------------------------------------------- /migrations/20220801061346_add_is_trialing_to_user.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN is_trialing BOOLEAN NOT NULL DEFAULT FALSE; -------------------------------------------------------------------------------- /migrations/20220801063251_add_timezone_to_timestamps_because_sqlx_doesn't_support_without.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ALTER COLUMN premium_expiry TYPE TIMESTAMP WITH TIME ZONE; 2 | -------------------------------------------------------------------------------- /migrations/20220801222122_add_timezone_to_timestamps_pt_2.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE blocked_users ALTER COLUMN blocked_since TYPE TIMESTAMP WITH TIME ZONE USING blocked_since::timestamp; 2 | ALTER TABLE blocked_guilds ALTER COLUMN blocked_since TYPE TIMESTAMP WITH TIME ZONE USING blocked_since::timestamp; 3 | -------------------------------------------------------------------------------- /migrations/20220824213251_remove_channels_table.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE guilds DROP CONSTRAINT guilds_premium_owner_id_fkey; 2 | DROP TABLE channels; 3 | -------------------------------------------------------------------------------- /migrations/20221122161618_automod_tables.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | -- language: postgresql 3 | 4 | -- add automod table 5 | CREATE TABLE automod_config ( 6 | item_id SERIAL PRIMARY KEY, 7 | guild_id bigint NOT NULL REFERENCES guilds (guild_id) ON DELETE CASCADE UNIQUE, 8 | enabled boolean NOT NULL DEFAULT false, 9 | log_channel_id bigint NOT NULL, 10 | 11 | -- if rule action is 2 or 3, log a recording of the message? 12 | log_recording boolean NOT NULL DEFAULT false, 13 | 14 | -- automatically join a voice channel if a user joins a voice channel? 15 | auto_join_voice boolean NOT NULL DEFAULT true 16 | ); 17 | 18 | CREATE TABLE automod_rules ( 19 | item_id SERIAL PRIMARY KEY, 20 | source_id bigint NOT NULL REFERENCES automod_config (item_id) ON DELETE CASCADE, 21 | 22 | -- rule type: currently only one type is supported, added for future expansion 23 | -- 1 for regular text match 24 | rule_type SMALLINT NOT NULL, 25 | 26 | -- rule data: any attached data to the rule, such as text to match 27 | rule_data TEXT NOT NULL, 28 | 29 | -- rule action: what to do when the rule is triggered 30 | -- 1 for silent delete 31 | -- 2 for delete and log 32 | -- 3 for delete, log, and remove user from voice 33 | rule_action SMALLINT NOT NULL 34 | ); 35 | -------------------------------------------------------------------------------- /migrations/20221124000934_add_unique_constraint_for_automod.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | -- language: postgresql 3 | 4 | -- add a unique constraint to table automod_rules, on columns rule_type, rule_data, rule_action, and source_id 5 | ALTER TABLE automod_rules ADD CONSTRAINT unique_rule UNIQUE (rule_type, rule_data, rule_action, source_id); 6 | -------------------------------------------------------------------------------- /migrations/20221127221641_fix_table_types.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE automod_rules ALTER COLUMN source_id TYPE integer; -------------------------------------------------------------------------------- /migrations/20230607172816_voice_message_enable.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE guilds ADD COLUMN transcribe_voice_messages bool NOT NULL DEFAULT true; 2 | -------------------------------------------------------------------------------- /migrations/20230814023409_remove_target_channel_and_add_agreed_tos.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds DROP COLUMN target_channel; 3 | ALTER TABLE guilds ADD COLUMN agreed_tos boolean NOT NULL DEFAULT false; 4 | -------------------------------------------------------------------------------- /migrations/20231014010314_guild_all_audio_file_transcription.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds ADD COLUMN transcribe_audio_files BOOLEAN NOT NULL DEFAULT FALSE; -------------------------------------------------------------------------------- /migrations/20231014175034_guild_all_video_file_transcription.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds ADD COLUMN transcribe_video_files BOOLEAN NOT NULL DEFAULT false; -------------------------------------------------------------------------------- /migrations/20231129024221_guild_auto_detect_lang.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds ADD COLUMN auto_detect_lang BOOLEAN NOT NULL DEFAULT FALSE; 3 | -------------------------------------------------------------------------------- /migrations/20231202032922_guild_transcript_only_role_members.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds ADD COLUMN transcript_only_role BIGINT DEFAULT NULL; 3 | -------------------------------------------------------------------------------- /migrations/20231211021825_translate_to_english.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds ADD COLUMN translate BOOLEAN NOT NULL DEFAULT FALSE; 3 | -------------------------------------------------------------------------------- /migrations/20231211023309_translate_to_english_enabled_by_default.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds ALTER COLUMN translate SET DEFAULT true; 3 | -------------------------------------------------------------------------------- /migrations/20231216214434_vote_reminder_table.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | CREATE TABLE vote_reminders ( 3 | user_id BIGINT NOT NULL, 4 | site_id SMALLINT NOT NULL, 5 | next_reminder TIMESTAMP NOT NULL, 6 | 7 | PRIMARY KEY (user_id, site_id) 8 | ); 9 | -------------------------------------------------------------------------------- /migrations/20231217034034_vote_reminder_opt_out.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE users ADD COLUMN vote_reminder_disabled BOOLEAN NOT NULL DEFAULT FALSE; 3 | -------------------------------------------------------------------------------- /migrations/20231229163655_vote_reminder_enabled.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE users DROP COLUMN vote_reminder_disabled; 3 | ALTER TABLE users ADD COLUMN vote_reminder_enabled boolean NOT NULL DEFAULT true; 4 | -------------------------------------------------------------------------------- /migrations/20240115205731_kiai_integration_enabled.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds ADD COLUMN kiai_enabled BOOLEAN NOT NULL DEFAULT FALSE; 3 | -------------------------------------------------------------------------------- /migrations/20240415174617_remove_tos_agree.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds 3 | DROP COLUMN agreed_tos; 4 | -------------------------------------------------------------------------------- /migrations/20240920165244_default_translate_to_false.sql: -------------------------------------------------------------------------------- 1 | -- Add migration script here 2 | ALTER TABLE guilds 3 | ALTER COLUMN translate SET DEFAULT FALSE; -------------------------------------------------------------------------------- /migrations/20250119180948_add_default_command_parameter_options.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS default_join_settings 2 | ( 3 | guild_id BIGINT NOT NULL PRIMARY KEY REFERENCES guilds, 4 | record_transcriptions BOOLEAN DEFAULT FALSE NOT NULL, 5 | target_channel BIGINT DEFAULT NULL, 6 | new_thread BOOLEAN DEFAULT FALSE NOT NULL, 7 | ephemeral BOOLEAN DEFAULT FALSE NOT NULL 8 | ); 9 | -------------------------------------------------------------------------------- /migrations/20250222001334_move_auto_join_to_config.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE guilds 2 | ADD COLUMN auto_join BOOLEAN NOT NULL DEFAULT FALSE; 3 | 4 | -- automigrate data from automod_config to guilds 5 | -- default false covers the other cases 6 | UPDATE guilds 7 | SET auto_join = true 8 | WHERE guild_id IN (SELECT guild_id FROM automod_config WHERE auto_join_voice = true); 9 | 10 | ALTER TABLE automod_config 11 | DROP COLUMN auto_join_voice; 12 | 13 | -- clean up any servers that used automod only for the auto join function 14 | DELETE 15 | FROM automod_config AS r 16 | WHERE NOT EXISTS (SELECT source_id FROM automod_rules WHERE source_id = r.item_id); 17 | -------------------------------------------------------------------------------- /migrations/20250302041748_fix_fkey_relations.sql: -------------------------------------------------------------------------------- 1 | -- stop errors when a user requests to have their data deleted and themselves banned 2 | ALTER TABLE blocked_users 3 | DROP CONSTRAINT blocked_users_user_id_fkey; 4 | -------------------------------------------------------------------------------- /migrations/20250302043001_move_prefixes_into_guilds_table.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE prefixes; 2 | 3 | ALTER TABLE guilds 4 | ADD COLUMN prefix varchar(8) DEFAULT NULL; 5 | -------------------------------------------------------------------------------- /migrations/20250525065734_per_voice_channel_target_channel.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE per_voice_channel_settings 2 | ( 3 | channel_id BIGINT NOT NULL PRIMARY KEY, 4 | target_channel BIGINT NULL DEFAULT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /migrations/20250526220035_per_voice_channel_auto_join_settings.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE scripty.public.per_voice_channel_settings 2 | ADD COLUMN auto_join_enabled BOOLEAN NOT NULL DEFAULT TRUE; 3 | -------------------------------------------------------------------------------- /prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | 4 | scrape_configs: 5 | - job_name: scripty 6 | static_configs: 7 | targets: 8 | - 'localhost:42069' 9 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true # Not up to negotiation. 2 | enum_discrim_align_threshold = 20 3 | hex_literal_case = "Lower" 4 | imports_layout = "HorizontalVertical" 5 | imports_granularity = "Crate" 6 | newline_style = "Unix" 7 | normalize_comments = true 8 | reorder_impl_items = true 9 | group_imports = "StdExternalCrate" 10 | struct_field_align_threshold = 20 11 | format_strings = true 12 | -------------------------------------------------------------------------------- /scripty_audio_handler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_audio_handler" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | sqlx.workspace = true 14 | ahash.workspace = true 15 | tokio.workspace = true 16 | dashmap.workspace = true 17 | tracing.workspace = true 18 | secrecy.workspace = true 19 | songbird.workspace = true 20 | serenity.workspace = true 21 | backtrace.workspace = true 22 | async-trait.workspace = true 23 | 24 | scripty_db = { path = "../scripty_db" } 25 | scripty_stt = { path = "../scripty_stt" } 26 | #scripty_tts = { path = "../scripty_tts" } 27 | scripty_error = { path = "../scripty_error" } 28 | scripty_utils = { path = "../scripty_utils" } 29 | scripty_redis = { path = "../scripty_redis" } 30 | scripty_automod = { path = "../scripty_automod" } 31 | scripty_metrics = { path = "../scripty_metrics" } 32 | scripty_premium = { path = "../scripty_premium" } 33 | scripty_data_type = { path = "../scripty_data_type" } 34 | scripty_integrations = { path = "../scripty_integrations" } 35 | scripty_data_storage = { path = "../scripty_data_storage" } 36 | -------------------------------------------------------------------------------- /scripty_audio_handler/src/consts.rs: -------------------------------------------------------------------------------- 1 | pub const SIZE_OF_I16: usize = std::mem::size_of::(); 2 | -------------------------------------------------------------------------------- /scripty_audio_handler/src/disconnect.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use dashmap::DashMap; 4 | use scripty_data_type::get_data; 5 | use scripty_error::Error; 6 | use serenity::{gateway::client::Context, model::id::GuildId}; 7 | use songbird::error::JoinError; 8 | 9 | pub async fn disconnect_from_vc(ctx: &Context, guild_id: GuildId) -> Result { 10 | let sb = crate::get_songbird(); 11 | let res = match sb.remove(guild_id).await { 12 | Ok(()) => Ok(true), 13 | Err(JoinError::NoCall) => Ok(false), 14 | Err(e) => Err(e.into()), 15 | }; 16 | 17 | let _ = get_data(ctx).existing_calls.force_remove_guild(&guild_id); 18 | 19 | let existing = super::AUTO_LEAVE_TASKS 20 | .get_or_init(|| DashMap::with_hasher(ahash::RandomState::default())) 21 | .remove(&guild_id); 22 | if let Some(existing) = existing { 23 | // cancel the existing auto-leave task 24 | let _ = existing.1.send(()); // ignore errors as the task may have already been cancelled 25 | } 26 | 27 | tokio::spawn(async move { 28 | const FIVE_SECONDS: Duration = Duration::from_secs(5); 29 | tokio::time::sleep(FIVE_SECONDS).await; 30 | crate::force_handler_update(&guild_id); 31 | }); 32 | 33 | res 34 | } 35 | -------------------------------------------------------------------------------- /scripty_audio_handler/src/events/driver_connect.rs: -------------------------------------------------------------------------------- 1 | use songbird::id::GuildId; 2 | 3 | use crate::audio_handler::ArcSsrcMaps; 4 | 5 | pub async fn driver_connect( 6 | session_id: String, 7 | guild_id: GuildId, 8 | ssrc: u32, 9 | ssrc_state: ArcSsrcMaps, 10 | ) { 11 | debug!( 12 | "connected to Discord voice gateway: session ID {} for guild {}, with ssrc {}", 13 | session_id, guild_id, ssrc 14 | ); 15 | 16 | // ignore self 17 | ssrc_state.ssrc_ignored_map.insert(ssrc, true); 18 | } 19 | -------------------------------------------------------------------------------- /scripty_audio_handler/src/events/mod.rs: -------------------------------------------------------------------------------- 1 | mod client_disconnect; 2 | mod driver_connect; 3 | mod driver_disconnect; 4 | mod rtp_packet; 5 | mod speaking_state_update; 6 | mod voice_tick; 7 | 8 | pub use client_disconnect::client_disconnect; 9 | pub use driver_connect::driver_connect; 10 | pub use driver_disconnect::driver_disconnect; 11 | pub use rtp_packet::rtp_packet; 12 | pub use speaking_state_update::speaking_state_update; 13 | pub use voice_tick::{VoiceTickContext, voice_tick}; 14 | -------------------------------------------------------------------------------- /scripty_audio_handler/src/events/rtp_packet.rs: -------------------------------------------------------------------------------- 1 | use serenity::model::id::GuildId; 2 | use songbird::events::context_data::RtpData; 3 | 4 | pub fn rtp_packet(rtp_packet: &RtpData, guild_id: GuildId) { 5 | let rtp_packet = rtp_packet.rtp(); 6 | let ssrc = rtp_packet.get_ssrc(); 7 | let timestamp = rtp_packet.get_timestamp().0; 8 | let sequence = rtp_packet.get_sequence().0; 9 | 10 | trace!( 11 | %guild_id, 12 | %ssrc, 13 | "debug_log_audio_data: RTP packet data: sequence {}, timestamp {}", 14 | sequence, 15 | timestamp, 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /scripty_automod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_automod" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | stfu.workspace = true 14 | sqlx.workspace = true 15 | poise.workspace = true 16 | tracing.workspace = true 17 | 18 | scripty_db = { path = "../scripty_db" } 19 | scripty_premium = { path = "../scripty_premium" } 20 | -------------------------------------------------------------------------------- /scripty_automod/src/db.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{AutomodRule, AutomodServerConfig}; 2 | 3 | pub async fn get_guild_config(guild_id: u64) -> Result, sqlx::Error> { 4 | let db = scripty_db::get_db(); 5 | let Some(mut cfg) = sqlx::query!( 6 | "SELECT * FROM automod_config WHERE guild_id = $1", 7 | guild_id as i64 8 | ) 9 | .fetch_optional(db) 10 | .await? 11 | .map(|row| { 12 | AutomodServerConfig::new( 13 | row.guild_id as u64, 14 | row.item_id, 15 | row.enabled, 16 | vec![], 17 | vec![], 18 | row.log_channel_id as u64, 19 | row.log_recording, 20 | ) 21 | }) else { 22 | return Ok(None); 23 | }; 24 | 25 | // fetch rules 26 | // TODO: groups are currently not supported 27 | let res = sqlx::query!( 28 | "SELECT * FROM automod_rules WHERE source_id = $1", 29 | cfg.internal_id 30 | ) 31 | .fetch_all(db) 32 | .await?; 33 | 34 | for rule in res { 35 | cfg.add_rule(AutomodRule { 36 | rule_type: rule.rule_type.into(), 37 | rule_data: rule.rule_data, 38 | rule_action: rule.rule_action.into(), 39 | }); 40 | } 41 | Ok(Some(cfg)) 42 | } 43 | -------------------------------------------------------------------------------- /scripty_automod/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod db; 2 | pub mod types; 3 | pub mod utils; 4 | -------------------------------------------------------------------------------- /scripty_automod/src/utils.rs: -------------------------------------------------------------------------------- 1 | use scripty_premium::PremiumTierList; 2 | 3 | pub fn get_next_tier(current_tier: PremiumTierList) -> PremiumTierList { 4 | match current_tier { 5 | PremiumTierList::None => PremiumTierList::Tier1, 6 | PremiumTierList::Tier1 => PremiumTierList::Tier2, 7 | PremiumTierList::Tier2 => PremiumTierList::Tier3, 8 | PremiumTierList::Tier3 => PremiumTierList::Tier4, 9 | PremiumTierList::Tier4 => PremiumTierList::Tier5, 10 | PremiumTierList::Tier5 => PremiumTierList::Tier6, 11 | PremiumTierList::Tier6 => PremiumTierList::Tier6, 12 | } 13 | } 14 | 15 | pub fn get_tier_rule_count(tier: PremiumTierList) -> i64 { 16 | // the equation is roughly 500x^3 + 2500x^2 - 500x 17 | match tier { 18 | PremiumTierList::None => 25, 19 | PremiumTierList::Tier1 => 500, 20 | PremiumTierList::Tier2 => 5000, 21 | PremiumTierList::Tier3 => 16500, 22 | PremiumTierList::Tier4 => 38000, 23 | PremiumTierList::Tier5 => 72500, 24 | PremiumTierList::Tier6 => 123000, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripty_bot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_bot" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | sqlx.workspace = true 14 | tokio.workspace = true 15 | poise.workspace = true 16 | tracing.workspace = true 17 | serenity.workspace = true 18 | 19 | scripty_error = { path = "../scripty_error" } 20 | scripty_config = { path = "../scripty_config" } 21 | scripty_commands = { path = "../scripty_commands" } 22 | scripty_botlists = { path = "../scripty_botlists" } 23 | scripty_bot_utils = { path = "../scripty_bot_utils" } 24 | scripty_audio_handler = { path = "../scripty_audio_handler" } 25 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/available_language_autocomplete.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use serenity::builder::{AutocompleteChoice, AutocompleteValue, CreateAutocompleteResponse}; 4 | 5 | use crate::Context; 6 | 7 | pub async fn available_language_autocomplete<'a>( 8 | _: Context<'_>, 9 | partial: &'_ str, 10 | ) -> CreateAutocompleteResponse<'a> { 11 | let lm = scripty_i18n::get_language_map(); 12 | CreateAutocompleteResponse::new().set_choices( 13 | scripty_i18n::get_all_bundle_languages() 14 | .into_iter() 15 | .filter_map(move |lang| { 16 | let lang = lang.language.as_str(); 17 | let pretty = lm.get(lang)?; 18 | if lang.starts_with(partial) || pretty.starts_with(partial) { 19 | let name = Cow::Owned(format!("{} ({})", pretty.native, pretty.english)); 20 | let value = AutocompleteValue::String(lang.to_owned().into()); 21 | Some(AutocompleteChoice { 22 | name, 23 | name_localizations: None, 24 | value, 25 | }) 26 | } else { 27 | None 28 | } 29 | }) 30 | .take(25) // arbitrary new discord limitation 31 | .collect::>(), 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/background_tasks/mod.rs: -------------------------------------------------------------------------------- 1 | //! Background task management. 2 | //! 3 | //! How to use: 4 | //! 1) Create a background task struct in its own mod inside of mod tasks. 5 | //! 2) Implement `core::BackgroundTask` for the struct. 6 | //! 3) Call the macro `init_task!()` in `init_background_tasks`. The argument is the **full** path to the struct. 7 | 8 | mod core; 9 | mod tasks; 10 | 11 | pub use self::core::init_background_tasks; 12 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/background_tasks/tasks/basic_stats_update.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use scripty_error::Error; 4 | use scripty_metrics::Metrics; 5 | use serenity::gateway::client::Context; 6 | 7 | use crate::background_tasks::core::BackgroundTask; 8 | 9 | /// Updates bot stats in Prometheus every 20 seconds. 10 | pub struct BasicStatsUpdater(Arc, Context); 11 | 12 | impl BackgroundTask for BasicStatsUpdater { 13 | async fn init(ctx: Context) -> Result { 14 | Ok(BasicStatsUpdater(scripty_metrics::get_metrics(), ctx)) 15 | } 16 | 17 | fn interval(&mut self) -> Duration { 18 | Duration::from_secs(20) 19 | } 20 | 21 | async fn run(&mut self) { 22 | self.0.guilds.set(self.1.cache.guild_count() as i64); 23 | self.0.users.set( 24 | self.1 25 | .cache 26 | .guilds() 27 | .into_iter() 28 | .filter_map(|g| { 29 | g.to_guild_cached(&self.1.cache) 30 | .map(|g| g.member_count as i64) 31 | }) 32 | .sum(), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/background_tasks/tasks/cmd_latency_clear.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use scripty_error::Error; 4 | use serenity::gateway::client::Context; 5 | 6 | use crate::background_tasks::core::BackgroundTask; 7 | 8 | /// Clears stale latency metrics every 2 minutes to free up memory. 9 | pub struct CommandLatencyClearer; 10 | 11 | impl BackgroundTask for CommandLatencyClearer { 12 | async fn init(_: Context) -> Result { 13 | Ok(Self) 14 | } 15 | 16 | fn interval(&mut self) -> Duration { 17 | Duration::from_secs(120) 18 | } 19 | 20 | async fn run(&mut self) { 21 | scripty_metrics::clear_latency_start_times(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/background_tasks/tasks/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic_stats_update; 2 | mod bot_list_poster; 3 | mod bot_vote_reminder; 4 | mod cmd_latency_clear; 5 | mod prometheus_latency_update; 6 | mod status_update; 7 | 8 | pub use basic_stats_update::*; 9 | pub use bot_list_poster::*; 10 | pub use bot_vote_reminder::*; 11 | pub use cmd_latency_clear::*; 12 | pub use prometheus_latency_update::*; 13 | pub use status_update::*; 14 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/error/mod.rs: -------------------------------------------------------------------------------- 1 | // pub(super) mod error_type; 2 | pub mod handler; 3 | mod message; 4 | 5 | pub use message::log_error_message; 6 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/file_transcripts/consts.rs: -------------------------------------------------------------------------------- 1 | pub const AUDIO_EXTENSIONS: [&str; 14] = [ 2 | "3gp", "aac", "aiff", "alac", "flac", "m4a", "m4b", "mp3", "ogg", "oga", "mogg", "opus", "wav", 3 | "webm", 4 | ]; 5 | pub const VIDEO_EXTENSIONS: [&str; 4] = ["mp4", "mov", "webm", "mkv"]; 6 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/globals.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use once_cell::sync::OnceCell; 4 | use serenity::cache::Cache; 5 | 6 | use crate::{Data, dm_support::DmSupportStatus}; 7 | 8 | pub static CLIENT_CACHE: OnceCell> = OnceCell::new(); 9 | pub static CLIENT_DATA: OnceCell> = OnceCell::new(); 10 | pub static DM_SUPPORT_GLOBAL: OnceCell = OnceCell::new(); 11 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | mod normal; 2 | mod post_command; 3 | mod pre_command; 4 | mod ratelimit; 5 | mod raw; 6 | 7 | pub use normal::EventHandler; 8 | pub use post_command::post_command; 9 | pub use pre_command::pre_command; 10 | pub use ratelimit::ratelimit; 11 | pub use raw::RawEventHandler; 12 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/normal/cache.rs: -------------------------------------------------------------------------------- 1 | use serenity::all::{Context, GuildId}; 2 | 3 | pub(crate) async fn cache_ready(_ctx: &Context, guilds: &[GuildId]) { 4 | info!("cache ready, got {} guilds", guilds.len()); 5 | } 6 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/normal/message.rs: -------------------------------------------------------------------------------- 1 | use serenity::{gateway::client::Context, model::prelude::Message}; 2 | 3 | use crate::{ 4 | file_transcripts::{MessageUpdater, transcribe_generic_message}, 5 | globals::DM_SUPPORT_GLOBAL, 6 | }; 7 | 8 | pub async fn message(ctx: &Context, msg: &Message) { 9 | tokio::spawn(scripty_data_storage::ingest_message(msg.clone())); 10 | if let Some(st) = DM_SUPPORT_GLOBAL.get() { 11 | tokio::spawn(st.handle_message(ctx.clone(), msg.clone())); 12 | } 13 | 14 | let ctx2 = ctx.clone(); 15 | let msg2 = msg.clone(); 16 | tokio::spawn(async move { 17 | let channel_id = msg2.channel_id; 18 | let reference_id = msg2.id; 19 | 20 | let resolved_language = scripty_i18n::get_resolved_language( 21 | msg2.author.id.get(), 22 | msg2.guild_id.map(|g| g.get()), 23 | ) 24 | .await; 25 | 26 | if let Err(e) = transcribe_generic_message( 27 | msg2, 28 | MessageUpdater::from((ctx2, (channel_id, reference_id))), 29 | None, 30 | resolved_language, 31 | ) 32 | .await 33 | { 34 | error!(msg_id = %reference_id, "failed to transcribe generic message: {}", e); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/normal/mod.rs: -------------------------------------------------------------------------------- 1 | mod cache; 2 | mod message; 3 | mod ready; 4 | mod resume; 5 | mod shards; 6 | mod voice_state_update; 7 | 8 | use serenity::gateway::client::{Context, EventHandler as SerenityEventHandler, FullEvent}; 9 | 10 | pub struct EventHandler; 11 | 12 | #[async_trait] 13 | impl SerenityEventHandler for EventHandler { 14 | async fn dispatch(&self, ctx: &Context, event: &FullEvent) { 15 | match event { 16 | FullEvent::CacheReady { guilds, .. } => cache::cache_ready(ctx, guilds).await, 17 | FullEvent::ShardsReady { total_shards, .. } => { 18 | shards::shards_ready(ctx, total_shards).await 19 | } 20 | FullEvent::Message { 21 | new_message: msg, .. 22 | } => message::message(ctx, msg).await, 23 | FullEvent::Ready { 24 | data_about_bot: ready, 25 | .. 26 | } => ready::ready(ctx, ready).await, 27 | FullEvent::Resume { .. } => resume::resume(ctx).await, 28 | FullEvent::ShardStageUpdate { event, .. } => { 29 | shards::shard_stage_update(ctx, event).await 30 | } 31 | FullEvent::VoiceStateUpdate { new, .. } => { 32 | voice_state_update::voice_state_update(ctx, new).await 33 | } 34 | _ => {} 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/normal/ready.rs: -------------------------------------------------------------------------------- 1 | use serenity::{all::ShardInfo, gateway::client::Context, model::prelude::Ready}; 2 | 3 | use crate::{ 4 | dm_support::DmSupportStatus, 5 | extern_utils::set_cache_http, 6 | globals::{CLIENT_CACHE, DM_SUPPORT_GLOBAL}, 7 | }; 8 | 9 | pub async fn ready( 10 | ctx: &Context, 11 | Ready { 12 | version, 13 | user, 14 | guilds, 15 | shard, 16 | .. 17 | }: &Ready, 18 | ) { 19 | set_cache_http(ctx.http.clone(), ctx.cache.clone()); 20 | 21 | let _ = CLIENT_CACHE.set(ctx.cache.clone()); 22 | 23 | let dm_support = DmSupportStatus::new(); 24 | let _ = DM_SUPPORT_GLOBAL.set(dm_support); 25 | 26 | if let Some(ShardInfo { id, total }) = shard { 27 | info!( 28 | "shard {} of {} ready: logged in as {}, in {} guilds, using API version {}", 29 | id, 30 | total, 31 | user.tag(), 32 | guilds.len(), 33 | version 34 | ); 35 | } else { 36 | info!( 37 | "bot ready: logged in as {}, in {} guilds, using API version {}", 38 | user.tag(), 39 | guilds.len(), 40 | version 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/normal/resume.rs: -------------------------------------------------------------------------------- 1 | use serenity::gateway::client::Context; 2 | 3 | pub async fn resume(_: &Context) { 4 | info!("successfully resumed"); 5 | } 6 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/normal/shards.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU16; 2 | 3 | use serenity::all::{Context, ShardStageUpdateEvent}; 4 | 5 | pub async fn shards_ready(ctx: &Context, total_shards: &NonZeroU16) { 6 | info!( 7 | "all {} shards have received ready event", 8 | total_shards.get() 9 | ); 10 | 11 | crate::background_tasks::init_background_tasks(ctx.clone()); 12 | } 13 | 14 | pub async fn shard_stage_update( 15 | _: &Context, 16 | ShardStageUpdateEvent { new, old, shard_id }: &ShardStageUpdateEvent, 17 | ) { 18 | info!(%shard_id, "shard {} transitioned from {} to {}", shard_id, old, new); 19 | } 20 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/post_command.rs: -------------------------------------------------------------------------------- 1 | use poise::BoxFuture; 2 | 3 | async fn _post_command(_: crate::Context<'_>) { 4 | // now unused 5 | } 6 | 7 | pub fn post_command(ctx: crate::Context<'_>) -> BoxFuture<()> { 8 | Box::pin(_post_command(ctx)) 9 | } 10 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/pre_command.rs: -------------------------------------------------------------------------------- 1 | use poise::BoxFuture; 2 | 3 | use crate::{Context, types::InvocationData}; 4 | 5 | async fn _pre_command(ctx: Context<'_>) { 6 | scripty_metrics::measure_end_latency(ctx.id()); 7 | 8 | let metrics = scripty_metrics::get_metrics(); 9 | metrics.total_commands.inc(); 10 | match metrics 11 | .commands 12 | .get_metric_with_label_values(&[&ctx.command().qualified_name]) 13 | { 14 | Ok(cmd_counter) => cmd_counter.inc(), 15 | Err(e) => { 16 | error!( 17 | "invalid number of arguments passed to get_metric_with_label_values: {}", 18 | e 19 | ); 20 | } 21 | }; 22 | 23 | match ctx { 24 | Context::Prefix(..) => { 25 | metrics.command_usage.prefix.inc(); 26 | } 27 | Context::Application(..) => { 28 | metrics.command_usage.slash.inc(); 29 | } 30 | } 31 | 32 | let invocation_data = InvocationData { 33 | resolved_language: scripty_i18n::get_resolved_language( 34 | ctx.author().id.get(), 35 | ctx.guild_id().map(|g| g.get()), 36 | ) 37 | .await, 38 | }; 39 | ctx.set_invocation_data(invocation_data).await; 40 | } 41 | 42 | pub fn pre_command(ctx: Context) -> BoxFuture<()> { 43 | debug!("pre_command"); 44 | Box::pin(_pre_command(ctx)) 45 | } 46 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/handler/ratelimit.rs: -------------------------------------------------------------------------------- 1 | use serenity::http::RatelimitInfo; 2 | 3 | pub fn ratelimit( 4 | RatelimitInfo { 5 | timeout, 6 | limit, 7 | method, 8 | path, 9 | global, 10 | .. 11 | }: RatelimitInfo, 12 | ) { 13 | let method = method.reqwest_method(); 14 | warn!( 15 | "Ratelimited! Timeout: {:?}s, Limit: {}, Method: {}, Path: {}, Global: {}", 16 | timeout, limit, method, path, global 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::expect_used, clippy::unwrap_used)] 2 | #![feature(let_chains)] 3 | #[macro_use] 4 | extern crate tracing; 5 | #[macro_use] 6 | extern crate scripty_i18n; 7 | #[macro_use] 8 | extern crate async_trait; 9 | extern crate core; 10 | 11 | mod available_language_autocomplete; 12 | pub mod background_tasks; 13 | pub mod dm_support; 14 | pub mod entity_block; 15 | pub mod error; 16 | pub mod extern_utils; 17 | pub mod file_transcripts; 18 | pub mod globals; 19 | pub mod handler; 20 | pub mod prefix_handling; 21 | pub mod types; 22 | 23 | pub use available_language_autocomplete::available_language_autocomplete; 24 | pub use scripty_data_type::Data; 25 | pub use types::Context; 26 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/types/ctx.rs: -------------------------------------------------------------------------------- 1 | use scripty_error::Error; 2 | 3 | use crate::Data; 4 | 5 | pub type Context<'a> = poise::Context<'a, Data, Error>; 6 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/types/invocation_data.rs: -------------------------------------------------------------------------------- 1 | use scripty_i18n::LanguageIdentifier; 2 | 3 | pub struct InvocationData { 4 | pub resolved_language: LanguageIdentifier, 5 | } 6 | -------------------------------------------------------------------------------- /scripty_bot_utils/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod ctx; 2 | mod invocation_data; 3 | mod lang; 4 | 5 | pub use ctx::Context; 6 | pub use invocation_data::InvocationData; 7 | pub use lang::{Language, LanguageInvalid}; 8 | -------------------------------------------------------------------------------- /scripty_botlists/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_botlists" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | serde.workspace = true 14 | reqwest.workspace = true 15 | tracing.workspace = true 16 | serde-aux.workspace = true 17 | serde_json.workspace = true 18 | async-trait.workspace = true 19 | serde_derive.workspace = true 20 | 21 | scripty_config = { path = "../scripty_config" } 22 | -------------------------------------------------------------------------------- /scripty_botlists/src/common.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use reqwest::{Client, Error as ReqwestError, StatusCode}; 4 | 5 | #[async_trait] 6 | pub trait StatPoster { 7 | async fn post_stats(&self, client: &Client, stats: PostStats) -> Result; 8 | } 9 | 10 | #[derive(Debug, Copy, Clone)] 11 | pub struct PostStats { 12 | pub server_count: usize, 13 | pub shard_count: u16, 14 | } 15 | 16 | #[derive(Deserialize, Serialize, Debug, Copy, Clone)] 17 | #[serde(transparent)] 18 | pub struct UserId( 19 | #[serde(deserialize_with = "serde_aux::field_attributes::deserialize_number_from_string")] 20 | pub u64, 21 | ); 22 | 23 | #[derive(Debug)] 24 | pub enum Error { 25 | Reqwest(ReqwestError), 26 | Json(serde_json::Error), 27 | StatusCode(StatusCode), 28 | } 29 | 30 | impl From for Error { 31 | fn from(error: ReqwestError) -> Self { 32 | Self::Reqwest(error) 33 | } 34 | } 35 | 36 | impl From for Error { 37 | fn from(error: serde_json::Error) -> Self { 38 | Self::Json(error) 39 | } 40 | } 41 | 42 | impl fmt::Display for Error { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | match self { 45 | Error::Reqwest(e) => write!(f, "Reqwest error: {}", e), 46 | Error::Json(e) => write!(f, "JSON error: {}", e), 47 | Error::StatusCode(e) => write!(f, "Status code error: {}", e), 48 | } 49 | } 50 | } 51 | 52 | impl std::error::Error for Error {} 53 | -------------------------------------------------------------------------------- /scripty_botlists/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | #[macro_use] 4 | extern crate async_trait; 5 | #[macro_use] 6 | extern crate tracing; 7 | 8 | mod common; 9 | mod lists; 10 | 11 | pub use common::{Error, PostStats, StatPoster, UserId}; 12 | pub use lists::*; 13 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/botlist_me/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use trait_impl::BotListMe; 5 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/botlist_me/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Serialize)] 2 | pub struct PostStats { 3 | pub server_count: usize, 4 | pub shard_count: u16, 5 | } 6 | 7 | #[derive(Debug, Deserialize, Copy, Clone)] 8 | pub struct PostStatsResponse { 9 | pub error: bool, 10 | } 11 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discord_bots_gg/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use trait_impl::DiscordBotsGG; 5 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discord_bots_gg/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Debug, Copy, Clone)] 2 | #[serde(rename_all = "camelCase")] 3 | pub struct PostStats { 4 | pub server_count: usize, 5 | pub shard_count: u16, 6 | } 7 | 8 | #[derive(Deserialize, Debug, Clone)] 9 | #[allow(dead_code)] 10 | pub struct PostStatsResponse { 11 | pub code: u16, 12 | pub message: String, 13 | } 14 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discord_bots_gg/trait_impl.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{Client, RequestBuilder}; 2 | 3 | use crate::common::{PostStats, StatPoster}; 4 | 5 | pub struct DiscordBotsGG { 6 | token: String, 7 | bot_id: u64, 8 | } 9 | 10 | impl DiscordBotsGG { 11 | pub fn new(token: String, bot_id: u64) -> Self { 12 | Self { token, bot_id } 13 | } 14 | 15 | pub fn token(&self) -> &str { 16 | &self.token 17 | } 18 | 19 | pub fn bot_id(&self) -> u64 { 20 | self.bot_id 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl StatPoster for DiscordBotsGG { 26 | async fn post_stats( 27 | &self, 28 | client: &Client, 29 | stats: PostStats, 30 | ) -> Result { 31 | let request: RequestBuilder = client 32 | .post(format!( 33 | "https://discord.bots.gg/api/v1/bots/{}/stats", 34 | self.bot_id 35 | )) 36 | .header("Authorization", &self.token) 37 | .json(&super::models::PostStats { 38 | server_count: stats.server_count, 39 | shard_count: stats.shard_count, 40 | }); 41 | let response = request.send().await?; 42 | debug!("discord.bots.gg response: {:?}", response); 43 | let status = response.status(); 44 | debug!( 45 | "discord.bots.gg response body: <{}>", 46 | response.text().await? 47 | ); 48 | Ok(status == reqwest::StatusCode::OK) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discordbotlist_com/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use trait_impl::DiscordBotListCom; 5 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discordbotlist_com/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Debug, Copy, Clone)] 2 | pub struct PostStats { 3 | pub guilds: usize, 4 | } 5 | 6 | #[derive(Deserialize, Debug, Clone)] 7 | pub struct PostStatsResponse { 8 | pub success: bool, 9 | } 10 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discordbotlist_com/trait_impl.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{Client, RequestBuilder}; 2 | 3 | use crate::common::{PostStats, StatPoster}; 4 | 5 | pub struct DiscordBotListCom { 6 | token: String, 7 | bot_id: u64, 8 | } 9 | 10 | impl DiscordBotListCom { 11 | pub fn new(token: String, bot_id: u64) -> Self { 12 | Self { token, bot_id } 13 | } 14 | 15 | pub fn token(&self) -> &str { 16 | &self.token 17 | } 18 | 19 | pub fn bot_id(&self) -> u64 { 20 | self.bot_id 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl StatPoster for DiscordBotListCom { 26 | async fn post_stats( 27 | &self, 28 | client: &Client, 29 | stats: PostStats, 30 | ) -> Result { 31 | let request: RequestBuilder = client 32 | .post(format!( 33 | "https://discordbotlist.com/api/v1/bots/{}/stats", 34 | self.bot_id 35 | )) 36 | .header("Authorization", &self.token) 37 | .json(&super::models::PostStats { 38 | guilds: stats.server_count, 39 | }); 40 | let response = request.send().await?; 41 | debug!("discordbotlist.com response: {:?}", response); 42 | let status = response.status(); 43 | let maybe_error = if status.is_client_error() || status.is_server_error() { 44 | Some(crate::common::Error::StatusCode(status)) 45 | } else { 46 | None 47 | }; 48 | let body = response.text().await?; 49 | debug!("discordbotlist.com response body: <{}>", body); 50 | if let Some(maybe_error) = maybe_error { 51 | return Err(maybe_error); 52 | } 53 | let body: super::models::PostStatsResponse = serde_json::from_str(&body)?; 54 | Ok(body.success) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discordextremelist_xyz/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use trait_impl::DiscordExtremeListXyz; 5 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discordextremelist_xyz/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Debug, Copy, Clone)] 2 | #[serde(rename_all = "camelCase")] 3 | pub struct PostStats { 4 | pub server_count: usize, 5 | pub shard_count: u16, 6 | } 7 | 8 | #[derive(Deserialize, Debug, Clone)] 9 | #[allow(dead_code)] 10 | pub struct PostStatsResponse { 11 | pub error: bool, 12 | pub status: u16, 13 | } 14 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discords_com/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | pub use trait_impl::DiscordsCom; 4 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discords_com/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Serialize)] 2 | pub struct PostStats { 3 | pub server_count: usize, 4 | } 5 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discords_com/trait_impl.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{Client, RequestBuilder}; 2 | 3 | use crate::common::{PostStats, StatPoster}; 4 | 5 | pub struct DiscordsCom { 6 | token: String, 7 | bot_id: u64, 8 | } 9 | 10 | impl DiscordsCom { 11 | pub fn new(token: String, bot_id: u64) -> Self { 12 | Self { token, bot_id } 13 | } 14 | 15 | pub fn token(&self) -> &str { 16 | &self.token 17 | } 18 | 19 | pub fn bot_id(&self) -> u64 { 20 | self.bot_id 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl StatPoster for DiscordsCom { 26 | async fn post_stats( 27 | &self, 28 | client: &Client, 29 | stats: PostStats, 30 | ) -> Result { 31 | let request: RequestBuilder = client 32 | .post(format!("https://discords.com/bots/api/bot/{}", self.bot_id)) 33 | .header("Authorization", &self.token) 34 | .json(&super::models::PostStats { 35 | server_count: stats.server_count, 36 | }); 37 | let response = request.send().await?; 38 | debug!("discords.com response: {:?}", response); 39 | let status = response.status(); 40 | let maybe_error = if status.is_client_error() || status.is_server_error() { 41 | Some(crate::common::Error::StatusCode(status)) 42 | } else { 43 | None 44 | }; 45 | let body = response.text().await?; 46 | debug!("discords.com response body: <{}>", body); 47 | if let Some(maybe_error) = maybe_error { 48 | return Err(maybe_error); 49 | } 50 | Ok(status == reqwest::StatusCode::OK) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discordservices_net/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use models::*; 5 | pub use trait_impl::DiscordServicesNet; 6 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/discordservices_net/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Debug, Copy, Clone)] 2 | pub struct PostStats { 3 | pub servers: usize, 4 | pub shards: u16, 5 | } 6 | 7 | #[derive(Deserialize, Debug, Clone)] 8 | pub struct PostStatsResponse { 9 | pub code: u16, 10 | pub message: String, 11 | } 12 | 13 | #[derive(Serialize, Deserialize)] 14 | pub struct DiscordServicesNetIncomingWebhook { 15 | pub bot: Bot, 16 | pub user: Bot, 17 | } 18 | 19 | #[derive(Serialize, Deserialize)] 20 | #[serde(rename_all = "camelCase")] 21 | pub struct Bot { 22 | #[serde(deserialize_with = "serde_aux::field_attributes::deserialize_number_from_string")] 23 | pub id: u64, 24 | pub name: String, 25 | #[serde(rename = "discrim")] 26 | pub discriminator: Option, 27 | pub avatar_id: String, 28 | } 29 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/disforge_com/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use trait_impl::DisforgeCom; 5 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/disforge_com/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Debug, Copy, Clone)] 2 | pub struct PostStats { 3 | pub servers: usize, 4 | } 5 | 6 | #[derive(Deserialize, Debug, Clone)] 7 | #[allow(dead_code)] 8 | pub struct PostStatsResponse { 9 | pub status: String, 10 | pub message: String, 11 | } 12 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/infinitybots_gg/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | pub use trait_impl::InfinityBotsGG; 4 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/infinitybots_gg/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Debug, Copy, Clone)] 2 | pub struct PostStats { 3 | pub servers: usize, 4 | pub shards: u16, 5 | } 6 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/infinitybots_gg/trait_impl.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{Client, RequestBuilder}; 2 | 3 | use crate::common::{PostStats, StatPoster}; 4 | 5 | pub struct InfinityBotsGG { 6 | token: String, 7 | bot_id: u64, 8 | } 9 | 10 | impl InfinityBotsGG { 11 | pub fn new(token: String, bot_id: u64) -> Self { 12 | Self { token, bot_id } 13 | } 14 | 15 | pub fn token(&self) -> &str { 16 | &self.token 17 | } 18 | 19 | pub fn bot_id(&self) -> u64 { 20 | self.bot_id 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl StatPoster for InfinityBotsGG { 26 | async fn post_stats( 27 | &self, 28 | client: &Client, 29 | stats: PostStats, 30 | ) -> Result { 31 | let request: RequestBuilder = client 32 | .post("https://spider.infinitybots.gg/bots/stats") 33 | .header("Authorization", &self.token) 34 | .json(&super::models::PostStats { 35 | servers: stats.server_count, 36 | shards: stats.shard_count, 37 | }); 38 | let response = request.send().await?; 39 | debug!("infinitybots.gg response: {:?}", response); 40 | let status = response.status(); 41 | let maybe_error = if status.is_client_error() || status.is_server_error() { 42 | Some(crate::common::Error::StatusCode(status)) 43 | } else { 44 | None 45 | }; 46 | let body = response.text().await?; 47 | debug!("infinitybots.gg response body: <{}>", body); 48 | if let Some(maybe_error) = maybe_error { 49 | return Err(maybe_error); 50 | } 51 | Ok(status == reqwest::StatusCode::NO_CONTENT) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod botlist_me; 2 | pub mod discord_bots_gg; 3 | pub mod discordbotlist_com; 4 | pub mod discordextremelist_xyz; 5 | pub mod discords_com; 6 | pub mod discordservices_net; 7 | pub mod disforge_com; 8 | pub mod infinitybots_gg; 9 | pub mod top_gg; 10 | pub mod voidbots_net; 11 | pub mod wumpus_store; 12 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/top_gg/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use models::*; 5 | pub use trait_impl::TopGG; 6 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/top_gg/models.rs: -------------------------------------------------------------------------------- 1 | use serde_aux::field_attributes::deserialize_number_from_string; 2 | 3 | #[derive(Debug, Serialize, Copy, Clone)] 4 | pub struct PostStats { 5 | pub server_count: usize, 6 | pub shard_count: u16, 7 | } 8 | 9 | #[derive(Debug, Deserialize, Copy, Clone)] 10 | pub struct IncomingWebhook { 11 | #[serde(deserialize_with = "deserialize_number_from_string")] 12 | pub bot: u64, 13 | 14 | #[serde(deserialize_with = "deserialize_number_from_string")] 15 | pub user: u64, 16 | 17 | #[serde(rename = "type")] 18 | pub kind: VoteWebhookType, 19 | 20 | #[serde(default)] // idiots at top.gg don't know how to make a proper webhook 21 | pub is_weekend: bool, 22 | } 23 | 24 | #[derive(Debug, Deserialize, Copy, Clone, Eq, PartialEq)] 25 | #[serde(rename_all = "snake_case")] 26 | pub enum VoteWebhookType { 27 | Upvote, 28 | Test, 29 | } 30 | impl VoteWebhookType { 31 | pub fn is_upvote(self) -> bool { 32 | self == Self::Upvote 33 | } 34 | 35 | pub fn is_test(self) -> bool { 36 | self == Self::Test 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/top_gg/trait_impl.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{Client, RequestBuilder, StatusCode}; 2 | 3 | use crate::common::{PostStats, StatPoster}; 4 | 5 | pub struct TopGG { 6 | token: String, 7 | bot_id: u64, 8 | } 9 | 10 | impl TopGG { 11 | pub fn new(token: String, bot_id: u64) -> Self { 12 | Self { token, bot_id } 13 | } 14 | 15 | pub fn token(&self) -> &str { 16 | &self.token 17 | } 18 | 19 | pub fn bot_id(&self) -> u64 { 20 | self.bot_id 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl StatPoster for TopGG { 26 | async fn post_stats( 27 | &self, 28 | client: &Client, 29 | stats: PostStats, 30 | ) -> Result { 31 | let request: RequestBuilder = client 32 | .post(format!("https://top.gg/api/bots/{}/stats", self.bot_id)) 33 | .header("Authorization", &self.token) 34 | .json(&super::models::PostStats { 35 | server_count: stats.server_count, 36 | shard_count: stats.shard_count, 37 | }); 38 | let response = request.send().await?; 39 | debug!("top.gg response: {:?}", response); 40 | let status = response.status(); 41 | let maybe_error = if status.is_client_error() || status.is_server_error() { 42 | Some(crate::common::Error::StatusCode(status)) 43 | } else { 44 | None 45 | }; 46 | let body = response.text().await?; 47 | debug!("top.gg response body: <{}>", body); 48 | if let Some(maybe_error) = maybe_error { 49 | return Err(maybe_error); 50 | } 51 | Ok(status == StatusCode::OK) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/voidbots_net/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod trait_impl; 3 | 4 | pub use trait_impl::VoidBotsNet; 5 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/voidbots_net/models.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Serialize)] 2 | pub struct PostStats { 3 | pub server_count: usize, 4 | pub shard_count: u16, 5 | } 6 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/voidbots_net/trait_impl.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{Client, RequestBuilder, StatusCode}; 2 | 3 | use crate::common::{PostStats, StatPoster}; 4 | 5 | pub struct VoidBotsNet { 6 | token: String, 7 | bot_id: u64, 8 | } 9 | 10 | impl VoidBotsNet { 11 | pub fn new(token: String, bot_id: u64) -> Self { 12 | Self { token, bot_id } 13 | } 14 | 15 | pub fn token(&self) -> &str { 16 | &self.token 17 | } 18 | 19 | pub fn bot_id(&self) -> u64 { 20 | self.bot_id 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl StatPoster for VoidBotsNet { 26 | async fn post_stats( 27 | &self, 28 | client: &Client, 29 | stats: PostStats, 30 | ) -> Result { 31 | let request: RequestBuilder = client 32 | .post(format!( 33 | "https://api.voidbots.net/bot/stats/{}", 34 | self.bot_id 35 | )) 36 | .header("Authorization", &self.token) 37 | .json(&super::models::PostStats { 38 | server_count: stats.server_count, 39 | shard_count: stats.shard_count, 40 | }); 41 | let response = request.send().await?; 42 | debug!("voidbots.net response: {:?}", response); 43 | let status = response.status(); 44 | let maybe_error = if status.is_client_error() || status.is_server_error() { 45 | Some(crate::common::Error::StatusCode(status)) 46 | } else { 47 | None 48 | }; 49 | let body = response.text().await?; 50 | debug!("top.gg response body: <{}>", body); 51 | if let Some(maybe_error) = maybe_error { 52 | return Err(maybe_error); 53 | } 54 | Ok(status == StatusCode::OK) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/wumpus_store/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | 3 | pub use models::*; 4 | -------------------------------------------------------------------------------- /scripty_botlists/src/lists/wumpus_store/models.rs: -------------------------------------------------------------------------------- 1 | use serde_aux::field_attributes::deserialize_number_from_string; 2 | 3 | #[derive(Debug, Deserialize, Copy, Clone)] 4 | #[serde(rename_all = "camelCase")] 5 | pub struct IncomingWebhook { 6 | pub webhook_test: bool, 7 | 8 | #[serde(deserialize_with = "deserialize_number_from_string")] 9 | pub user_id: u64, 10 | #[serde(deserialize_with = "deserialize_number_from_string")] 11 | pub bot_id: u64, 12 | } 13 | -------------------------------------------------------------------------------- /scripty_build_checks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_build_checks" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /scripty_build_checks/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[cfg(target_os = "windows")] 4 | compile_error!( 5 | "\ 6 | Scripty *will not* run on Windows whatsoever. Don't even try.\nNote: one of our downstream \ 7 | dependencies also has a chance to segfault on Windows." 8 | ); 9 | 10 | #[cfg(all(not(target_os = "linux"), not(ignore_os)))] 11 | compile_error!( 12 | "Scripty is only designed for Linux. It may not run at all on other platforms. If you'd like \ 13 | to try anyway, enable the `--cfg ignore_os` flag in RUSTFLAGS." 14 | ); 15 | -------------------------------------------------------------------------------- /scripty_commands/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_commands" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | hex.workspace = true 14 | rand.workspace = true 15 | sqlx.workspace = true 16 | tokio.workspace = true 17 | poise.workspace = true 18 | tracing.workspace = true 19 | indexmap.workspace = true 20 | serenity.workspace = true 21 | humantime.workspace = true 22 | typesize.workspace = true 23 | num-format.workspace = true 24 | 25 | scripty_db = { path = "../scripty_db" } 26 | scripty_i18n = { path = "../scripty_i18n" } 27 | scripty_error = { path = "../scripty_error" } 28 | scripty_utils = { path = "../scripty_utils" } 29 | scripty_redis = { path = "../scripty_redis" } 30 | scripty_config = { path = "../scripty_config" } 31 | scripty_automod = { path = "../scripty_automod" } 32 | scripty_premium = { path = "../scripty_premium" } 33 | scripty_bot_utils = { path = "../scripty_bot_utils" } 34 | scripty_data_storage = { path = "../scripty_data_storage" } 35 | scripty_integrations = { path = "../scripty_integrations" } 36 | scripty_audio_handler = { path = "../scripty_audio_handler" } 37 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/admin/hash_user_id.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Error}; 2 | 3 | #[poise::command(prefix_command, hide_in_help, owners_only)] 4 | pub async fn hash_user_id(ctx: Context<'_>, uid: u64) -> Result<(), Error> { 5 | ctx.say(hex::encode(scripty_utils::hash_user_id(uid))) 6 | .await?; 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/admin/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Error}; 2 | 3 | mod cache_info; 4 | mod guild_check; 5 | mod hash_user_id; 6 | mod shutdown; 7 | 8 | #[poise::command( 9 | prefix_command, 10 | hide_in_help, 11 | owners_only, 12 | rename = "admin", 13 | subcommands( 14 | "cache_info::cache_info", 15 | "guild_check::check_guilds", 16 | "hash_user_id::hash_user_id", 17 | "shutdown::shutdown" 18 | ), 19 | subcommand_required 20 | )] 21 | pub async fn admin_root(ctx: Context<'_>) -> Result<(), Error> { 22 | ctx.say( 23 | "don't use the root command, use the subcommands that you should remember since you're \ 24 | the owner of the bot", 25 | ) 26 | .await?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/automod/mod.rs: -------------------------------------------------------------------------------- 1 | mod add_rule; 2 | mod list_rules; 3 | mod remove_rule; 4 | mod setup; 5 | 6 | use crate::{Context, Error}; 7 | 8 | /// Manage Scripty's automod. 9 | /// 10 | /// Does nothing, instead check out the sub-commands of this command. 11 | #[poise::command( 12 | prefix_command, 13 | slash_command, 14 | guild_only, 15 | rename = "automod", 16 | subcommands( 17 | "add_rule::automod_add_rule", 18 | "list_rules::automod_list_rules", 19 | "remove_rule::automod_remove_rule", 20 | "setup::automod_setup" 21 | ), 22 | subcommand_required 23 | )] 24 | pub async fn automod_root(ctx: Context<'_>) -> Result<(), Error> { 25 | let resolved_language = 26 | scripty_i18n::get_resolved_language(ctx.author().id.get(), ctx.guild_id().map(|g| g.get())) 27 | .await; 28 | 29 | ctx.say( 30 | format_message!(resolved_language, "automod-root-response", contextPrefix: ctx.prefix()), 31 | ) 32 | .await?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/config/default_settings/record_transcriptions.rs: -------------------------------------------------------------------------------- 1 | use scripty_bot_utils::Context; 2 | use scripty_error::Error; 3 | 4 | /// Should Scripty, by default, record all transcriptions to a text file? 5 | #[poise::command( 6 | prefix_command, 7 | slash_command, 8 | guild_only, 9 | required_permissions = "MANAGE_GUILD", 10 | rename = "record_transcriptions" 11 | )] 12 | pub async fn config_default_settings_record_transcriptions( 13 | ctx: Context<'_>, 14 | record_transcriptions: bool, 15 | ) -> Result<(), Error> { 16 | let guild_id = ctx.guild_id().ok_or_else(Error::expected_guild)?; 17 | let resolved_language = 18 | scripty_i18n::get_resolved_language(ctx.author().id.get(), Some(guild_id.get())).await; 19 | let db = scripty_db::get_db(); 20 | 21 | super::ensure_guild_exists(guild_id, db).await?; 22 | sqlx::query!( 23 | "INSERT INTO default_join_settings (guild_id, record_transcriptions) 24 | VALUES ($1, $2) 25 | ON CONFLICT ON CONSTRAINT default_join_settings_pkey 26 | DO UPDATE SET record_transcriptions = $2", 27 | guild_id.get() as i64, 28 | record_transcriptions 29 | ) 30 | .execute(db) 31 | .await?; 32 | 33 | ctx.say(format_message!( 34 | resolved_language, 35 | if record_transcriptions { 36 | "config-default-record-transcriptions-enabled" 37 | } else { 38 | "config-default-record-transcriptions-disabled" 39 | } 40 | )) 41 | .await?; 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/config/kiai_enabled.rs: -------------------------------------------------------------------------------- 1 | use scripty_bot_utils::Context; 2 | use scripty_error::Error; 3 | use scripty_integrations::kiai::{Permissions as KiaiPermissions, get_kiai_api_client}; 4 | 5 | /// Enable Scripty's Kiai integration. You should disable Kiai's voice XP levelling if you use this. 6 | #[poise::command( 7 | prefix_command, 8 | slash_command, 9 | guild_only, 10 | required_permissions = "MANAGE_GUILD", 11 | rename = "enable_kiai" 12 | )] 13 | pub async fn config_enable_kiai(ctx: Context<'_>, enable_kiai: Option) -> Result<(), Error> { 14 | let guild_id = ctx 15 | .guild_id() 16 | .map(|g| g.get()) 17 | .ok_or_else(Error::expected_guild)?; 18 | let resolved_language = 19 | scripty_i18n::get_resolved_language(ctx.author().id.get(), Some(guild_id)).await; 20 | 21 | let i18n_string = if let Some(enable_kiai) = enable_kiai { 22 | let kc = get_kiai_api_client(); 23 | let perms = kc.get_permissions(guild_id).await?; 24 | if perms.contains(KiaiPermissions::LEVELS) { 25 | sqlx::query!( 26 | "INSERT INTO guilds (guild_id, kiai_enabled) VALUES ($1, $2) ON CONFLICT \ 27 | (guild_id) DO UPDATE SET kiai_enabled = $2", 28 | guild_id as i64, 29 | enable_kiai 30 | ) 31 | .execute(scripty_db::get_db()) 32 | .await?; 33 | 34 | if enable_kiai { 35 | "config-kiai-enabled" 36 | } else { 37 | "config-kiai-disabled" 38 | } 39 | } else { 40 | "config-kiai-missing-perms" 41 | } 42 | } else { 43 | "config-kiai-info" 44 | }; 45 | 46 | ctx.say(format_message!(resolved_language, i18n_string)) 47 | .await?; 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/config/transcribe_audio.rs: -------------------------------------------------------------------------------- 1 | use scripty_bot_utils::Context; 2 | use scripty_error::Error; 3 | 4 | /// Toggle whether Scripty transcribes arbitrary audio files posted. Requires premium. 5 | #[poise::command( 6 | prefix_command, 7 | slash_command, 8 | guild_only, 9 | required_permissions = "MANAGE_GUILD", 10 | rename = "transcribe_audio" 11 | )] 12 | pub async fn config_transcribe_audio( 13 | ctx: Context<'_>, 14 | #[description = "Defaults to false"] transcribe_audio: bool, 15 | ) -> Result<(), Error> { 16 | let guild_id = ctx 17 | .guild_id() 18 | .map(|g| g.get()) 19 | .ok_or_else(Error::expected_guild)?; 20 | let resolved_language = 21 | scripty_i18n::get_resolved_language(ctx.author().id.get(), Some(guild_id)).await; 22 | 23 | sqlx::query!( 24 | "INSERT INTO guilds (guild_id, transcribe_audio_files) VALUES ($1, $2) ON CONFLICT \ 25 | (guild_id) DO UPDATE SET transcribe_audio_files = $2", 26 | guild_id as i64, 27 | transcribe_audio 28 | ) 29 | .execute(scripty_db::get_db()) 30 | .await?; 31 | 32 | ctx.say(format_message!( 33 | resolved_language, 34 | if transcribe_audio { 35 | "config-transcribe-audio-enabled" 36 | } else { 37 | "config-transcribe-audio-disabled" 38 | } 39 | )) 40 | .await?; 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/config/transcribe_voice_messages.rs: -------------------------------------------------------------------------------- 1 | use scripty_bot_utils::Context; 2 | use scripty_error::Error; 3 | 4 | /// Toggle whether Scripty transcribes voice messages 5 | #[poise::command( 6 | prefix_command, 7 | slash_command, 8 | guild_only, 9 | required_permissions = "MANAGE_GUILD", 10 | rename = "transcribe_voice_messages" 11 | )] 12 | pub async fn config_transcribe_voice_messages( 13 | ctx: Context<'_>, 14 | #[description = "Defaults to true"] transcribe_voice_messages: bool, 15 | ) -> Result<(), Error> { 16 | let resolved_language = 17 | scripty_i18n::get_resolved_language(ctx.author().id.get(), ctx.guild_id().map(|g| g.get())) 18 | .await; 19 | 20 | let guild_id = ctx 21 | .guild_id() 22 | .map(|g| g.get()) 23 | .ok_or_else(Error::expected_guild)?; 24 | sqlx::query!( 25 | "INSERT INTO guilds (guild_id, transcribe_voice_messages) VALUES ($1, $2) ON CONFLICT \ 26 | (guild_id) DO UPDATE SET transcribe_voice_messages = $2", 27 | guild_id as i64, 28 | transcribe_voice_messages 29 | ) 30 | .execute(scripty_db::get_db()) 31 | .await?; 32 | scripty_redis::run_transaction::<()>("DEL", |cmd| { 33 | cmd.arg(format!("voice_msg_transcript_{}", guild_id)); 34 | }) 35 | .await?; 36 | 37 | ctx.say(format_message!( 38 | resolved_language, 39 | if transcribe_voice_messages { 40 | "config-transcribe-voice-messages-enabled" 41 | } else { 42 | "config-transcribe-voice-messages-disabled" 43 | } 44 | )) 45 | .await?; 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/config/translate.rs: -------------------------------------------------------------------------------- 1 | use scripty_bot_utils::Context; 2 | use scripty_error::Error; 3 | 4 | /// Automatically translate transcriptions to English? 5 | #[poise::command( 6 | prefix_command, 7 | slash_command, 8 | guild_only, 9 | required_permissions = "MANAGE_GUILD", 10 | rename = "translate" 11 | )] 12 | pub async fn config_translate( 13 | ctx: Context<'_>, 14 | #[description = "Defaults to false"] translate: bool, 15 | ) -> Result<(), Error> { 16 | let guild_id = ctx 17 | .guild_id() 18 | .map(|g| g.get()) 19 | .ok_or_else(Error::expected_guild)?; 20 | let resolved_language = scripty_i18n::get_resolved_language(0, Some(guild_id)).await; 21 | 22 | if resolved_language.language != "en" && translate { 23 | ctx.say(format_message!( 24 | resolved_language, 25 | "config-translate-not-english", 26 | contextPrefix: ctx.prefix(), 27 | )) 28 | .await?; 29 | return Ok(()); 30 | } 31 | 32 | sqlx::query!( 33 | "INSERT INTO guilds (guild_id, translate) VALUES ($1, $2) ON CONFLICT (guild_id) DO \ 34 | UPDATE SET translate = $2", 35 | guild_id as i64, 36 | translate 37 | ) 38 | .execute(scripty_db::get_db()) 39 | .await?; 40 | 41 | ctx.say(format_message!( 42 | resolved_language, 43 | if translate { 44 | "config-translate-enabled" 45 | } else { 46 | "config-translate-disabled" 47 | } 48 | )) 49 | .await?; 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/config/verbose.rs: -------------------------------------------------------------------------------- 1 | use scripty_bot_utils::Context; 2 | use scripty_error::Error; 3 | 4 | /// Toggle whether Scripty is verbose during transcriptions. Most people don't need this. 5 | /// 6 | /// When enabled, Scripty will add timestamps to voice transcriptions, and place them in an embed. 7 | #[poise::command( 8 | prefix_command, 9 | slash_command, 10 | guild_only, 11 | required_permissions = "MANAGE_GUILD", 12 | rename = "verbose" 13 | )] 14 | pub async fn config_verbose( 15 | ctx: Context<'_>, 16 | #[description = "Defaults to false"] verbose: bool, 17 | ) -> Result<(), Error> { 18 | let resolved_language = 19 | scripty_i18n::get_resolved_language(ctx.author().id.get(), ctx.guild_id().map(|g| g.get())) 20 | .await; 21 | 22 | sqlx::query!( 23 | "INSERT INTO guilds (guild_id, be_verbose) VALUES ($1, $2) ON CONFLICT (guild_id) DO \ 24 | UPDATE SET be_verbose = $2", 25 | ctx.guild_id() 26 | .map(|g| g.get()) 27 | .ok_or_else(Error::expected_guild)? as i64, 28 | verbose 29 | ) 30 | .execute(scripty_db::get_db()) 31 | .await?; 32 | 33 | ctx.say(format_message!( 34 | resolved_language, 35 | if verbose { 36 | "config-verbose-enabled" 37 | } else { 38 | "config-verbose-disabled" 39 | } 40 | )) 41 | .await?; 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/debug.rs: -------------------------------------------------------------------------------- 1 | use poise::CreateReply; 2 | use serenity::builder::CreateAttachment; 3 | 4 | use crate::{Context, Error}; 5 | 6 | /// Output some data useful for debugging Scripty 7 | #[poise::command(prefix_command, slash_command, guild_only)] 8 | pub async fn debug(ctx: Context<'_>) -> Result<(), Error> { 9 | let guild_id = ctx.guild_id().ok_or_else(Error::expected_guild)?; 10 | let resolved_language = 11 | scripty_i18n::get_resolved_language(ctx.author().id.get(), Some(guild_id.get())).await; 12 | let state = scripty_audio_handler::get_internal_state(&guild_id); 13 | if let Some(state) = state { 14 | ctx.send( 15 | CreateReply::new() 16 | .content(format_message!(resolved_language, "debug-info-message")) 17 | .attachment(CreateAttachment::bytes( 18 | format!("{:#?}", state), 19 | Cow::Borrowed("debug_info.txt"), 20 | )), 21 | ) 22 | .await?; 23 | } else { 24 | ctx.send( 25 | CreateReply::new().content(format_message!(resolved_language, "debug-not-in-call")), 26 | ) 27 | .await?; 28 | } 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/dm_support.rs: -------------------------------------------------------------------------------- 1 | use scripty_bot_utils::globals::DM_SUPPORT_GLOBAL; 2 | 3 | use crate::{Context, Error}; 4 | 5 | #[poise::command(prefix_command, hide_in_help, rename = "ps", subcommands("ps_close"))] 6 | pub async fn ps_root(ctx: Context<'_>) -> Result<(), Error> { 7 | ctx.say(format!("subcommands: `{}ps close`", ctx.prefix())) 8 | .await?; 9 | Ok(()) 10 | } 11 | 12 | #[poise::command(prefix_command, hide_in_help, guild_only, rename = "close")] 13 | pub async fn ps_close(ctx: Context<'_>) -> Result<(), Error> { 14 | if let Some(st) = DM_SUPPORT_GLOBAL.get() { 15 | st.close_ticket( 16 | ctx.serenity_context(), 17 | ctx.channel_id() 18 | .to_channel(&ctx.http(), ctx.guild_id()) 19 | .await? 20 | .guild() 21 | .expect("should be in guild"), 22 | ) 23 | .await?; 24 | } else { 25 | ctx.say("error").await?; 26 | } 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/leave.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Error}; 2 | 3 | /// Leave any current voice call. 4 | #[poise::command(prefix_command, slash_command, guild_cooldown = 15, guild_only)] 5 | pub async fn leave(ctx: Context<'_>) -> Result<(), Error> { 6 | let resolved_language = 7 | scripty_i18n::get_resolved_language(ctx.author().id.get(), ctx.guild_id().map(|g| g.get())) 8 | .await; 9 | 10 | let _typing = ctx.defer_or_broadcast().await; 11 | let guild_id = { 12 | let guild = ctx.guild().ok_or_else(Error::expected_guild)?; 13 | guild.id 14 | }; 15 | 16 | scripty_audio_handler::disconnect_from_vc(ctx.serenity_context(), guild_id).await?; 17 | 18 | ctx.say(format_message!(resolved_language, "leave-success")) 19 | .await?; 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod admin; 2 | pub mod automod; 3 | pub mod config; 4 | mod data_storage; 5 | mod debug; 6 | pub mod dm_support; 7 | pub mod entity_block; 8 | mod help; 9 | mod join; 10 | mod language; 11 | mod leave; 12 | mod ping; 13 | pub mod premium; 14 | mod register_cmds; 15 | mod throw_error; 16 | mod transcribe_message; 17 | mod vote_reminders; 18 | 19 | pub use data_storage::*; 20 | pub use debug::debug; 21 | pub use help::help; 22 | pub use join::join; 23 | pub use language::user_language; 24 | pub use leave::leave; 25 | pub use ping::ping; 26 | pub use register_cmds::register_cmds; 27 | pub use throw_error::throw_error; 28 | pub use transcribe_message::{transcribe_message, transcribe_message_ctx_menu}; 29 | pub use vote_reminders::vote_reminder; 30 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/premium/mod.rs: -------------------------------------------------------------------------------- 1 | use poise::CreateReply; 2 | use serenity::builder::CreateEmbed; 3 | 4 | use crate::{Context, Error}; 5 | 6 | mod claim; 7 | mod info; 8 | mod remove; 9 | 10 | /// Premium commands 11 | #[poise::command( 12 | prefix_command, 13 | slash_command, 14 | rename = "premium", 15 | subcommands("claim::premium_claim", "info::premium_info", "remove::premium_remove"), 16 | subcommand_required 17 | )] 18 | pub async fn premium_root(ctx: Context<'_>) -> Result<(), Error> { 19 | let resolved_language = 20 | scripty_i18n::get_resolved_language(ctx.author().id.get(), ctx.guild_id().map(|g| g.get())) 21 | .await; 22 | 23 | ctx.send( 24 | CreateReply::default().ephemeral(true).embed( 25 | CreateEmbed::default() 26 | .title(format_message!( 27 | resolved_language, 28 | "root-command-invoked-title" 29 | )) 30 | .description(format_message!( 31 | resolved_language, 32 | "root-command-invoked-description", 33 | contextPrefix: ctx.prefix(), 34 | commandName: "premium" 35 | )), 36 | ), 37 | ) 38 | .await?; 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/premium/remove.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Error}; 2 | 3 | /// Remove your premium from this guild. 4 | #[poise::command( 5 | prefix_command, 6 | slash_command, 7 | guild_cooldown = 15, 8 | guild_only, 9 | rename = "remove" 10 | )] 11 | pub async fn premium_remove(ctx: Context<'_>) -> Result<(), Error> { 12 | let resolved_language = 13 | scripty_i18n::get_resolved_language(ctx.author().id.get(), ctx.guild_id().map(|g| g.get())) 14 | .await; 15 | 16 | let db = scripty_db::get_db(); 17 | let guild_id = ctx.guild().ok_or_else(Error::expected_guild)?.id.get() as i64; 18 | let hashed_user_id = scripty_utils::hash_user_id(ctx.author().id.get()); 19 | 20 | sqlx::query!( 21 | "UPDATE guilds SET premium_owner_id = nullif(premium_owner_id, $2) WHERE guild_id = $1", 22 | guild_id, 23 | &hashed_user_id, 24 | ) 25 | .execute(db) 26 | .await?; 27 | 28 | ctx.say(format_message!(resolved_language, "premium-removed")) 29 | .await?; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/register_cmds.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Error}; 2 | 3 | /// Register application commands in this guild or globally 4 | /// 5 | /// Run with no arguments to register in guild, run with argument "global" to register globally. 6 | #[poise::command(prefix_command, hide_in_help)] 7 | pub async fn register_cmds(ctx: Context<'_>) -> Result<(), Error> { 8 | poise::builtins::register_application_commands_buttons(ctx).await?; 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/throw_error.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Error}; 2 | 3 | #[poise::command(prefix_command, hide_in_help)] 4 | pub async fn throw_error(_ctx: Context<'_>) -> Result<(), Error> { 5 | Err(Error::manual()) 6 | } 7 | -------------------------------------------------------------------------------- /scripty_commands/src/cmds/vote_reminders.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Error}; 2 | 3 | /// Opt in or out of vote reminders 4 | #[poise::command(prefix_command, slash_command)] 5 | pub async fn vote_reminder( 6 | ctx: Context<'_>, 7 | #[description = "Enable vote reminders?"] enabled: bool, 8 | ) -> Result<(), Error> { 9 | let resolved_language = 10 | scripty_i18n::get_resolved_language(ctx.author().id.get(), ctx.guild_id().map(|g| g.get())) 11 | .await; 12 | 13 | let user_id = ctx.author().id.get(); 14 | let db = scripty_db::get_db(); 15 | let hashed_user_id = scripty_utils::hash_user_id(user_id); 16 | sqlx::query!( 17 | "INSERT INTO users (user_id) VALUES ($1) ON CONFLICT ON CONSTRAINT users_pkey DO NOTHING", 18 | &hashed_user_id, 19 | ) 20 | .execute(db) 21 | .await?; 22 | sqlx::query!( 23 | "UPDATE users SET vote_reminder_enabled = $1 WHERE user_id = $2", 24 | enabled, 25 | &hashed_user_id, 26 | ) 27 | .execute(db) 28 | .await?; 29 | if !enabled { 30 | sqlx::query!( 31 | "DELETE FROM vote_reminders WHERE user_id = $1", 32 | user_id as i64, 33 | ) 34 | .execute(db) 35 | .await?; 36 | } 37 | 38 | ctx.say(format_message!( 39 | resolved_language, 40 | if enabled { 41 | "vote-reminders-enabled" 42 | } else { 43 | "vote-reminders-disabled" 44 | } 45 | )) 46 | .await?; 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /scripty_commands/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(let_chains)] 2 | #![feature(duration_millis_float)] 3 | #[macro_use] 4 | extern crate scripty_i18n; 5 | #[macro_use] 6 | extern crate tracing; 7 | 8 | mod cmds; 9 | mod i18n; 10 | 11 | pub fn build_commands() -> Vec> { 12 | let mut cmds = vec![ 13 | cmds::register_cmds(), 14 | cmds::help(), 15 | cmds::join(), 16 | cmds::data_storage(), 17 | cmds::ping(), 18 | cmds::leave(), 19 | cmds::delete_all_data(), 20 | cmds::throw_error(), 21 | cmds::user_language(), 22 | cmds::vote_reminder(), 23 | cmds::transcribe_message(), 24 | cmds::transcribe_message_ctx_menu(), 25 | cmds::debug(), 26 | cmds::config::config_root(), 27 | cmds::entity_block::block_root(), 28 | cmds::admin::admin_root(), 29 | cmds::dm_support::ps_root(), 30 | cmds::premium::premium_root(), 31 | cmds::automod::automod_root(), 32 | ]; 33 | i18n::localize_commands(&mut cmds); 34 | cmds 35 | } 36 | 37 | use scripty_bot_utils::{Context, Data}; 38 | use scripty_error::Error; 39 | -------------------------------------------------------------------------------- /scripty_commands/target/sqlx/scripty_commands/query-d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": "SELECT COUNT(*) AS \"count!\" FROM automod_rules WHERE source_id = $1", 3 | "describe": { 4 | "columns": [ 5 | { 6 | "ordinal": 0, 7 | "name": "count!", 8 | "type_info": "Int8" 9 | } 10 | ], 11 | "parameters": { 12 | "Left": [ 13 | "Int4" 14 | ] 15 | }, 16 | "nullable": [ 17 | null 18 | ] 19 | }, 20 | "hash": "5d29e7b84d7f07dbae3aa1050e29f8118590a1f14c6cff076d89b14fd2e17247" 21 | } -------------------------------------------------------------------------------- /scripty_config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_config" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | toml.workspace = true 14 | serde.workspace = true 15 | once_cell.workspace = true 16 | -------------------------------------------------------------------------------- /scripty_config/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde; 3 | 4 | mod cfg; 5 | mod load; 6 | 7 | pub use cfg::*; 8 | pub use load::*; 9 | -------------------------------------------------------------------------------- /scripty_config/src/load.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use once_cell::sync::OnceCell; 4 | 5 | use crate::cfg::BotConfig; 6 | 7 | static GLOBAL_CONFIG: OnceCell = OnceCell::new(); 8 | 9 | pub fn load_config(cfg_path: &str) { 10 | let cfg = fs::read(cfg_path).expect("failed to read config"); 11 | let cfg_str = String::from_utf8(cfg).expect("config is not valid utf8"); 12 | 13 | let parsed_cfg: BotConfig = toml::from_str(&cfg_str).expect("config invalid"); 14 | 15 | GLOBAL_CONFIG 16 | .set(parsed_cfg) 17 | .unwrap_or_else(|_| panic!("don't call `load_config()` more than once")); 18 | } 19 | 20 | pub fn get_config() -> &'static BotConfig { 21 | GLOBAL_CONFIG 22 | .get() 23 | .expect("called `get_config()` before config was initialized") 24 | } 25 | -------------------------------------------------------------------------------- /scripty_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_core" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [[bin]] 10 | name = "scripty_v2" 11 | path = "src/main.rs" 12 | 13 | [lints] 14 | workspace = true 15 | 16 | [dependencies] 17 | url.workspace = true 18 | fern.workspace = true 19 | tokio.workspace = true 20 | rlimit.workspace = true 21 | tracing.workspace = true 22 | num_cpus.workspace = true 23 | humantime.workspace = true 24 | fenrir-rs.workspace = true 25 | 26 | scripty_db = { path = "../scripty_db" } 27 | scripty_bot = { path = "../scripty_bot" } 28 | scripty_stt = { path = "../scripty_stt" } 29 | scripty_i18n = { path = "../scripty_i18n" } 30 | scripty_redis = { path = "../scripty_redis" } 31 | scripty_config = { path = "../scripty_config" } 32 | scripty_metrics = { path = "../scripty_metrics" } 33 | scripty_webserver = { path = "../scripty_webserver" } 34 | scripty_build_checks = { path = "../scripty_build_checks" } 35 | scripty_data_storage = { path = "../scripty_data_storage" } 36 | -------------------------------------------------------------------------------- /scripty_data_storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_data_storage" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | csv.workspace = true 14 | hex.workspace = true 15 | rand.workspace = true 16 | sqlx.workspace = true 17 | tokio.workspace = true 18 | hound.workspace = true 19 | poise.workspace = true 20 | dashmap.workspace = true 21 | aes-gcm.workspace = true 22 | tracing.workspace = true 23 | serenity.workspace = true 24 | once_cell.workspace = true 25 | ouroboros.workspace = true 26 | 27 | scripty_db = { path = "../scripty_db" } 28 | scripty_utils = { path = "../scripty_utils" } 29 | scripty_redis = { path = "../scripty_redis" } 30 | scripty_config = { path = "../scripty_config" } -------------------------------------------------------------------------------- /scripty_data_storage/src/cache/mod.rs: -------------------------------------------------------------------------------- 1 | //! Cache for data storage. 2 | //! 3 | //! Because data storage can ingest huge amounts of data, it is necessary to cache whether or not a user has opted 4 | //! in to a particular data type being stored. 5 | //! 6 | //! This module provides abstractions for the cache, allowing for a quick, and easy way to check a user's status. 7 | //! 8 | //! A user can be opted into either storing their voice data, message data, neither, or both. As such, the cache 9 | //! requires multiple functions to check the status of a user. 10 | 11 | mod text; 12 | mod voice; 13 | 14 | pub use text::{change_text_state, get_text_state}; 15 | pub use voice::{change_voice_state, get_voice_state}; 16 | -------------------------------------------------------------------------------- /scripty_data_storage/src/crypto.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce, aead, aead::Aead}; 4 | use once_cell::sync::OnceCell; 5 | use rand::RngCore; 6 | 7 | static CRYPTO_CIPHER: OnceCell = OnceCell::new(); 8 | 9 | /// Generate a random 96 bit nonce. 10 | #[must_use] 11 | pub fn generate_nonce() -> [u8; 12] { 12 | let mut nonce = [0u8; 12]; 13 | rand::rng().fill_bytes(&mut nonce); 14 | nonce 15 | } 16 | 17 | /// Encrypt bytes with the configured key in bot config, and the given nonce. 18 | pub fn encrypt_bytes(bytes: &[u8], nonce: [u8; 12]) -> aead::Result> { 19 | let cipher = CRYPTO_CIPHER.get_or_init(init_cipher); 20 | 21 | let nonce = Nonce::from_slice(&nonce); 22 | 23 | cipher.encrypt(nonce, bytes) 24 | } 25 | 26 | /// Decrypt bytes with the configured key in bot config, and the given nonce. 27 | pub fn decrypt_bytes(bytes: &[u8], nonce: [u8; 12]) -> aead::Result> { 28 | let cipher = CRYPTO_CIPHER.get_or_init(init_cipher); 29 | 30 | let nonce = Nonce::from_slice(&nonce); 31 | 32 | cipher.decrypt(nonce, bytes) 33 | } 34 | 35 | #[cold] 36 | fn init_cipher() -> Aes256Gcm { 37 | let key = Key::::from_slice(scripty_config::get_config().secret_key.as_ref()); 38 | Aes256Gcm::new(key) 39 | } 40 | -------------------------------------------------------------------------------- /scripty_data_storage/src/ingest/mod.rs: -------------------------------------------------------------------------------- 1 | mod text; 2 | mod voice; 3 | 4 | pub use text::ingest_message; 5 | pub use voice::VoiceIngest; 6 | -------------------------------------------------------------------------------- /scripty_data_storage/src/ingest/text.rs: -------------------------------------------------------------------------------- 1 | use serenity::model::prelude::Message; 2 | 3 | pub async fn ingest_message(msg: Message) { 4 | let opted_in = crate::cache::get_text_state(msg.author.id.get()).await; 5 | 6 | if !opted_in { 7 | return; 8 | } 9 | 10 | let msg_content = msg.content.as_bytes(); 11 | let nonce = crate::crypto::generate_nonce(); 12 | let encrypted_msg_content = match crate::crypto::encrypt_bytes(msg_content, nonce) { 13 | Ok(encrypted_msg_content) => encrypted_msg_content, 14 | Err(e) => { 15 | error!("Error encrypting message: {}", e); 16 | return; 17 | } 18 | }; 19 | 20 | if let Err(e) = sqlx::query!( 21 | "INSERT INTO message_store (message_content, nonce) VALUES ($1, $2)", 22 | encrypted_msg_content, 23 | nonce.as_ref() 24 | ) 25 | .execute(scripty_db::get_db()) 26 | .await 27 | { 28 | warn!("Error inserting message into database: {}", e); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scripty_data_storage/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate tracing; 3 | 4 | mod cache; 5 | mod crypto; 6 | mod ingest; 7 | 8 | pub use cache::*; 9 | pub use crypto::*; 10 | pub use ingest::*; 11 | -------------------------------------------------------------------------------- /scripty_data_type/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_data_type" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | dashmap.workspace = true 14 | serenity.workspace = true 15 | futures-channel.workspace = true 16 | -------------------------------------------------------------------------------- /scripty_data_type/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod call_death_struct; 2 | 3 | use std::sync::{Arc, OnceLock}; 4 | 5 | pub use call_death_struct::{CallDeath, CallLivenessMap}; 6 | use dashmap::DashMap; 7 | use futures_channel::mpsc::UnboundedSender; 8 | use serenity::all::{ShardId, ShardRunnerInfo, ShardRunnerMessage}; 9 | 10 | pub type ShardRunnerMap = 11 | Arc)>>; 12 | 13 | pub struct Data { 14 | pub shard_runners: OnceLock, 15 | pub existing_calls: CallLivenessMap, 16 | } 17 | 18 | impl Data { 19 | #[must_use] 20 | pub fn new() -> Self { 21 | Self { 22 | shard_runners: OnceLock::new(), 23 | existing_calls: CallLivenessMap::new(), 24 | } 25 | } 26 | } 27 | 28 | impl Default for Data { 29 | fn default() -> Self { 30 | Self::new() 31 | } 32 | } 33 | 34 | #[must_use] 35 | pub fn get_data(ctx: &serenity::gateway::client::Context) -> Arc { 36 | ctx.data() 37 | } 38 | -------------------------------------------------------------------------------- /scripty_db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_db" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | sqlx.workspace = true 14 | tracing.workspace = true 15 | once_cell.workspace = true 16 | 17 | scripty_config = { path = "../scripty_config" } 18 | -------------------------------------------------------------------------------- /scripty_db/src/init.rs: -------------------------------------------------------------------------------- 1 | use scripty_config::DatabaseConnection; 2 | use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; 3 | 4 | pub async fn init_db() { 5 | let cfg = scripty_config::get_config(); 6 | 7 | let mut conn_opts = PgConnectOptions::new() 8 | .database(&cfg.database.database) 9 | .password(&cfg.database.password) 10 | .username(&cfg.database.user) 11 | .application_name("scripty"); 12 | 13 | conn_opts = match &cfg.database.host { 14 | DatabaseConnection::Tcp(host, port) => conn_opts.host(host).port(*port), 15 | DatabaseConnection::Unix(path) => conn_opts.socket(path), 16 | }; 17 | 18 | info!("trying to connect to postgres"); 19 | let pool = PgPoolOptions::new() 20 | .min_connections(2) 21 | .max_connections(32) 22 | .connect_with(conn_opts) 23 | .await 24 | .expect("failed to connect to db"); 25 | 26 | info!("running pending migrations"); 27 | sqlx::migrate!("../migrations") 28 | .run(&pool) 29 | .await 30 | .expect("failed to run migrations"); 31 | 32 | crate::store::set_db(pool); 33 | } 34 | -------------------------------------------------------------------------------- /scripty_db/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate tracing; 3 | 4 | mod init; 5 | mod store; 6 | 7 | pub use init::init_db; 8 | pub use sqlx::{self, postgres::PgPool}; 9 | pub use store::get_db; 10 | -------------------------------------------------------------------------------- /scripty_db/src/store.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::OnceCell; 2 | use sqlx::{Pool, Postgres}; 3 | 4 | static DATABASE_POOL: OnceCell> = OnceCell::new(); 5 | 6 | pub fn get_db() -> &'static Pool { 7 | DATABASE_POOL 8 | .get() 9 | .expect("called `get_db()` before initializing db") 10 | } 11 | 12 | pub fn set_db(db: Pool) { 13 | DATABASE_POOL 14 | .set(db) 15 | .unwrap_or_else(|_| panic!("called `set_db()` more than once")); 16 | } 17 | -------------------------------------------------------------------------------- /scripty_error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_error" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | sqlx.workspace = true 14 | uuid.workspace = true 15 | redis.workspace = true 16 | tokio.workspace = true 17 | magnum.workspace = true 18 | reqwest.workspace = true 19 | serenity.workspace = true 20 | songbird.workspace = true 21 | backtrace.workspace = true 22 | rmp-serde.workspace = true 23 | serde_json.workspace = true 24 | async-tempfile.workspace = true 25 | deadpool-redis.workspace = true 26 | 27 | scripty_integrations = { path = "../scripty_integrations" } 28 | -------------------------------------------------------------------------------- /scripty_error/src/internal.rs: -------------------------------------------------------------------------------- 1 | /// Syntax: 2 | /// 3 | /// ```rust 4 | /// error_fn_impl!( 5 | /// ErrorType, ErrorEnum; 6 | /// function_name, EnumVariantName, AttachedType; 7 | /// function_name_attached, EnumVariant; 8 | /// function_name_no_from_impl, EnumVariantFrom, AttachedType2 nofrom; 9 | /// ) 10 | /// ``` 11 | macro_rules! error_fn_impl { 12 | ($ty: ident, $en: ident; $name: ident, $enum_variant: ident) => { 13 | impl $ty { 14 | pub fn $name() -> $ty { 15 | $ty { 16 | bt: Backtrace::new_unresolved(), 17 | error: $en::$enum_variant, 18 | } 19 | } 20 | } 21 | }; 22 | 23 | ($ty: ident, $en: ident; $name: ident, $enum_variant: ident, $error_type: ty, nofrom) => { 24 | impl $ty { 25 | pub fn $name(error: $error_type) -> $ty { 26 | $ty { 27 | bt: Backtrace::new_unresolved(), 28 | error: $en::$enum_variant(error), 29 | } 30 | } 31 | } 32 | }; 33 | 34 | ($ty: ident, $en: ident; $name: ident, $enum_variant: ident, $error_type: ty) => { 35 | impl $ty { 36 | pub fn $name(error: $error_type) -> $ty { 37 | $ty { 38 | bt: Backtrace::new_unresolved(), 39 | error: $en::$enum_variant(error), 40 | } 41 | } 42 | } 43 | 44 | impl From<$error_type> for $ty { 45 | fn from(error: $error_type) -> $ty { 46 | $ty::$name(error) 47 | } 48 | } 49 | }; 50 | 51 | ($ty: ident, $en: ident; $($name: ident, $enum_variant: ident$(, $error_type: ty $(, $nf: tt)?)?);* $(;)?) => { 52 | $(error_fn_impl!($ty, $en; $name, $enum_variant$(, $error_type $(, $nf)?)?);)* 53 | }; 54 | } 55 | pub(super) use error_fn_impl; 56 | -------------------------------------------------------------------------------- /scripty_error/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod ffprobe; 2 | mod file_transcript; 3 | mod global; 4 | mod internal; 5 | mod model; 6 | 7 | pub use ffprobe::{FfprobeParsingError, FfprobeParsingErrorEnum}; 8 | pub use file_transcript::{FileTranscriptError, FileTranscriptErrorEnum}; 9 | pub use global::{Error, ErrorEnum}; 10 | pub use model::{SttServerError, SttServerErrorEnum}; 11 | -------------------------------------------------------------------------------- /scripty_i18n/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_i18n" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | sqlx.workspace = true 14 | serde.workspace = true 15 | fluent.workspace = true 16 | dashmap.workspace = true 17 | tracing.workspace = true 18 | once_cell.workspace = true 19 | serde_json.workspace = true 20 | unic-langid.workspace = true 21 | intl-memoizer.workspace = true 22 | 23 | scripty_db = { path = "../scripty_db" } 24 | scripty_utils = { path = "../scripty_utils" } 25 | scripty_config = { path = "../scripty_config" } 26 | -------------------------------------------------------------------------------- /scripty_i18n/locales/af.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/af.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/ar.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/ar.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/be.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/be.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/bg.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/bg.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/ca.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/ca.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/cs.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/cs.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/cy.ftl: -------------------------------------------------------------------------------- 1 | # generic strings 2 | # Message shown if a guild has not claimed their free trial of premium. Always appears on its own standalone line in the surrounding message. 3 | free-trial-upsell = Rydym yn cynnig treialon 3 diwrnod o Scripty Premium os hoffech chi roi cynnig arno a gweld a yw'n iawn i chi. DM y bot i ddechrau gyda threial am ddim. 4 | # premium command 5 | # This is shown to the user when they are not subscribed to premium. 6 | premium-not-premium = Nid ydych yn danysgrifiwr premiwm. Tanysgrifiwch yn https://scripty.org/premium. Os ydych yn gwybod eich bod yn un, os gwelwch yn dda DM y bot fel y gallwn adfer eich premiwm. 7 | # premium command 8 | # This is shown to the user when they have too many used servers to add more. 9 | premium-too-many-guilds = Rydych wedi hawlio { $totalServers } allwedd premiwm. Ni allwch ychwanegu rhagor, oni bai eich bod yn uwchraddio'ch tanysgrifiad premiwm yn , neu'n dileu rhai gyda'r gorchymyn `{ $commandPrefix }premium remove`. 10 | -------------------------------------------------------------------------------- /scripty_i18n/locales/da.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/da.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/el.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/el.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/en_devel.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/en_devel.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/et.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/et.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/fa.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/fa.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/gl.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/gl.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/he.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/he.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/hi.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/hi.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/hr.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/hr.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/hy.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/hy.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/id.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/id.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/is.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/is.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/it.ftl: -------------------------------------------------------------------------------- 1 | # join command 2 | # This and all attributes show up exclusively in the slash command picker when `join` is selected. 3 | cmds_join = entra 4 | .description = Entra nel canale vocale. Le trascrizioni vengono inviate nel canale in cui invii questo comando. 5 | .voice_channel = canale_vocale 6 | .voice_channel-description = Canale vocale da collegare. 7 | .record_transcriptions = registra_trascrizioni 8 | .record_transcriptions-description = Salvare le trascrizioni? Gli utenti saranno notificati nei Messaggi Diretti quando Scripty lascia il canale. Default su falso. 9 | .target_channel = canale_destinato 10 | .target_channel-description = Manda le trascrizioni qui, al posto del canale corrente. Scegli come target un forum per creare un nuovo post. 11 | .create_thread = crea_thread 12 | .create_thread-description = Creare un nuovo thread per questa trascrizione? Defaults su falso. 13 | join-success-description = Unito con successo { $voiceTargetMention }, e inviando l'output della trascrizione a { $outputChannelMention }. 14 | join-success-premium = Puoi controllare il Premium status di questo server con `/premium info`. 15 | join-success-help-title = Hai bisogno di aiuto? 16 | join-success-help-description = Puoi sia entrare nel server del supporto a { $supportServerInvite }, o inviare un messaggio privato al bot. 17 | join-success-footer-free-trial-upsell = Questo server è idoneo per una prova gratuita del Premium. Contatta nei messaggi privati il bot per richiederne una. 18 | -------------------------------------------------------------------------------- /scripty_i18n/locales/ja.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/ja.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/kk.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/kk.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/kn.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/kn.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/ko.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/ko.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/lt.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/lt.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/lv.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/lv.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/mi.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/mi.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/mk.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/mk.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/mr.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/mr.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/ms.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/ms.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/nb_NO.ftl: -------------------------------------------------------------------------------- 1 | # Language configuration strings 2 | # This message is shown as the embed title when the database returns an error when setting the language for an entity. 3 | language-set-failure-title-db = Databasefeil. 4 | # Command invocation contexts 5 | # This message is shown as the embed title when a user tries to invoke the root command of a group. 6 | root-command-invoked-title = Dette er en rot-kommando. 7 | # transcription info - verbose mode 8 | # This is shown as the percent accuracy of the transcription (roughly) 9 | transcription-info-transcription-confidence = Sikkerhet 10 | # Help menu translation strings 11 | default-category-name = Kommandoer 12 | # Language configuration strings 13 | # This message is shown as the embed title when the user sets their language successfully. 14 | user-language-set-success = Brukerspråk satt til «{ $language }». 15 | # Language configuration strings 16 | # This message is shown as the embed title when an entity tries to set their language to an invalid language. 17 | language-set-failure-title-invalid = Fant ikke språket «{ $language }». 18 | # automod list rules command 19 | automod-list-rules-no-rules = Du har ingen regler. 20 | # blocked entities description 21 | blocked-entity-no-reason-given = Blokkeringen ble ikke begrunnet. 22 | -------------------------------------------------------------------------------- /scripty_i18n/locales/ne.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/ne.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/pt.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/pt.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/pt_BR.ftl: -------------------------------------------------------------------------------- 1 | # join command 2 | # This message is shown when the bot does not have permissions for the voice channel it is trying to join. 3 | join-no-permission = Não tenho permissão para entrar em { $targetMention }. Por favor, conceda-me as permissões de Visualizar Canal e Entrar, ou entre em outro chat de voz onde eu tenha permissões. 4 | # join command 5 | # This message is shown when Discord tosses a Dropped or TimedOut error when trying to join a voice channel. 6 | join-failed-dropped = O Discord parece estar enfrentando problemas, não podemos fazer nada a respeito disso. Por favor, tente novamente mais tarde. 7 | # join command 8 | # This message is shown when the user attempts to make Scripty join a voice channel, but there is no one in the channel. 9 | join-no-one-in-channel = Não há ninguém em { $targetMention }. Não irei entrar se não houver ninguém lá, pois isso é um desperdício de recursos limitados. 10 | # premium command 11 | # This is shown to the user when they are not subscribed to premium. 12 | premium-not-premium = Você não é um assinante premium. Assine em https://scripty.org/premium. Se você sabe que é um assinante premium, envie uma DM para o bot, para que possamos restabelecer o seu acesso premium. 13 | -------------------------------------------------------------------------------- /scripty_i18n/locales/sk.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/sk.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/sl.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/sl.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/sr.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/sr.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/sv.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/sv.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/sw.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/sw.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/th.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/th.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/tl.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/tl.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/tr.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/tr.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/uk.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/uk.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/ur.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/ur.ftl -------------------------------------------------------------------------------- /scripty_i18n/locales/vi.ftl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scripty-bot/scripty/9b07e035ec1d1e166f34254324050f967b9a6e69/scripty_i18n/locales/vi.ftl -------------------------------------------------------------------------------- /scripty_i18n/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate tracing; 3 | 4 | mod bundles; 5 | mod cache; 6 | mod init; 7 | mod pretty; 8 | mod store; 9 | mod strings; 10 | 11 | pub use bundles::*; 12 | pub use cache::*; 13 | pub use fluent::FluentArgs; 14 | pub use init::*; 15 | pub use pretty::*; 16 | pub use store::*; 17 | pub use strings::*; 18 | pub use unic_langid::LanguageIdentifier; 19 | -------------------------------------------------------------------------------- /scripty_i18n/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // we can assume we're running at the root of the workspace 3 | 4 | // Load the translations 5 | let num_langs = scripty_i18n::init_i18n_with_path( 6 | std::env::current_dir() 7 | .expect("failed to get current dir") 8 | .join("scripty_i18n") 9 | .join("locales"), 10 | ); 11 | println!("Loaded {} languages", num_langs); 12 | 13 | // Iterate over every language and check some specific strings for validity 14 | let mut errors: Vec = vec![]; 15 | for lang in scripty_i18n::get_all_bundle_languages() 16 | .into_iter() 17 | .map(|x| scripty_i18n::get_bundle_for_language(&x)) 18 | { 19 | lang.value().set_transform() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scripty_i18n/src/store.rs: -------------------------------------------------------------------------------- 1 | use dashmap::DashMap; 2 | use fluent::{FluentResource, bundle::FluentBundle}; 3 | use intl_memoizer::concurrent::IntlLangMemoizer; 4 | use once_cell::sync::OnceCell; 5 | use unic_langid::LanguageIdentifier; 6 | 7 | pub(crate) type FluentBundleMap = 8 | DashMap>; 9 | 10 | static I18N_LANGUAGE_STORAGE: OnceCell = OnceCell::new(); 11 | 12 | pub(crate) fn set_i18n_store(store: FluentBundleMap) { 13 | I18N_LANGUAGE_STORAGE 14 | .set(store) 15 | .unwrap_or_else(|_| panic!("don't call `set_i18n_store` more than once")); 16 | } 17 | 18 | /// Fetch the i18n map from the global store. 19 | pub fn get_i18n_store() -> &'static FluentBundleMap { 20 | I18N_LANGUAGE_STORAGE 21 | .get() 22 | .unwrap_or_else(|| panic!("don't call `get_i18n_store` before `set_i18n_store`")) 23 | } 24 | -------------------------------------------------------------------------------- /scripty_integrations/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_integrations" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | serde.workspace = true 14 | reqwest.workspace = true 15 | bitflags.workspace = true 16 | serde_json.workspace = true 17 | 18 | scripty_config = { path = "../scripty_config" } 19 | -------------------------------------------------------------------------------- /scripty_integrations/src/kiai/api_models.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | bitflags! { 5 | pub struct Permissions: u8 { 6 | const LEVELS = 1 << 0; 7 | const MULTIPLIERS = 1 << 1; 8 | const EXPORT = 1 << 2; 9 | const BLACKLIST = 1 << 3; 10 | const REWARDS = 1 << 4; 11 | } 12 | } 13 | 14 | #[derive(Debug, Serialize, Deserialize)] 15 | pub struct KiaiPostVirtualMessage { 16 | pub channel: ChannelId, 17 | pub member: Member, 18 | pub guild: GuildId, 19 | } 20 | 21 | #[derive(Debug, Serialize, Deserialize)] 22 | pub struct ChannelId { 23 | pub channel: u64, 24 | } 25 | 26 | #[derive(Debug, Serialize, Deserialize)] 27 | pub struct Member { 28 | pub id: u64, 29 | pub roles: Vec, 30 | } 31 | 32 | #[derive(Debug, Serialize, Deserialize)] 33 | pub struct GuildId { 34 | pub guild: u64, 35 | } 36 | 37 | #[derive(Debug, Serialize, Deserialize)] 38 | pub struct KiaiPostVirtualMessageResponse { 39 | pub message: String, 40 | } 41 | -------------------------------------------------------------------------------- /scripty_integrations/src/kiai/mod.rs: -------------------------------------------------------------------------------- 1 | //! Integration with the Kiai bot: 2 | 3 | mod api_models; 4 | mod error; 5 | mod http_client; 6 | 7 | use std::sync::{Arc, OnceLock}; 8 | 9 | pub use api_models::*; 10 | pub use error::{KiaiApiError, KiaiApiResult}; 11 | pub use http_client::KiaiHttpClient; 12 | 13 | pub type KiaiApiClient = Arc; 14 | 15 | static KIAI_API_CLIENT: OnceLock = OnceLock::new(); 16 | 17 | pub fn get_kiai_api_client() -> &'static KiaiApiClient { 18 | KIAI_API_CLIENT.get_or_init(|| { 19 | let token = scripty_config::get_config().tokens.kiai.clone(); 20 | Arc::new(KiaiHttpClient::new(token).expect("failed to create kiai client")) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /scripty_integrations/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod kiai; 2 | -------------------------------------------------------------------------------- /scripty_metrics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_metrics" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | tokio.workspace = true 14 | chrono.workspace = true 15 | dashmap.workspace = true 16 | tracing.workspace = true 17 | once_cell.workspace = true 18 | systemstat.workspace = true 19 | prometheus.workspace = true 20 | tokio-metrics.workspace = true 21 | prometheus-static-metric.workspace = true 22 | -------------------------------------------------------------------------------- /scripty_metrics/src/cmd_handler.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | pub fn increment_command(_command_name: &str) {} 3 | -------------------------------------------------------------------------------- /scripty_metrics/src/get_metrics.rs: -------------------------------------------------------------------------------- 1 | use prometheus::TextEncoder; 2 | 3 | pub fn get_formatted_metrics() -> String { 4 | TextEncoder::new() 5 | .encode_to_string( 6 | &crate::METRICS 7 | .get() 8 | .expect("metrics not initialized") 9 | .registry 10 | .gather(), 11 | ) 12 | .expect("writing to a string should be infallible") 13 | } 14 | -------------------------------------------------------------------------------- /scripty_metrics/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate tracing; 3 | 4 | mod cmd_handler; 5 | mod cmd_latency; 6 | mod get_metrics; 7 | mod metrics; 8 | mod rt_metrics; 9 | 10 | pub use cmd_latency::*; 11 | pub use get_metrics::get_formatted_metrics; 12 | use metrics::METRICS; 13 | pub use metrics::{Metrics, get_metrics}; 14 | pub use prometheus::TEXT_FORMAT as METRIC_CONTENT_TYPE_HEADER_VALUE_TEXT; 15 | pub use rt_metrics::register_metrics; 16 | -------------------------------------------------------------------------------- /scripty_premium/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_premium" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | time.workspace = true 14 | sqlx.workspace = true 15 | dashmap.workspace = true 16 | tracing.workspace = true 17 | bitflags.workspace = true 18 | once_cell.workspace = true 19 | 20 | scripty_db = { path = "../scripty_db" } 21 | scripty_utils = { path = "../scripty_utils" } 22 | -------------------------------------------------------------------------------- /scripty_redis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_redis" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | redis.workspace = true 14 | tracing.workspace = true 15 | deadpool.workspace = true 16 | once_cell.workspace = true 17 | deadpool-redis.workspace = true 18 | 19 | scripty_error = { path = "../scripty_error" } 20 | scripty_config = { path = "../scripty_config" } 21 | -------------------------------------------------------------------------------- /scripty_redis/src/init.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use deadpool_redis::{Config, PoolConfig, Runtime, Timeouts, redis::cmd}; 4 | 5 | pub async fn init_redis() { 6 | info!("configuring redis pool"); 7 | 8 | // set up pool config 9 | let mut config = Config::from_url(&scripty_config::get_config().redis_url); 10 | let mut timeouts = Timeouts::new(); 11 | timeouts.create = Some(Duration::from_secs(5)); 12 | timeouts.recycle = Some(Duration::from_secs(2)); 13 | timeouts.wait = Some(Duration::from_secs(5)); 14 | let mut pool = PoolConfig::new(128); 15 | pool.timeouts = timeouts; 16 | config.pool = Some(pool); 17 | 18 | // initialize the pool 19 | info!("connecting to redis server"); 20 | let pool = config 21 | .create_pool(Some(Runtime::Tokio1)) 22 | .expect("failed to init redis"); 23 | 24 | // test the pool by setting a key and getting it, then deleting it 25 | info!("testing connection"); 26 | let mut conn = pool.get().await.unwrap(); 27 | let _: () = cmd("SET") 28 | .arg("test") 29 | .arg("test") 30 | .query_async(&mut conn) 31 | .await 32 | .unwrap(); 33 | let test: String = cmd("GET").arg("test").query_async(&mut conn).await.unwrap(); 34 | assert_eq!(test, "test"); 35 | let _: () = cmd("DEL").arg("test").query_async(&mut conn).await.unwrap(); 36 | 37 | // set the pool as the global pool 38 | crate::REDIS_POOL 39 | .set(pool) 40 | .unwrap_or_else(|_| panic!("failed to set redis pool")); 41 | } 42 | -------------------------------------------------------------------------------- /scripty_redis/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! General wrapper around Redis. 2 | 3 | #[macro_use] 4 | extern crate tracing; 5 | 6 | mod init; 7 | mod transaction; 8 | 9 | use deadpool_redis::Pool; 10 | pub use deadpool_redis::PoolError; 11 | pub use init::init_redis; 12 | use once_cell::sync::OnceCell; 13 | pub use redis; 14 | pub use transaction::run_transaction; 15 | 16 | static REDIS_POOL: OnceCell = OnceCell::new(); 17 | 18 | pub fn get_pool() -> &'static Pool { 19 | REDIS_POOL.get().expect("redis pool not initialized") 20 | } 21 | -------------------------------------------------------------------------------- /scripty_redis/src/transaction.rs: -------------------------------------------------------------------------------- 1 | //! Transaction helper for Redis. 2 | 3 | use redis::{Cmd, FromRedisValue}; 4 | use scripty_error::Error; 5 | 6 | use crate::get_pool; 7 | 8 | pub async fn run_transaction( 9 | cmd_name: &str, 10 | cmd_fn: impl FnOnce(&mut Cmd), 11 | ) -> Result { 12 | let mut conn = get_pool().get().await?; 13 | let mut cmd = redis::cmd(cmd_name); 14 | cmd_fn(&mut cmd); 15 | Ok(cmd.query_async(&mut conn).await?) 16 | } 17 | -------------------------------------------------------------------------------- /scripty_stt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_stt" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | uuid.workspace = true 14 | flume.workspace = true 15 | serde.workspace = true 16 | tokio.workspace = true 17 | magnum.workspace = true 18 | dashmap.workspace = true 19 | futures.workspace = true 20 | tracing.workspace = true 21 | byteorder.workspace = true 22 | rmp-serde.workspace = true 23 | once_cell.workspace = true 24 | serde_json.workspace = true 25 | dasp_signal.workspace = true 26 | dasp_interpolate.workspace = true 27 | 28 | scripty-common.workspace = true 29 | scripty_error = { path = "../scripty_error" } 30 | scripty_config = { path = "../scripty_config" } 31 | scripty_metrics = { path = "../scripty_metrics" } -------------------------------------------------------------------------------- /scripty_stt/src/decode_ogg_opus.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use magnum::{container::ogg::OpusSourceOgg, error::OpusSourceError}; 4 | 5 | pub fn decode_ogg_opus_file(file: Vec) -> Result, OpusSourceError> { 6 | let audio_source = OpusSourceOgg::new(Cursor::new(file))?; 7 | let channel_count = audio_source.metadata.channel_count; 8 | let sample_rate = audio_source.metadata.sample_rate; 9 | let f32_audio = audio_source.collect::>(); 10 | 11 | // convert the audio to i16 12 | let mut i16_audio = Vec::with_capacity(f32_audio.len()); 13 | for sample in f32_audio { 14 | // clamp the sample to [-1.0, 1.0] 15 | let sample = sample.clamp(-1.0, 1.0); 16 | i16_audio.push((sample * i16::MAX as f32) as i16); 17 | } 18 | 19 | // down-sample to 16KHz for whisper 20 | i16_audio = crate::process_audio(i16_audio, sample_rate as f64, 16000.0, channel_count); 21 | 22 | Ok(i16_audio) 23 | } 24 | -------------------------------------------------------------------------------- /scripty_stt/src/init.rs: -------------------------------------------------------------------------------- 1 | use crate::load_balancer::LoadBalancer; 2 | 3 | pub async fn init_stt() { 4 | info!("starting stt load balancer"); 5 | 6 | let balancer = LoadBalancer::new() 7 | .await 8 | .expect("failed to initialize a STT service"); 9 | crate::load_balancer::LOAD_BALANCER 10 | .set(balancer) 11 | .unwrap_or_else(|_| panic!("don't try to set the load balancer twice")); 12 | } 13 | -------------------------------------------------------------------------------- /scripty_stt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(portable_simd)] 2 | #[macro_use] 3 | extern crate tracing; 4 | 5 | mod decode_ogg_opus; 6 | mod ffprobe; 7 | mod init; 8 | mod load_balancer; 9 | mod models; 10 | mod process_audio; 11 | 12 | pub use decode_ogg_opus::decode_ogg_opus_file; 13 | pub use ffprobe::*; 14 | pub use init::init_stt; 15 | pub use magnum::error::OpusSourceError; 16 | pub use models::*; 17 | pub use process_audio::process_audio; 18 | use scripty_error::SttServerError; 19 | 20 | /// Number of times to try to find an available STT service before giving up. 21 | const NUM_STT_SERVICE_TRIES: usize = 1024; 22 | 23 | /// Check if a language is supported by the STT model. 24 | pub fn check_model_language(language: &str) -> bool { 25 | scripty_config::get_config() 26 | .languages 27 | .iter() 28 | .any(|l| l == language) 29 | } 30 | 31 | /// Get the list of supported languages by the STT model. 32 | pub fn get_model_languages() -> Vec { 33 | scripty_config::get_config().languages.clone() 34 | } 35 | 36 | /// Get a new stream. 37 | pub async fn get_stream() -> Result { 38 | load_balancer::LOAD_BALANCER 39 | .get() 40 | .expect("initialize load balancer before trying to get stream") 41 | .get_stream() 42 | .await 43 | } 44 | -------------------------------------------------------------------------------- /scripty_tts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_tts" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | serde.workspace = true 14 | tokio.workspace = true 15 | bitflags.workspace = true 16 | serde_derive.workspace = true 17 | -------------------------------------------------------------------------------- /scripty_tts/src/engine_trait.rs: -------------------------------------------------------------------------------- 1 | /// Trait for unique TTS engine backends. 2 | pub trait TtsEngine { 3 | type Error; 4 | 5 | /// Run the TTS engine on the given text, returning the resulting audio, 6 | /// in either WAV or raw signed 16-bit PCM format. 7 | fn get_waveform( 8 | &self, 9 | text: &str, 10 | params: &EngineParameters, 11 | ) -> Result; 12 | } 13 | 14 | pub enum TtsEngineOutput { 15 | Wav(Vec), 16 | RawPcm(Vec), 17 | } 18 | 19 | pub struct EngineParameters { 20 | pub voice: String, 21 | pub amplitude: u8, 22 | pub gap: u8, 23 | pub pitch: u8, 24 | pub speed: u16, 25 | } 26 | -------------------------------------------------------------------------------- /scripty_tts/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod engine_trait; 2 | 3 | mod tts_engines; 4 | -------------------------------------------------------------------------------- /scripty_tts/src/tts_engines/espeak_ng.rs: -------------------------------------------------------------------------------- 1 | use crate::engine_trait::{EngineParameters, TtsEngine, TtsEngineOutput}; 2 | 3 | pub struct EspeakNgEngine; 4 | 5 | pub struct EspeakNgError(u8); 6 | 7 | impl TtsEngine for EspeakNgEngine { 8 | type Error = EspeakNgError; 9 | 10 | fn get_waveform( 11 | &self, 12 | text: &str, 13 | params: &EngineParameters, 14 | ) -> Result { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripty_tts/src/tts_engines/mod.rs: -------------------------------------------------------------------------------- 1 | mod espeak_ng; 2 | -------------------------------------------------------------------------------- /scripty_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_utils" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | hex.workspace = true 14 | num.workspace = true 15 | sha2.workspace = true 16 | sqlx.workspace = true 17 | tokio.workspace = true 18 | tracing.workspace = true 19 | num_cpus.workspace = true 20 | serenity.workspace = true 21 | thousands.workspace = true 22 | once_cell.workspace = true 23 | systemstat.workspace = true 24 | 25 | scripty_db = { path = "../scripty_db" } 26 | scripty_config = { path = "../scripty_config" } -------------------------------------------------------------------------------- /scripty_utils/src/hash_user_id.rs: -------------------------------------------------------------------------------- 1 | use sha2::Digest; 2 | 3 | /// Hashes a user ID with sha512 and returns the hash. 4 | /// 5 | /// # Examples 6 | /// ``` 7 | /// use scripty_utils::hash_user_id; 8 | /// # fn main() { 9 | /// let user_id = 123456789; 10 | /// let hash = hash_user_id(user_id); 11 | /// assert_eq!(hash, [217, 230, 118, 45, 209, 200, 234, 246, 214, 27, 60, 97, 146, 252, 64, 141, 77, 109, 95, 17, 118, 208, 194, 145, 105, 188, 36, 231, 28, 63, 39, 74, 210, 127, 205, 88, 17, 179, 19, 214, 129, 247, 229, 94, 192, 45, 115, 212, 153, 201, 84, 85, 182, 181, 187, 80, 58, 207, 87, 79, 186, 143, 254, 133]); 12 | /// # } 13 | pub fn hash_user_id(user_id: u64) -> [u8; 64] { 14 | let mut hasher = sha2::Sha512::default(); 15 | hasher.update(user_id.to_string().into_bytes()); 16 | hasher.finalize().into() 17 | } 18 | -------------------------------------------------------------------------------- /scripty_utils/src/latency/db.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | /// Return database latency. 4 | pub async fn get_db_latency() -> Duration { 5 | let db = scripty_db::get_db(); 6 | let st = std::time::Instant::now(); 7 | let _ = sqlx::query!("SELECT guild_id FROM guilds LIMIT 1") 8 | .fetch_one(db) 9 | .await; 10 | let et = std::time::Instant::now(); 11 | et.duration_since(st) 12 | } 13 | -------------------------------------------------------------------------------- /scripty_utils/src/latency/http.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | use serenity::{model::id::GenericChannelId, prelude::Context}; 4 | 5 | pub async fn get_http_latency(ctx: &Context, channel: GenericChannelId) -> Duration { 6 | let st = Instant::now(); 7 | let _ = channel.broadcast_typing(&ctx.http).await; 8 | let et = Instant::now(); 9 | et.duration_since(st) 10 | } 11 | -------------------------------------------------------------------------------- /scripty_utils/src/latency/mod.rs: -------------------------------------------------------------------------------- 1 | mod db; 2 | mod http; 3 | mod ws; 4 | 5 | pub use db::get_db_latency; 6 | pub use http::get_http_latency; 7 | pub use ws::get_ws_latency; 8 | -------------------------------------------------------------------------------- /scripty_utils/src/latency/ws.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use serenity::all::{ShardId, ShardManager}; 4 | 5 | pub async fn get_ws_latency(shard_manager: &ShardManager, shard_id: u16) -> Option { 6 | shard_manager 7 | .runners 8 | .get(&ShardId(shard_id)) 9 | .and_then(|x| x.value().0.latency) 10 | } 11 | -------------------------------------------------------------------------------- /scripty_utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(slice_as_array)] 2 | 3 | mod embed_pagination; 4 | mod hash_user_id; 5 | pub mod latency; 6 | mod separate_num; 7 | 8 | pub use embed_pagination::do_paginate; 9 | pub use hash_user_id::hash_user_id; 10 | pub use separate_num::separate_num; 11 | -------------------------------------------------------------------------------- /scripty_utils/src/separate_num.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use num::Num; 4 | use thousands::Separable; 5 | 6 | pub fn separate_num(num: &T) -> String { 7 | num.separate_with_commas() 8 | } 9 | -------------------------------------------------------------------------------- /scripty_webserver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scripty_webserver" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dependencies] 13 | axum.workspace = true 14 | time.workspace = true 15 | sqlx.workspace = true 16 | serde.workspace = true 17 | tokio.workspace = true 18 | tracing.workspace = true 19 | serde_json.workspace = true 20 | 21 | scripty_db = { path = "../scripty_db" } 22 | scripty_i18n = { path = "../scripty_i18n" } 23 | scripty_utils = { path = "../scripty_utils" } 24 | scripty_config = { path = "../scripty_config" } 25 | scripty_metrics = { path = "../scripty_metrics" } 26 | scripty_botlists = { path = "../scripty_botlists" } 27 | scripty_bot_utils = { path = "../scripty_bot_utils" } 28 | -------------------------------------------------------------------------------- /scripty_webserver/src/auth.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::FromRequestParts, http::request::Parts}; 2 | 3 | use crate::errors::WebServerError; 4 | 5 | /// A type that handles authentication. 6 | /// 7 | /// Set it as the type of an argument to a server endpoint handler to enable authentication for that endpoint. 8 | pub struct Authentication { 9 | /// The token used for auth. 10 | #[allow(dead_code)] 11 | pub token: String, 12 | /// The user ID that was authenticated. 13 | /// 14 | /// If this is `0`, a global auth token was used. 15 | pub user_id: u64, 16 | } 17 | 18 | impl FromRequestParts for Authentication 19 | where 20 | S: Send + Sync, 21 | { 22 | type Rejection = WebServerError; 23 | 24 | async fn from_request_parts(req: &mut Parts, _state: &S) -> Result { 25 | // fetch config 26 | let config = scripty_config::get_config(); 27 | 28 | // get token from header 29 | let token = req 30 | .headers 31 | .get("Authorization") 32 | .ok_or(WebServerError::AuthenticationFailed(1))?; 33 | // convert to string 34 | let token = token 35 | .to_str() 36 | .map_err(|_| WebServerError::AuthenticationFailed(2))? 37 | .to_string(); 38 | 39 | // check token 40 | if config.api_tokens.contains(&token) { 41 | // global token was used 42 | return Ok(Authentication { token, user_id: 0 }); 43 | } 44 | 45 | // TODO: add support for user tokens 46 | // right now, only global tokens are supported 47 | Err(WebServerError::AuthenticationFailed(3)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /scripty_webserver/src/endpoints/bot_stats/mod.rs: -------------------------------------------------------------------------------- 1 | //! GET `/bot_stats` 2 | //! 3 | //! Returns bot statistics. 4 | 5 | mod advanced; 6 | 7 | use axum::{Json, routing::get}; 8 | use scripty_bot_utils::extern_utils::{ 9 | get_channel_count, 10 | get_guild_count, 11 | get_shard_count, 12 | get_user_count, 13 | }; 14 | 15 | use crate::errors::WebServerError; 16 | 17 | #[derive(Serialize, Deserialize, Debug)] 18 | pub struct BotStats { 19 | pub guild_count: usize, 20 | pub user_count: usize, 21 | pub channel_count: usize, 22 | pub shard_count: u16, 23 | } 24 | 25 | pub async fn get_bot_stats() -> Result, WebServerError> { 26 | Ok(Json(BotStats { 27 | guild_count: get_guild_count()?, 28 | user_count: get_user_count()?, 29 | channel_count: get_channel_count()?, 30 | shard_count: get_shard_count()?, 31 | })) 32 | } 33 | 34 | pub fn router() -> axum::Router { 35 | axum::Router::new() 36 | .route("/bot_stats", get(get_bot_stats)) 37 | .merge(advanced::router()) 38 | } 39 | -------------------------------------------------------------------------------- /scripty_webserver/src/endpoints/languages.rs: -------------------------------------------------------------------------------- 1 | //! GET `/languages` 2 | //! 3 | //! Returns the list of languages supported by the bot. 4 | 5 | use axum::{Json, routing::get}; 6 | use scripty_i18n::LanguageMapValue; 7 | 8 | pub async fn get_languages() -> Json> { 9 | Json( 10 | scripty_config::get_config() 11 | .languages 12 | .iter() 13 | .map(|x| scripty_i18n::get_pretty_language_name(x)) 14 | .map(|x| LanguageMapValue::new(x.0, x.1)) 15 | .collect::>(), 16 | ) 17 | } 18 | 19 | pub fn router() -> axum::Router { 20 | axum::Router::new().route("/languages", get(get_languages)) 21 | } 22 | -------------------------------------------------------------------------------- /scripty_webserver/src/endpoints/metrics.rs: -------------------------------------------------------------------------------- 1 | //! GET `/metrics` 2 | //! 3 | //! Returns Prometheus compatible metrics. 4 | 5 | use axum::{ 6 | http::{HeaderMap, HeaderValue, header::CONTENT_TYPE}, 7 | routing::get, 8 | }; 9 | 10 | // no auth, metrics are public 11 | pub async fn get_metrics() -> (HeaderMap, String) { 12 | let mut hm = HeaderMap::new(); 13 | hm.insert( 14 | CONTENT_TYPE, 15 | HeaderValue::from_static(scripty_metrics::METRIC_CONTENT_TYPE_HEADER_VALUE_TEXT), 16 | ); 17 | (hm, scripty_metrics::get_formatted_metrics()) 18 | } 19 | 20 | pub fn router() -> axum::Router { 21 | axum::Router::new().route("/metrics", get(get_metrics)) 22 | } 23 | -------------------------------------------------------------------------------- /scripty_webserver/src/endpoints/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bot_stats; 2 | pub mod languages; 3 | pub mod metrics; 4 | pub mod premium; 5 | pub mod webhooks; 6 | 7 | pub fn router() -> axum::Router { 8 | axum::Router::new() 9 | .merge(bot_stats::router()) 10 | .merge(metrics::router()) 11 | .merge(premium::router()) 12 | .merge(languages::router()) 13 | .merge(webhooks::router()) 14 | } 15 | -------------------------------------------------------------------------------- /scripty_webserver/src/endpoints/webhooks/mod.rs: -------------------------------------------------------------------------------- 1 | use axum::routing::post; 2 | 3 | mod discordservices_net; 4 | mod top_gg; 5 | mod wumpus_store; 6 | 7 | pub fn router() -> axum::Router { 8 | axum::Router::new() 9 | .route("/webhooks/top_gg", post(top_gg::top_gg_incoming_webhook)) 10 | .route( 11 | "/webhooks/wumpus_store", 12 | post(wumpus_store::wumpus_store_incoming_webhook), 13 | ) 14 | .route( 15 | "/webhooks/discordservices_net", 16 | post(discordservices_net::discordservices_net_incoming_webhook), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /scripty_webserver/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use tokio::net::TcpListener; 4 | 5 | pub async fn entrypoint() { 6 | let cfg = scripty_config::get_config(); 7 | let bind_addr: SocketAddr = cfg.bind_address.parse().expect("invalid bind address"); 8 | 9 | let router = crate::endpoints::router(); 10 | 11 | let tcp = TcpListener::bind(bind_addr).await.unwrap(); 12 | 13 | axum::serve(tcp, router) 14 | .await 15 | .expect("failed to start server"); 16 | } 17 | -------------------------------------------------------------------------------- /scripty_webserver/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(string_remove_matches)] 2 | 3 | mod auth; 4 | mod endpoints; 5 | mod entrypoint; 6 | mod errors; 7 | mod models; 8 | 9 | #[macro_use] 10 | extern crate tracing; 11 | #[macro_use] 12 | extern crate serde; 13 | 14 | pub use entrypoint::entrypoint; 15 | --------------------------------------------------------------------------------