├── .gitignore ├── .idea ├── .gitignore ├── fxbot.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── Cargo.toml ├── LICENSE ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/jetbrains,rust 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains,rust 3 | 4 | ### JetBrains ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # AWS User-specific 16 | .idea/**/aws.xml 17 | 18 | # Generated files 19 | .idea/**/contentModel.xml 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/artifacts 39 | # .idea/compiler.xml 40 | # .idea/jarRepositories.xml 41 | # .idea/modules.xml 42 | # .idea/*.iml 43 | # .idea/modules 44 | # *.iml 45 | # *.ipr 46 | 47 | # CMake 48 | cmake-build-*/ 49 | 50 | # Mongo Explorer plugin 51 | .idea/**/mongoSettings.xml 52 | 53 | # File-based project format 54 | *.iws 55 | 56 | # IntelliJ 57 | out/ 58 | 59 | # mpeltonen/sbt-idea plugin 60 | .idea_modules/ 61 | 62 | # JIRA plugin 63 | atlassian-ide-plugin.xml 64 | 65 | # Cursive Clojure plugin 66 | .idea/replstate.xml 67 | 68 | # SonarLint plugin 69 | .idea/sonarlint/ 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | 80 | # Android studio 3.1+ serialized cache file 81 | .idea/caches/build_file_checksums.ser 82 | 83 | ### JetBrains Patch ### 84 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 85 | 86 | # *.iml 87 | # modules.xml 88 | # .idea/misc.xml 89 | # *.ipr 90 | 91 | # Sonarlint plugin 92 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 93 | .idea/**/sonarlint/ 94 | 95 | # SonarQube Plugin 96 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 97 | .idea/**/sonarIssues.xml 98 | 99 | # Markdown Navigator plugin 100 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 101 | .idea/**/markdown-navigator.xml 102 | .idea/**/markdown-navigator-enh.xml 103 | .idea/**/markdown-navigator/ 104 | 105 | # Cache file creation bug 106 | # See https://youtrack.jetbrains.com/issue/JBR-2257 107 | .idea/$CACHE_FILE$ 108 | 109 | # CodeStream plugin 110 | # https://plugins.jetbrains.com/plugin/12206-codestream 111 | .idea/codestream.xml 112 | 113 | # Azure Toolkit for IntelliJ plugin 114 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 115 | .idea/**/azureSettings.xml 116 | 117 | ### Rust ### 118 | # Generated by Cargo 119 | # will have compiled files and executables 120 | debug/ 121 | target/ 122 | 123 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 124 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 125 | Cargo.lock 126 | 127 | # These are backup files generated by rustfmt 128 | **/*.rs.bk 129 | 130 | # MSVC Windows builds of rustc generate these, which store debugging information 131 | *.pdb 132 | 133 | # End of https://www.toptal.com/developers/gitignore/api/jetbrains,rust 134 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | -------------------------------------------------------------------------------- /.idea/fxbot.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fxbot" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Discord bot that fixes embeds for twitter.com / x.com links by reposting with https://fxtwitter.com" 7 | homepage = "http://getfx.bot" 8 | repository = "https://github.com/d0nutptr/fxbot" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | tokio = { version = "1", features = ["full"]} 14 | serenity = "0.11" 15 | regex = "1.10" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 d0nut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fxbot 2 | 3 | > [!NOTE] 4 | > Twitter / X embeds are working again! This bot is no longer necessary. 5 | 6 | ~This simple discord bot replaces links to posts containing https://twitter.com or https://x.com with links to https://fxtwitter.com, which fixes embeds!~ 7 | 8 | ## How to use 9 | There are two ways to use `fxbot`: 10 | 11 | 1. You can use the official `fxbot` (hosted, by me, for free) by [adding it to your instance](https://getfx.bot). 12 | 13 | 2. If you want to host an instance yourself, [create a discord bot token](https://www.writebots.com/discord-bot-token/). Then, install and launch the app as follows: 14 | 15 | ```bash 16 | cargo install fxbot 17 | 18 | DISCORD_TOKEN= fxbot 19 | ``` 20 | 21 | Then add your bot to your discord instance. 22 | 23 | ## Problem? 24 | If there are any problems with the bot, please reach out to me by [pinging me on twitter](https://twitter.com/d0nutptr) or filling an issue on this repository! Thanks! 25 | 26 | ## Buy me a coffee 27 | If you find `fxbot` useful, or you're just feeling generous, I'd appreciate a small tip to keep `fxbot` hosting going. 28 | 29 | 30 | BuyMeACoffee: [https://www.buymeacoffee.com/d0nut](https://www.buymeacoffee.com/d0nut) 31 | 32 | BTC: `bc1qp5v62wuhsv2eqwg7v7qjgzcw96me9qu3uxqe93` 33 | 34 | ETH: `0xb90D6F1589BF00d5c3098700c5033459F68A39B5` 35 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use serenity::{async_trait, Client}; 3 | use serenity::client::Context; 4 | use serenity::framework::StandardFramework; 5 | use serenity::model::channel::Message; 6 | use serenity::prelude::{EventHandler, GatewayIntents}; 7 | 8 | const DISCORD_TOKEN: &str = "DISCORD_TOKEN"; 9 | const FX_TWITTER_HOST: &str = "https://fxtwitter.com"; 10 | const X_LINK_REGEX: &str = r"https?:\/\/x[.]com\/(?\w+\/status\/\d+(?:\?[a-zA-Z0-9_=]*)?)"; 11 | const TWITTER_LINK_REGEX: &str = r"https?:\/\/twitter[.]com\/(?\w+\/status\/\d+(?:\?[a-zA-Z0-9_=]*)?)"; 12 | 13 | struct FXHandler { 14 | x_regex: Regex, 15 | twitter_regex: Regex, 16 | } 17 | 18 | impl FXHandler { 19 | pub fn new() -> Self { 20 | let x_regex = Regex::new(X_LINK_REGEX).unwrap(); 21 | let twitter_regex = Regex::new(TWITTER_LINK_REGEX).unwrap(); 22 | 23 | Self { 24 | x_regex, 25 | twitter_regex, 26 | } 27 | } 28 | 29 | fn contains_x_or_twitter_link(&self, message: &str) -> bool { 30 | self.x_regex.is_match(message) 31 | || self.twitter_regex.is_match(message) 32 | } 33 | 34 | fn correct_x_with_fx(&self, message: &str) -> Option { 35 | if !self.contains_x_or_twitter_link(message) { 36 | return None; 37 | } 38 | 39 | let message = self 40 | .twitter_regex 41 | .replace_all(message, format!("{FX_TWITTER_HOST}/$path")); 42 | 43 | let message = self 44 | .x_regex 45 | .replace_all(&message, format!("{FX_TWITTER_HOST}/$path")); 46 | 47 | Some(message.to_string()) 48 | } 49 | } 50 | 51 | #[async_trait] 52 | impl EventHandler for FXHandler { 53 | async fn message(&self, ctx: Context, message: Message) { 54 | if message.is_own(&ctx.cache) { 55 | // we don't want to process the bot's own messages 56 | return; 57 | } 58 | 59 | let Some(corrected_message) = self.correct_x_with_fx(&message.content) else { 60 | return; 61 | }; 62 | 63 | let bot_message = format!( 64 | "<@{user}> says: \n\n{message}", 65 | user = message.author.id, 66 | message = corrected_message 67 | ); 68 | 69 | let delete_message_result = message 70 | .delete(&ctx.http) 71 | .await; 72 | 73 | if let Err(err) = delete_message_result { 74 | println!("Error deleting origin message: {err:#?}"); 75 | // if we failed to delete the old message, let's bail out now 76 | return; 77 | } 78 | 79 | let send_message_result = match message.referenced_message { 80 | // if the user was replying to someone, we should as well 81 | Some(op_message) => { 82 | op_message 83 | .reply(&ctx.http, bot_message) 84 | .await 85 | } 86 | // ... otherwise post a regular message 87 | None => { 88 | message 89 | .channel_id 90 | .say(&ctx.http, bot_message) 91 | .await 92 | } 93 | }; 94 | 95 | if let Err(err) = send_message_result { 96 | println!("Error sending message: {err:#?}"); 97 | } 98 | } 99 | } 100 | 101 | #[tokio::main] 102 | async fn main() { 103 | let token = std::env::var(DISCORD_TOKEN) 104 | .expect("Missing discord token"); 105 | 106 | let intents = GatewayIntents::MESSAGE_CONTENT 107 | | GatewayIntents::GUILD_MESSAGES; 108 | 109 | let mut client = Client::builder(token, intents) 110 | .event_handler(FXHandler::new()) 111 | .framework(StandardFramework::new()) 112 | .await 113 | .expect("Failed to create bot client"); 114 | 115 | if let Err(e) = client.start().await { 116 | println!("client error: {e:#?}"); 117 | } 118 | } --------------------------------------------------------------------------------