init
This commit is contained in:
commit
8ddd89cb51
13
.github/workflows/build_nix.yml
vendored
Normal file
13
.github/workflows/build_nix.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: "Build legacy Nix package on Ubuntu"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: cachix/install-nix-action@v12
|
||||||
|
- name: Building package
|
||||||
|
run: nix-build . -A defaultPackage.x86_64-linux
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
target
|
||||||
|
.direnv
|
||||||
|
*.db
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.showUnlinkedFileNotification": false
|
||||||
|
}
|
3721
Cargo.lock
generated
Normal file
3721
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
Normal file
38
Cargo.toml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "hangitbot"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "A boring bot for hanging your boss up."
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wd_log = "0.2.0"
|
||||||
|
futures = "0.3.29"
|
||||||
|
strfmt = "^0.2.4"
|
||||||
|
reqwest= "^0.11"
|
||||||
|
rand="^0.8.5"
|
||||||
|
#lazy_static="^1.4.0"
|
||||||
|
|
||||||
|
[dependencies.clap]
|
||||||
|
version = "4.4.6"
|
||||||
|
features = ["derive", "env"]
|
||||||
|
|
||||||
|
[dependencies.tokio]
|
||||||
|
version = "^1.0"
|
||||||
|
features = ["full"]
|
||||||
|
|
||||||
|
[dependencies.teloxide]
|
||||||
|
version = "^0.12"
|
||||||
|
features = ["macros"]
|
||||||
|
|
||||||
|
[dependencies.sea-orm]
|
||||||
|
version = "^0.12.0"
|
||||||
|
features = ["macros", "sqlx-mysql", "sqlx-sqlite", "sqlx-postgres", "runtime-tokio-rustls"]
|
||||||
|
|
||||||
|
[dependencies.migration]
|
||||||
|
path = "migration"
|
||||||
|
|
||||||
|
[dependencies.models]
|
||||||
|
path = "entity"
|
7
default.nix
Normal file
7
default.nix
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
(import (
|
||||||
|
fetchTarball {
|
||||||
|
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||||
|
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
||||||
|
) {
|
||||||
|
src = ./.;
|
||||||
|
}).defaultNix
|
13
entity/Cargo.toml
Normal file
13
entity/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "models"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "models"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies.sea-orm]
|
||||||
|
version = "^0.12.0"
|
||||||
|
|
5
entity/src/lib.rs
Normal file
5
entity/src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod stats;
|
9
entity/src/prelude.rs
Normal file
9
entity/src/prelude.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||||
|
|
||||||
|
pub use super::stats::{
|
||||||
|
Entity as Stats,
|
||||||
|
ActiveModel as StatsActiveModel,
|
||||||
|
Column as StatsColumn,
|
||||||
|
Model as StatsModel,
|
||||||
|
PrimaryKey as ModelPrimaryKey,
|
||||||
|
};
|
28
entity/src/stats.rs
Normal file
28
entity/src/stats.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "stats")]
|
||||||
|
pub struct Model {
|
||||||
|
/// internal ID
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
/// group ID
|
||||||
|
#[sea_orm(indexed)]
|
||||||
|
pub group_id: i64,
|
||||||
|
|
||||||
|
/// name hanged
|
||||||
|
#[sea_orm(indexed)]
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// count of hanged
|
||||||
|
#[sea_orm(default = 0)]
|
||||||
|
pub counts: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
95
flake.lock
generated
Normal file
95
flake.lock
generated
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1698420672,
|
||||||
|
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1699186365,
|
||||||
|
"narHash": "sha256-Pxrw5U8mBsL3NlrJ6q1KK1crzvSUcdfwb9083sKDrcU=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a0b3b06b7a82c965ae0bb1d59f6e386fe755001d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1699186365,
|
||||||
|
"narHash": "sha256-Pxrw5U8mBsL3NlrJ6q1KK1crzvSUcdfwb9083sKDrcU=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a0b3b06b7a82c965ae0bb1d59f6e386fe755001d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1694529238,
|
||||||
|
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
51
flake.nix
Normal file
51
flake.nix
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
nixConfig = rec {
|
||||||
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
|
||||||
|
substituters = [
|
||||||
|
# Replace official cache with a mirror located in China
|
||||||
|
#
|
||||||
|
# Feel free to remove this line if you are not in China
|
||||||
|
"https://mirrors.ustc.edu.cn/nix-channels/store"
|
||||||
|
"https://mirrors.ustc.edu.cn/nix-channels/store" # 中科大
|
||||||
|
"https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store" #清华
|
||||||
|
"https://mirrors.bfsu.edu.cn/nix-channels/store" # 北外
|
||||||
|
"https://mirror.sjtu.edu.cn/nix-channels/store" #交大
|
||||||
|
#"https://cache.nixos.org"
|
||||||
|
];
|
||||||
|
trusted-substituters = substituters;
|
||||||
|
trusted-users = [
|
||||||
|
"coder"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
naersk.url = "github:nix-community/naersk/master";
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, utils, naersk }:
|
||||||
|
utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
naersk-lib = pkgs.callPackage naersk { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
defaultPackage = naersk-lib.buildPackage ./.;
|
||||||
|
devShell = with pkgs; mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
cargo
|
||||||
|
rustc
|
||||||
|
rustfmt
|
||||||
|
pre-commit
|
||||||
|
rustPackages.clippy
|
||||||
|
pkg-config
|
||||||
|
openssl
|
||||||
|
gcc
|
||||||
|
sqlite
|
||||||
|
];
|
||||||
|
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
24
migration/Cargo.toml
Normal file
24
migration/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "migration"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "migration"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-std = { version = "1", features = ["attributes", "tokio1"] }
|
||||||
|
models = { path = "../entity" }
|
||||||
|
|
||||||
|
[dependencies.sea-orm-migration]
|
||||||
|
version = "0.12.0"
|
||||||
|
features = [
|
||||||
|
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
|
||||||
|
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
|
||||||
|
# e.g.
|
||||||
|
# "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
|
||||||
|
# "sqlx-postgres", # `DATABASE_DRIVER` feature
|
||||||
|
]
|
||||||
|
|
41
migration/README.md
Normal file
41
migration/README.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Running Migrator CLI
|
||||||
|
|
||||||
|
- Generate a new migration file
|
||||||
|
```sh
|
||||||
|
cargo run -- generate MIGRATION_NAME
|
||||||
|
```
|
||||||
|
- Apply all pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
```sh
|
||||||
|
cargo run -- up
|
||||||
|
```
|
||||||
|
- Apply first 10 pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- up -n 10
|
||||||
|
```
|
||||||
|
- Rollback last applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down
|
||||||
|
```
|
||||||
|
- Rollback last 10 applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down -n 10
|
||||||
|
```
|
||||||
|
- Drop all tables from the database, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- fresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- refresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- reset
|
||||||
|
```
|
||||||
|
- Check the status of all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- status
|
||||||
|
```
|
12
migration/src/lib.rs
Normal file
12
migration/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
pub use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
mod m20220101_000001_create_table;
|
||||||
|
|
||||||
|
pub struct Migrator;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigratorTrait for Migrator {
|
||||||
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
|
vec![Box::new(m20220101_000001_create_table::Migration)]
|
||||||
|
}
|
||||||
|
}
|
25
migration/src/m20220101_000001_create_table.rs
Normal file
25
migration/src/m20220101_000001_create_table.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use models::*;
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
use sea_orm_migration::sea_orm::{ConnectionTrait, Schema};
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
let db = manager.get_connection();
|
||||||
|
let builder = db.get_database_backend();
|
||||||
|
let schema = Schema::new(builder);
|
||||||
|
|
||||||
|
db.execute(builder.build(&schema.create_table_from_entity(stats::Entity))).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager.drop_table(Table::drop().table(stats::Entity).to_owned()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
6
migration/src/main.rs
Normal file
6
migration/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
|
async fn main() {
|
||||||
|
cli::run_cli(migration::Migrator).await;
|
||||||
|
}
|
7
shell.nix
Normal file
7
shell.nix
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
(import (
|
||||||
|
fetchTarball {
|
||||||
|
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||||
|
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
||||||
|
) {
|
||||||
|
src = ./.;
|
||||||
|
}).shellNix
|
167
src/commands.rs
Normal file
167
src/commands.rs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
use std::{collections::HashMap, error::Error};
|
||||||
|
|
||||||
|
use rand::{rngs::OsRng, Rng};
|
||||||
|
use sea_orm::DbErr;
|
||||||
|
use strfmt::Format;
|
||||||
|
use teloxide::{
|
||||||
|
payloads::{SendMessage, SendMessageSetters},
|
||||||
|
prelude::Bot,
|
||||||
|
requests::{JsonRequest, Requester},
|
||||||
|
types::{Message, ParseMode},
|
||||||
|
utils::{command::BotCommands, markdown::escape},
|
||||||
|
};
|
||||||
|
use wd_log::log_error_ln;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::Args,
|
||||||
|
db_controller::Controller,
|
||||||
|
messages::{
|
||||||
|
BOT_ABOUT, BOT_TEXT_HANGED, BOT_TEXT_IS_CHANNEL, BOT_TEXT_NO_TARGET, BOT_TEXT_TOP_GLOBAL,
|
||||||
|
BOT_TEXT_TOP_GROUP, BOT_TEXT_TOP_NONE, BOT_TEXT_TOP_TEMPLATE, BOT_TEXT_TOP_TITLE,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(BotCommands, PartialEq, Debug, Clone)]
|
||||||
|
#[command(rename_rule = "lowercase", description = "帮助:")]
|
||||||
|
pub enum Commands {
|
||||||
|
#[command(description = "显示帮助信息")]
|
||||||
|
Help,
|
||||||
|
|
||||||
|
#[command(description = "关于本 Bot")]
|
||||||
|
About,
|
||||||
|
|
||||||
|
#[command(description = "排行榜")]
|
||||||
|
Top,
|
||||||
|
|
||||||
|
#[command(description = "吊丫起来!")]
|
||||||
|
HangIt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Commands {
|
||||||
|
fn default() -> Self {
|
||||||
|
Commands::Help
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CommandHandler {
|
||||||
|
pub controller: Controller,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandHandler {
|
||||||
|
pub async fn new(config: &Args) -> Result<Self, DbErr> {
|
||||||
|
Ok(Self {
|
||||||
|
controller: Controller::new(config.database_uri.to_owned()).await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init(&self) -> Result<(), DbErr> {
|
||||||
|
self.controller.migrate().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_text_reply(
|
||||||
|
&self,
|
||||||
|
bot: &Bot,
|
||||||
|
message: &Message,
|
||||||
|
text: String,
|
||||||
|
) -> JsonRequest<SendMessage> {
|
||||||
|
bot.send_message(message.chat.id, text)
|
||||||
|
.reply_to_message_id(message.id)
|
||||||
|
.parse_mode(ParseMode::MarkdownV2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn help_handler(
|
||||||
|
&self,
|
||||||
|
bot: &Bot,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<JsonRequest<SendMessage>, Box<dyn Error + Send + Sync>> {
|
||||||
|
Ok(self
|
||||||
|
.send_text_reply(bot, message, Commands::descriptions().to_string())
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn about_handler(
|
||||||
|
&self,
|
||||||
|
bot: &Bot,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<JsonRequest<SendMessage>, Box<dyn Error + Send + Sync>> {
|
||||||
|
Ok(self
|
||||||
|
.send_text_reply(bot, message, BOT_ABOUT.to_string())
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn hangit_handler(
|
||||||
|
&self,
|
||||||
|
bot: &Bot,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<JsonRequest<SendMessage>, Box<dyn Error + Send + Sync>> {
|
||||||
|
if let Some(reply) = message.reply_to_message() {
|
||||||
|
if let Some(user) = reply.from() {
|
||||||
|
let mut vars = HashMap::new();
|
||||||
|
let index = OsRng.gen::<usize>() % BOT_TEXT_HANGED.len();
|
||||||
|
let text = BOT_TEXT_HANGED[index];
|
||||||
|
|
||||||
|
vars.insert("name".to_string(), user.first_name.as_str());
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
.controller
|
||||||
|
.hangit(&user.full_name(), message.chat.id)
|
||||||
|
.await;
|
||||||
|
Ok(self
|
||||||
|
.send_text_reply(bot, reply, escape(&text.format(&vars).unwrap()))
|
||||||
|
.await)
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.send_text_reply(bot, message, BOT_TEXT_IS_CHANNEL.to_string())
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.send_text_reply(bot, message, BOT_TEXT_NO_TARGET.to_string())
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn top_handler(
|
||||||
|
&self,
|
||||||
|
bot: &Bot,
|
||||||
|
message: &Message,
|
||||||
|
) -> Result<JsonRequest<SendMessage>, Box<dyn Error + Send + Sync>> {
|
||||||
|
let chat_id = message.chat.id;
|
||||||
|
let scope = match chat_id.is_group() {
|
||||||
|
true => BOT_TEXT_TOP_GROUP,
|
||||||
|
false => BOT_TEXT_TOP_GLOBAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut index = 1;
|
||||||
|
let mut text = format!("{}-{}\n\n", BOT_TEXT_TOP_TITLE, scope);
|
||||||
|
let results = match self.controller.top(chat_id).await {
|
||||||
|
Ok(r) => match r {
|
||||||
|
Some(result) => result,
|
||||||
|
None => {
|
||||||
|
return Ok(self
|
||||||
|
.send_text_reply(bot, message, BOT_TEXT_TOP_NONE.to_string())
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
log_error_ln!("{}", error);
|
||||||
|
return Err(Box::new(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for result in results {
|
||||||
|
let mut vars = HashMap::new();
|
||||||
|
|
||||||
|
vars.insert("name".to_string(), result.name);
|
||||||
|
vars.insert("count".to_string(), result.counts.to_string());
|
||||||
|
|
||||||
|
let record = BOT_TEXT_TOP_TEMPLATE.format(&vars).unwrap();
|
||||||
|
|
||||||
|
text = format!("{}{} {}\n", text, index, record);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.send_text_reply(bot, message, text).await)
|
||||||
|
}
|
||||||
|
}
|
24
src/config.rs
Normal file
24
src/config.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
const DEFAULT_DATABASE: &'static str = "sqlite:///hangitbot.db";
|
||||||
|
const DEFAULT_API_URL: &'static str = "https://api.telegram.org";
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Enable debug mode
|
||||||
|
#[clap(short = 'D', long, value_parser, default_value_t = false)]
|
||||||
|
pub debug: bool,
|
||||||
|
|
||||||
|
/// Telegram bot token
|
||||||
|
#[clap(short, long, value_parser, env = "TGBOT_TOKEN")]
|
||||||
|
pub tgbot_token: String,
|
||||||
|
|
||||||
|
/// Database URI
|
||||||
|
#[clap(short, long, value_parser, env = "DATABASE_URI", default_value=DEFAULT_DATABASE)]
|
||||||
|
pub database_uri: String,
|
||||||
|
|
||||||
|
/// Api Server URL
|
||||||
|
#[clap(long, value_parser, env = "API_URL", default_value=DEFAULT_API_URL)]
|
||||||
|
pub api_url: String,
|
||||||
|
}
|
90
src/db_controller.rs
Normal file
90
src/db_controller.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use migration::{Migrator, MigratorTrait};
|
||||||
|
use models::prelude::*;
|
||||||
|
use sea_orm::{
|
||||||
|
ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, DbErr, EntityTrait,
|
||||||
|
PaginatorTrait, QueryFilter, QueryOrder, Set, TransactionTrait,
|
||||||
|
};
|
||||||
|
use teloxide::types::ChatId;
|
||||||
|
use wd_log::{log_info_ln, log_warn_ln};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Controller {
|
||||||
|
db: DatabaseConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Controller {
|
||||||
|
/// Create controller
|
||||||
|
pub async fn new(config: String) -> Result<Self, DbErr> {
|
||||||
|
Ok(Self {
|
||||||
|
db: Database::connect(config).await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do migrate
|
||||||
|
pub async fn migrate(&self) -> Result<(), DbErr> {
|
||||||
|
if let Err(err) = Migrator::install(&self.db).await {
|
||||||
|
log_warn_ln!("{}", err)
|
||||||
|
}
|
||||||
|
if let Err(err) = Migrator::up(&self.db, None).await {
|
||||||
|
Err(err)
|
||||||
|
} else {
|
||||||
|
log_info_ln!("database initialized.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// hang someone
|
||||||
|
pub async fn hangit(&self, name: &String, group_id: ChatId) -> Result<(), DbErr> {
|
||||||
|
let transcation = self.db.begin().await?;
|
||||||
|
match Stats::find()
|
||||||
|
.filter(StatsColumn::GroupId.eq(group_id.0))
|
||||||
|
.filter(StatsColumn::Name.eq(name))
|
||||||
|
.one(&transcation)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(one) => {
|
||||||
|
let counts = one.counts;
|
||||||
|
let mut one_active: StatsActiveModel = one.into();
|
||||||
|
one_active.counts = Set(counts + 1);
|
||||||
|
one_active.save(&transcation).await?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
StatsActiveModel {
|
||||||
|
group_id: Set(group_id.0),
|
||||||
|
name: Set(name.to_string()),
|
||||||
|
counts: Set(1),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.save(&transcation)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transcation.commit().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// stats
|
||||||
|
pub async fn top(&self, group_id: ChatId) -> Result<Option<Vec<StatsModel>>, DbErr> {
|
||||||
|
const LIMIT: u64 = 10;
|
||||||
|
let transcation = self.db.begin().await?;
|
||||||
|
|
||||||
|
let query = match group_id.is_group() {
|
||||||
|
true => {
|
||||||
|
Stats::find()
|
||||||
|
.filter(StatsColumn::GroupId.eq(group_id.0))
|
||||||
|
.order_by_desc(StatsColumn::Counts)
|
||||||
|
.paginate(&transcation, LIMIT)
|
||||||
|
.fetch()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
Stats::find()
|
||||||
|
.order_by_desc(StatsColumn::Counts)
|
||||||
|
.paginate(&transcation, LIMIT)
|
||||||
|
.fetch()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(query))
|
||||||
|
}
|
||||||
|
}
|
73
src/main.rs
Normal file
73
src/main.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
mod commands;
|
||||||
|
mod config;
|
||||||
|
mod db_controller;
|
||||||
|
mod messages;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use commands::{CommandHandler, Commands};
|
||||||
|
use config::Args;
|
||||||
|
|
||||||
|
use teloxide::{
|
||||||
|
prelude::*,
|
||||||
|
requests::{Request, Requester},
|
||||||
|
utils::command::BotCommands,
|
||||||
|
};
|
||||||
|
use wd_log::{
|
||||||
|
log_debug_ln, log_error_ln, log_info_ln, log_panic, set_level, set_prefix, DEBUG, INFO,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
set_prefix("hangitboot");
|
||||||
|
|
||||||
|
if args.debug {
|
||||||
|
set_level(DEBUG);
|
||||||
|
log_debug_ln!("{:?}", args);
|
||||||
|
} else {
|
||||||
|
set_level(INFO);
|
||||||
|
}
|
||||||
|
let command_handler = match CommandHandler::new(&args).await {
|
||||||
|
Err(err) => log_panic!("{}", err),
|
||||||
|
Ok(c) => c,
|
||||||
|
};
|
||||||
|
|
||||||
|
command_handler.init().await.unwrap();
|
||||||
|
|
||||||
|
let bot = Bot::new(args.tgbot_token.to_owned())
|
||||||
|
.set_api_url(reqwest::Url::parse(&args.api_url.as_str()).unwrap());
|
||||||
|
|
||||||
|
match bot.get_me().send().await {
|
||||||
|
Ok(result) => log_info_ln!(
|
||||||
|
"connect succeed: id={}, botname=\"{}\"",
|
||||||
|
result.id,
|
||||||
|
result.username()
|
||||||
|
),
|
||||||
|
Err(error) => log_panic!("{}", error),
|
||||||
|
}
|
||||||
|
|
||||||
|
register_commands(&bot).await;
|
||||||
|
|
||||||
|
Commands::repl(bot, move |bot: Bot, message: Message, cmd: Commands| {
|
||||||
|
let command_handler = command_handler.clone();
|
||||||
|
async move {
|
||||||
|
let _ = match cmd {
|
||||||
|
Commands::Help => command_handler.clone().help_handler(&bot, &message).await,
|
||||||
|
Commands::About => command_handler.clone().about_handler(&bot, &message).await,
|
||||||
|
Commands::Top => command_handler.clone().top_handler(&bot, &message).await,
|
||||||
|
Commands::HangIt => command_handler.clone().hangit_handler(&bot, &message).await,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn register_commands(bot: &Bot) {
|
||||||
|
if let Err(error) = bot.set_my_commands(Commands::bot_commands()).send().await {
|
||||||
|
log_error_ln!("{:?}", error);
|
||||||
|
} else {
|
||||||
|
log_info_ln!("commands registered")
|
||||||
|
}
|
||||||
|
}
|
27
src/messages.rs
Normal file
27
src/messages.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
pub const BOT_ABOUT: &'static str =
|
||||||
|
"*Hang it bot*\n\nHang your boss up!\n[Github](https://github.com/senseab/saysthbot-reborn) @ssthbot";
|
||||||
|
|
||||||
|
pub const BOT_TEXT_NO_TARGET: &'static str = "请在回复某一条信息时使用该指令";
|
||||||
|
pub const BOT_TEXT_IS_CHANNEL: &'static str = "这不是个人,吊不起来";
|
||||||
|
pub const BOT_TEXT_TOP_TITLE: &'static str = "**吊人排行榜**";
|
||||||
|
pub const BOT_TEXT_TOP_GROUP: &'static str = "群内";
|
||||||
|
pub const BOT_TEXT_TOP_GLOBAL: &'static str = "全球";
|
||||||
|
pub const BOT_TEXT_TOP_NONE: &'static str = "无人上榜";
|
||||||
|
pub const BOT_TEXT_TOP_TEMPLATE: &'static str = "{name} 被吊了 {count} 次";
|
||||||
|
|
||||||
|
const BOT_TEXT_HANGED_1: &'static str = "{name} 被吊路灯了,绳子是昨天他卖出去的……";
|
||||||
|
const BOT_TEXT_HANGED_2: &'static str = "因为 {name} 太过逆天,我们把 TA 吊在了路灯上……";
|
||||||
|
const BOT_TEXT_HANGED_3: &'static str = "{name} 吊在了路灯上,TA 兴风作浪的时代结束了……";
|
||||||
|
const BOT_TEXT_HANGED_4: &'static str = "吊在路灯上的 {name} 正在接受大家的鄙视……";
|
||||||
|
const BOT_TEXT_HANGED_5: &'static str = "对 {name} 来说,绳命来得快去得也快,只有路灯是永恒的……";
|
||||||
|
const BOT_TEXT_HANGED_6: &'static str =
|
||||||
|
"被套上麻袋的 {name} 在经历了一顿胖揍之后,最后还是成了路灯的挂件……";
|
||||||
|
|
||||||
|
pub const BOT_TEXT_HANGED: [&str; 6] = [
|
||||||
|
BOT_TEXT_HANGED_1,
|
||||||
|
BOT_TEXT_HANGED_2,
|
||||||
|
BOT_TEXT_HANGED_3,
|
||||||
|
BOT_TEXT_HANGED_4,
|
||||||
|
BOT_TEXT_HANGED_5,
|
||||||
|
BOT_TEXT_HANGED_6,
|
||||||
|
];
|
91
src/telegram_bot.rs
Normal file
91
src/telegram_bot.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use sea_orm::DbErr;
|
||||||
|
use teloxide::{
|
||||||
|
prelude::*,
|
||||||
|
requests::{Request, Requester},
|
||||||
|
types::{Message, UpdateKind},
|
||||||
|
utils::command::BotCommands,
|
||||||
|
RequestError,
|
||||||
|
};
|
||||||
|
use wd_log::{log_debug_ln, log_error_ln, log_info_ln, log_panic};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commands::{CommandHandler, Commands},
|
||||||
|
config::Args,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct BotServer {
|
||||||
|
bot: Bot,
|
||||||
|
pub command_handler: CommandHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BotServer {
|
||||||
|
/// Create new bot
|
||||||
|
pub async fn new(config: &Args) -> Result<Self, DbErr> {
|
||||||
|
Ok(Self {
|
||||||
|
bot: Bot::new(config.tgbot_token.to_owned())
|
||||||
|
.set_api_url(reqwest::Url::parse(&config.api_url.as_str()).unwrap()),
|
||||||
|
command_handler: CommandHandler::new(config).await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_error_hander(&self, error: &RequestError) {
|
||||||
|
log_error_ln!("{:?}", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn register_commands(&self) {
|
||||||
|
if let Err(error) = self
|
||||||
|
.bot
|
||||||
|
.set_my_commands(Commands::bot_commands())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
self.default_error_hander(&error);
|
||||||
|
} else {
|
||||||
|
log_info_ln!("commands registered")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn default_update_handler(&self, update_kind: &UpdateKind) {
|
||||||
|
log_debug_ln!("non-supported kind {:?}", update_kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_error_handler(&self, error: &RequestError) {
|
||||||
|
log_error_ln!("{:?}", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the bot
|
||||||
|
pub async fn run(&self) {
|
||||||
|
match self.bot.get_me().send().await {
|
||||||
|
Ok(result) => log_info_ln!(
|
||||||
|
"connect succeed: id={}, botname=\"{}\"",
|
||||||
|
result.id,
|
||||||
|
result.username()
|
||||||
|
),
|
||||||
|
Err(error) => log_panic!("{}", error),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.register_commands().await;
|
||||||
|
|
||||||
|
Commands::repl(
|
||||||
|
self.bot.to_owned(),
|
||||||
|
|bot: Bot, msg: Message, cmd: Commands| async move {},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn answer_generator(&self) -> impl Future<
|
||||||
|
Output = fn (Bot, Message, Commands) -> ResponseResult<()>> {
|
||||||
|
fn (bot: Bot, msg: Message, cmd: Commands) -> ResponseResult<()> {
|
||||||
|
async {
|
||||||
|
match cmd {
|
||||||
|
Commands::Help => Ok(self.command_handler.help_handler(&bot, &msg).await),
|
||||||
|
Commands::About => Ok(self.command_handler.about_handler(&bot, &msg).await),
|
||||||
|
Commands::Top => Ok(self.command_handler.top_handler(&bot, &msg).await),
|
||||||
|
Commands::HangIt => Ok(self.command_handler.hangit_handler(&bot, &msg).await),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user