make nix module
This commit is contained in:
parent
a4b8bdd8c0
commit
3ff112b1cd
6 changed files with 96 additions and 35 deletions
31
CLAUDE.md
31
CLAUDE.md
|
|
@ -1,4 +1,4 @@
|
||||||
# Telegram Bot
|
# Task Force Beta Bot
|
||||||
|
|
||||||
General-purpose Telegram bot with modular handler architecture.
|
General-purpose Telegram bot with modular handler architecture.
|
||||||
|
|
||||||
|
|
@ -6,17 +6,38 @@ General-purpose Telegram bot with modular handler architecture.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build --release
|
cargo build --release
|
||||||
cargo run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Usage
|
||||||
|
|
||||||
Bot token must be placed at: `/var/trust/instagram_link_stripper/telegram_token`
|
```
|
||||||
|
task_force_beta_bot <token_file> <allowed_chats_file>
|
||||||
|
```
|
||||||
|
|
||||||
|
Both arguments are required:
|
||||||
|
- `token_file` - Path to file containing the Telegram bot token
|
||||||
|
- `allowed_chats_file` - Path to file containing allowed chat IDs
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```bash
|
||||||
|
cargo run -- /path/to/token /path/to/allowed_chats
|
||||||
|
```
|
||||||
|
|
||||||
|
### Allowed Chats File
|
||||||
|
|
||||||
|
One chat ID per line. Lines starting with `#` are comments.
|
||||||
|
|
||||||
|
```
|
||||||
|
# My group
|
||||||
|
-100123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
The bot ignores messages from chats not in this list.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- `src/main.rs` - Entry point, dispatcher setup
|
- `src/main.rs` - Entry point, dispatcher setup
|
||||||
- `src/config.rs` - Token loading from /var/trust/
|
- `src/config.rs` - Token and allowed chats loading from CLI arguments
|
||||||
- `src/handlers/` - Message handlers (modular, each feature in own file)
|
- `src/handlers/` - Message handlers (modular, each feature in own file)
|
||||||
- `src/utils/` - Shared utilities
|
- `src/utils/` - Shared utilities
|
||||||
|
|
||||||
|
|
|
||||||
23
Cargo.lock
generated
23
Cargo.lock
generated
|
|
@ -757,17 +757,6 @@ dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "instagram_link_stripper"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"pretty_env_logger",
|
|
||||||
"teloxide",
|
|
||||||
"tokio",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
|
|
@ -1536,6 +1525,18 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e"
|
checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "task_force_beta_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"pretty_env_logger",
|
||||||
|
"teloxide",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "teloxide"
|
name = "teloxide"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "instagram_link_stripper"
|
name = "task_force_beta_bot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
|
@ -9,3 +9,4 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||||
url = "2"
|
url = "2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.5"
|
pretty_env_logger = "0.5"
|
||||||
|
once_cell = "1"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,25 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
const TOKEN_PATH: &str = "/var/trust/instagram_link_stripper/telegram_token";
|
fn get_required_arg(n: usize, name: &str) -> String {
|
||||||
|
std::env::args()
|
||||||
|
.nth(n)
|
||||||
|
.unwrap_or_else(|| panic!("Missing required argument {}: {}", n, name))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_token() -> String {
|
pub fn load_token() -> String {
|
||||||
let path = Path::new(TOKEN_PATH);
|
let path = get_required_arg(1, "token_file");
|
||||||
|
fs::read_to_string(&path)
|
||||||
fs::read_to_string(path)
|
.unwrap_or_else(|_| panic!("Failed to read token"))
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"Failed to read Telegram bot token from {}: {}",
|
|
||||||
TOKEN_PATH, e
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.trim()
|
.trim()
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_allowed_chats() -> Vec<i64> {
|
||||||
|
let path = get_required_arg(2, "allowed_chats_file");
|
||||||
|
fs::read_to_string(&path)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to read allowed chats"))
|
||||||
|
.lines()
|
||||||
|
.filter(|line| !line.trim().is_empty() && !line.trim().starts_with('#'))
|
||||||
|
.filter_map(|line| line.trim().parse::<i64>().ok())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,48 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use teloxide::dispatching::UpdateHandler;
|
use teloxide::dispatching::UpdateHandler;
|
||||||
use teloxide::prelude::*;
|
use teloxide::prelude::*;
|
||||||
use teloxide::types::ReplyParameters;
|
use teloxide::types::ReplyParameters;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
use crate::utils::url::strip_tracking_params;
|
use crate::utils::url::strip_tracking_params;
|
||||||
|
|
||||||
const INSTAGRAM_PATTERN: &str = "instagram.com/";
|
static ALLOWED_CHATS: Lazy<Vec<i64>> = Lazy::new(config::load_allowed_chats);
|
||||||
|
|
||||||
|
/// Force initialization and log the allowed chat IDs. Call at startup.
|
||||||
|
pub fn init() {
|
||||||
|
let chats = &*ALLOWED_CHATS;
|
||||||
|
log::info!("Loaded {} allowed chat IDs", chats.len());
|
||||||
|
for chat_id in chats {
|
||||||
|
log::debug!(" Allowed chat: {}", chat_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_chat_allowed(chat_id: i64) -> bool {
|
||||||
|
let allowed = ALLOWED_CHATS.contains(&chat_id);
|
||||||
|
if !allowed {
|
||||||
|
log::debug!("Message from unauthorized chat {}, ignoring", chat_id);
|
||||||
|
}
|
||||||
|
allowed
|
||||||
|
}
|
||||||
|
|
||||||
fn get_message_text(msg: &Message) -> Option<&str> {
|
fn get_message_text(msg: &Message) -> Option<&str> {
|
||||||
msg.text().or_else(|| msg.caption())
|
msg.text().or_else(|| msg.caption())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_instagram_link(text: &str) -> bool {
|
fn is_instagram_host(url: &Url) -> bool {
|
||||||
text.contains(INSTAGRAM_PATTERN)
|
url.host_str()
|
||||||
|
.map(|h| h == "instagram.com" || h == "www.instagram.com")
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_instagram_urls(text: &str) -> Vec<&str> {
|
fn extract_instagram_urls(text: &str) -> Vec<&str> {
|
||||||
text.split_whitespace()
|
text.split_whitespace()
|
||||||
.filter(|word| word.contains(INSTAGRAM_PATTERN))
|
.filter(|word| {
|
||||||
|
Url::parse(word)
|
||||||
|
.map(|u| is_instagram_host(&u))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,10 +73,13 @@ async fn handle_instagram_links(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains_instagram_url(msg: &Message) -> bool {
|
||||||
|
get_message_text(msg).map_or(false, |text| !extract_instagram_urls(text).is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handler() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
|
pub fn handler() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
Update::filter_message()
|
Update::filter_message()
|
||||||
.filter(|msg: Message| {
|
.filter(|msg: Message| is_chat_allowed(msg.chat.id.0))
|
||||||
get_message_text(&msg).map_or(false, contains_instagram_link)
|
.filter(|msg: Message| contains_instagram_url(&msg))
|
||||||
})
|
|
||||||
.endpoint(handle_instagram_links)
|
.endpoint(handle_instagram_links)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ async fn main() {
|
||||||
let token = config::load_token();
|
let token = config::load_token();
|
||||||
let bot = Bot::new(token);
|
let bot = Bot::new(token);
|
||||||
|
|
||||||
|
handlers::instagram::init();
|
||||||
|
|
||||||
Dispatcher::builder(bot, handlers::schema())
|
Dispatcher::builder(bot, handlers::schema())
|
||||||
.enable_ctrlc_handler()
|
.enable_ctrlc_handler()
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue