├── .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 |
10 |
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 | }
--------------------------------------------------------------------------------