feat: Make config.toml
hold all configrations
Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
parent
8b8056f670
commit
83148f848a
3 changed files with 149 additions and 50 deletions
122
src/config.rs
Normal file
122
src/config.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// A simple API to ping telegram bots and returns if it's online or not.
|
||||||
|
// Copyright (C) 2023-2024 Awiteb <a@4rs.nl>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/agpl-3.0>.
|
||||||
|
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
use serde::{de::Error as DeError, Deserialize};
|
||||||
|
|
||||||
|
use crate::{ServerError, ServerResult};
|
||||||
|
|
||||||
|
/// The config of telegram client
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub(crate) struct TelegramClient {
|
||||||
|
pub api_hash: String,
|
||||||
|
pub api_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The config of the api
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub(crate) struct ApiConfig {
|
||||||
|
#[serde(deserialize_with = "host_deserialize")]
|
||||||
|
#[serde(default = "default_host")]
|
||||||
|
pub host: String,
|
||||||
|
#[serde(default = "default_port")]
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The config struct
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
pub client: TelegramClient,
|
||||||
|
pub api: ApiConfig,
|
||||||
|
#[serde(deserialize_with = "bots_deserialize")]
|
||||||
|
pub bots: Vec<String>,
|
||||||
|
#[serde(deserialize_with = "one_or_more_string")]
|
||||||
|
pub tokens: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Initialize the config from toml file
|
||||||
|
pub fn from_toml_file(file_path: impl AsRef<Path>) -> ServerResult<Self> {
|
||||||
|
toml::from_str(
|
||||||
|
&fs::read_to_string(file_path).map_err(|err| ServerError::Config(err.to_string()))?,
|
||||||
|
)
|
||||||
|
.map_err(|err| ServerError::Config(err.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function to deserialize the host, make sure it's a valid host to band
|
||||||
|
fn host_deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result<String, D::Error> {
|
||||||
|
let host = String::deserialize(d)?;
|
||||||
|
if host == "localhost" {
|
||||||
|
return Ok(host);
|
||||||
|
}
|
||||||
|
let octets = host
|
||||||
|
.split(".")
|
||||||
|
.map(|octet| octet.parse::<u8>())
|
||||||
|
.collect::<Result<Vec<_>, _>>();
|
||||||
|
if let Ok(octets) = octets {
|
||||||
|
if octets.len() != 4 {
|
||||||
|
return Err(DeError::custom("There is more then 4 octets"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(DeError::custom("Contain invalid number"));
|
||||||
|
}
|
||||||
|
Ok(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bots_deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<String>, D::Error> {
|
||||||
|
let bots = Vec::<String>::deserialize(d)?;
|
||||||
|
if bots.is_empty() {
|
||||||
|
return Err(DeError::custom("There must be one or more bots"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for bot in &bots {
|
||||||
|
if !bot.starts_with('@') {
|
||||||
|
return Err(DeError::custom(format!(
|
||||||
|
"Invalid bot username `{bot}`: must starts with `@`"
|
||||||
|
)));
|
||||||
|
} else if !bot.to_lowercase().ends_with("bot") {
|
||||||
|
return Err(DeError::custom(format!(
|
||||||
|
"Invalid bot username `{bot}`: must end with `bot`"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(bots.iter().map(|s| s.trim().to_owned()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn one_or_more_string<'de, D>(d: D) -> Result<Vec<String>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let str_vec = Vec::<String>::deserialize(d)?;
|
||||||
|
if str_vec.is_empty() {
|
||||||
|
return Err(DeError::custom("There is must be one at least"));
|
||||||
|
}
|
||||||
|
Ok(str_vec.iter().map(|s| s.trim().to_owned()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// The default host `0.0.0.0`
|
||||||
|
fn default_host() -> String {
|
||||||
|
"0.0.0.0".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default port `3939`
|
||||||
|
const fn default_port() -> u16 {
|
||||||
|
3939
|
||||||
|
}
|
|
@ -28,6 +28,8 @@ pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||||
pub(crate) enum Error {
|
pub(crate) enum Error {
|
||||||
#[error("Cli Error: {0}")]
|
#[error("Cli Error: {0}")]
|
||||||
CliParse(String),
|
CliParse(String),
|
||||||
|
#[error("Config Error: {0}")]
|
||||||
|
Config(String),
|
||||||
#[error("IO Error: {0}")]
|
#[error("IO Error: {0}")]
|
||||||
Io(#[from] IoError),
|
Io(#[from] IoError),
|
||||||
#[error("Thread Error: {0}")]
|
#[error("Thread Error: {0}")]
|
||||||
|
|
55
src/main.rs
55
src/main.rs
|
@ -16,18 +16,20 @@
|
||||||
|
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
use std::{env, fs, process::ExitCode, sync::Mutex};
|
use std::{process::ExitCode, sync::Mutex};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use salvo::{conn::TcpListener, Listener};
|
use salvo::{conn::TcpListener, Listener};
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod cli_parser;
|
mod cli_parser;
|
||||||
|
mod config;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod superbot;
|
mod superbot;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
|
||||||
pub(crate) use errors::{Error as ServerError, Result as ServerResult};
|
pub(crate) use errors::{Error as ServerError, Result as ServerResult};
|
||||||
|
use tokio::signal;
|
||||||
pub(crate) use traits::PingList;
|
pub(crate) use traits::PingList;
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
|
@ -67,60 +69,33 @@ async fn try_main() -> ServerResult<()> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let config = config::Config::from_toml_file(&cli_args.config_file)?;
|
||||||
|
|
||||||
let bots: Vec<String> = fs::read_to_string("bots.txt")?
|
let (client, sign_out) = superbot::login(config.client.api_hash, config.client.api_id).await?;
|
||||||
.lines()
|
let app_state = api::AppState::new(config.bots, config.tokens, client.clone());
|
||||||
.map(|b| b.trim().to_owned())
|
|
||||||
.collect();
|
|
||||||
let tokens: Vec<String> = fs::read_to_string("tokens.txt")?
|
|
||||||
.lines()
|
|
||||||
.map(|b| b.trim().to_owned())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if bots
|
|
||||||
.iter()
|
|
||||||
.any(|b| !b.starts_with('@') || !b.to_lowercase().ends_with("bot"))
|
|
||||||
{
|
|
||||||
bots.iter().for_each(|b| {
|
|
||||||
if !b.starts_with('@') {
|
|
||||||
eprintln!("Invalid bot username `{b}`: must starts with `@`");
|
|
||||||
} else if !b.to_lowercase().ends_with("bot") {
|
|
||||||
eprintln!("Invalid bot username `{b}`: must end with `bot`");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let (client, sign_out) = superbot::login(
|
|
||||||
env::var("TELEPINGBOT_API_HASH")
|
|
||||||
.expect("`TELEPINGBOT_API_HASH` environment variable is required"),
|
|
||||||
env::var("TELEPINGBOT_API_ID")
|
|
||||||
.expect("`TELEPINGBOT_API_ID` environment variable is required")
|
|
||||||
.parse()
|
|
||||||
.expect("Invalid value for `TELEPINGBOT_API_ID` must be a number"),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let host = env::var("TELEOINGBOT_HOST")
|
|
||||||
.expect("`TELEOINGBOT_HOST` environment variable must be set");
|
|
||||||
let port = env::var("TELEOINGBOT_PORT")
|
|
||||||
.expect("`TELEOINGBOT_PORT` environment variable must be set");
|
|
||||||
let app_state = api::AppState::new(bots, tokens, client.clone());
|
|
||||||
|
|
||||||
let handler_client = client.clone();
|
let handler_client = client.clone();
|
||||||
let acceptor = TcpListener::new(format!("{host}:{port}")).bind().await;
|
let acceptor = TcpListener::new(format!("{}:{}", config.api.host, config.api.port))
|
||||||
|
.bind()
|
||||||
|
.await;
|
||||||
let client_handler = tokio::spawn(async move { superbot::handler(handler_client).await });
|
let client_handler = tokio::spawn(async move { superbot::handler(handler_client).await });
|
||||||
let server_handler = tokio::spawn(async move {
|
let server_handler = tokio::spawn(async move {
|
||||||
salvo::Server::new(acceptor)
|
salvo::Server::new(acceptor)
|
||||||
.serve(api::service(app_state))
|
.serve(api::service(app_state))
|
||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
log::info!("Bind the API to {}:{}", config.api.host, config.api.port);
|
||||||
|
|
||||||
client_handler.await?;
|
tokio::select! {
|
||||||
server_handler.await?;
|
_ = client_handler => {},
|
||||||
|
_ = server_handler=> {},
|
||||||
|
_ = signal::ctrl_c() => {},
|
||||||
|
}
|
||||||
|
|
||||||
log::debug!("Close the API, telegram sign out status: {sign_out}");
|
log::debug!("Close the API, telegram sign out status: {sign_out}");
|
||||||
if sign_out {
|
if sign_out {
|
||||||
client.sign_out_disconnect().await?;
|
client.sign_out_disconnect().await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue