mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-21 10:34:21 +00:00
Add Dota guild match monitoring and notifications (#9)
This commit is contained in:
parent
2b31bb8c0a
commit
ba087ab4de
20 changed files with 67853 additions and 62 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/src/stratz/schema.json linguist-generated
|
188
Cargo.lock
generated
188
Cargo.lock
generated
|
@ -60,6 +60,12 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
|
@ -125,15 +131,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.6.0"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.104"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490"
|
||||
checksum = "18e2d530f35b40a84124146478cd16f34225306a8441998836466a2e2961c950"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -155,6 +161,19 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"byteorder",
|
||||
"either",
|
||||
"memchr",
|
||||
"unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
|
@ -189,12 +208,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core 0.20.9",
|
||||
"darling_macro 0.20.9",
|
||||
"darling_core 0.20.10",
|
||||
"darling_macro 0.20.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -213,16 +232,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -238,13 +257,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core 0.20.9",
|
||||
"darling_core 0.20.10",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -257,7 +276,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -283,7 +302,7 @@ dependencies = [
|
|||
"dsl_auto_type",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -292,7 +311,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25"
|
||||
dependencies = [
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -310,12 +329,12 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0892a17df262a24294c382f0d5997571006e7a4348b4327557c4ff1cd4a8bccc"
|
||||
dependencies = [
|
||||
"darling 0.20.9",
|
||||
"darling 0.20.10",
|
||||
"either",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -464,7 +483,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -514,6 +533,64 @@ version = "0.29.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "graphql-introspection-query"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql-parser"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ebc8013b4426d5b81a4364c419a95ed0b404af2b82e2457de52d9348f0e474"
|
||||
dependencies = [
|
||||
"combine",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_client"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a50cfdc7f34b7f01909d55c2dcb71d4c13cbcbb4a1605d6c8bd760d654c1144b"
|
||||
dependencies = [
|
||||
"graphql_query_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_client_codegen"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e27ed0c2cf0c0cc52c6bcf3b45c907f433015e580879d14005386251842fb0a"
|
||||
dependencies = [
|
||||
"graphql-introspection-query",
|
||||
"graphql-parser",
|
||||
"heck 0.4.1",
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_query_derive"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83febfa838f898cfa73dfaa7a8eb69ff3409021ac06ee94cfb3d622f6eeb1a97"
|
||||
dependencies = [
|
||||
"graphql_client_codegen",
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
|
@ -652,9 +729,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.29"
|
||||
version = "0.14.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
|
||||
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
|
@ -718,7 +795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper 0.14.29",
|
||||
"hyper 0.14.30",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
|
@ -850,6 +927,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
|
@ -891,7 +974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a567fdbbc6155f0a8b18caa80aa8ffa2d661c46455ce8e01ba68a039f9cac979"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1026,7 +1109,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1081,7 +1164,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1251,7 +1334,7 @@ dependencies = [
|
|||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.29",
|
||||
"hyper 0.14.30",
|
||||
"hyper-tls 0.5.0",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
|
@ -1345,6 +1428,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"chrono",
|
||||
"diesel",
|
||||
"graphql_client",
|
||||
"log",
|
||||
"micronfig",
|
||||
"once_cell",
|
||||
|
@ -1352,9 +1436,10 @@ dependencies = [
|
|||
"pretty_env_logger",
|
||||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.12.5",
|
||||
"serde",
|
||||
"teloxide",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
@ -1502,7 +1587,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1611,9 +1696,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.68"
|
||||
version = "2.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
|
||||
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1757,29 +1842,29 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.1"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82"
|
||||
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
@ -1816,7 +1901,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1946,6 +2031,15 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
dependencies = [
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
@ -1966,9 +2060,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.9.1"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
@ -1985,6 +2079,12 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
@ -2021,7 +2121,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -2055,7 +2155,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.71",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
|
|
@ -41,6 +41,8 @@ regex = "1.10.5"
|
|||
once_cell = "1.19.0"
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
graphql_client = "0.14.0"
|
||||
thiserror = "1.0.62"
|
||||
|
||||
[[bin]]
|
||||
name = "royalnet"
|
||||
|
|
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
|
@ -0,0 +1,36 @@
|
|||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
|
||||
|
||||
|
||||
-- Sets up a trigger for the given table to automatically set a column called
|
||||
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||
-- in the modified columns)
|
||||
--
|
||||
-- # Example
|
||||
--
|
||||
-- ```sql
|
||||
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||
--
|
||||
-- SELECT diesel_manage_updated_at('users');
|
||||
-- ```
|
||||
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF (
|
||||
NEW IS DISTINCT FROM OLD AND
|
||||
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||
) THEN
|
||||
NEW.updated_at := current_timestamp;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
1
migrations/2024-07-15-083921_table_brooch/down.sql
Normal file
1
migrations/2024-07-15-083921_table_brooch/down.sql
Normal file
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS brooch_match;
|
3
migrations/2024-07-15-083921_table_brooch/up.sql
Normal file
3
migrations/2024-07-15-083921_table_brooch/up.sql
Normal file
|
@ -0,0 +1,3 @@
|
|||
CREATE TABLE brooch_match (
|
||||
id BIGINT PRIMARY KEY
|
||||
);
|
|
@ -1,6 +1,6 @@
|
|||
use diesel::{Identifiable, Insertable, Queryable, Selectable, Associations};
|
||||
use diesel::pg::Pg;
|
||||
use super::schema::{users, telegram, discord, steam};
|
||||
use super::schema::{users, telegram, discord, steam, brooch_match};
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
|
@ -22,7 +22,6 @@ pub struct TelegramUser {
|
|||
pub telegram_id: i64,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = discord)]
|
||||
|
@ -43,3 +42,11 @@ pub struct SteamUser {
|
|||
pub user_id: i32,
|
||||
pub steam_id: i64,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = brooch_match)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct BroochMatch {
|
||||
pub id: i64,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
brooch_match (id) {
|
||||
id -> Int8,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
discord (discord_id) {
|
||||
user_id -> Int4,
|
||||
|
@ -33,6 +39,7 @@ diesel::joinable!(steam -> users (user_id));
|
|||
diesel::joinable!(telegram -> users (user_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
brooch_match,
|
||||
discord,
|
||||
steam,
|
||||
telegram,
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::services::RoyalnetService;
|
|||
pub(crate) mod database;
|
||||
pub(crate) mod utils;
|
||||
mod services;
|
||||
|
||||
mod stratz;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
|
@ -16,10 +16,15 @@ async fn main() -> Result<()> {
|
|||
log::trace!("Setting up Telegram bot service...");
|
||||
let telegram = services::telegram::BotService::from_config();
|
||||
|
||||
// Brooch setup
|
||||
log::trace!("Setting up Brooch service...");
|
||||
let brooch = services::brooch::BroochService::from_config();
|
||||
|
||||
// Run all services concurrently
|
||||
log::info!("Starting services...");
|
||||
let result = tokio::try_join![
|
||||
telegram.run(),
|
||||
brooch.run(),
|
||||
];
|
||||
|
||||
// This should never happen, but just in case...
|
||||
|
|
7
src/services/brooch/config.rs
Normal file
7
src/services/brooch/config.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
use micronfig::config;
|
||||
|
||||
config! {
|
||||
BROOCH_TELEGRAM_BOT_TOKEN,
|
||||
BROOCH_WATCHED_GUILD_ID: String > i64 -> crate::stratz::GuildId,
|
||||
BROOCH_NOTIFICATION_CHAT_ID: String > i64 -> crate::utils::hacks::ChatIdConversionHack -> teloxide::types::ChatId
|
||||
}
|
494
src/services/brooch/mod.rs
Normal file
494
src/services/brooch/mod.rs
Normal file
|
@ -0,0 +1,494 @@
|
|||
use std::cmp::PartialEq;
|
||||
use std::convert::Infallible;
|
||||
use anyhow::Result;
|
||||
use std::time::Duration;
|
||||
use anyhow::Context;
|
||||
use chrono::{TimeDelta, TimeZone};
|
||||
use diesel::PgConnection;
|
||||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::ChatId;
|
||||
use tokio::time::sleep;
|
||||
use crate::database;
|
||||
use crate::services::RoyalnetService;
|
||||
use crate::stratz::{GuildId, Match, Player, Role, Lane, query_guild_matches};
|
||||
use crate::stratz::guild_matches_query::{GameModeEnumType, LobbyTypeEnum};
|
||||
|
||||
mod config;
|
||||
|
||||
pub struct BroochService {
|
||||
pub guild_id: GuildId,
|
||||
pub chat_id: ChatId,
|
||||
pub bot: Bot,
|
||||
}
|
||||
|
||||
impl BroochService {
|
||||
const MAX_IMP_WAIT: TimeDelta = TimeDelta::minutes(60);
|
||||
|
||||
pub fn from_config() -> Self {
|
||||
Self {
|
||||
guild_id: config::BROOCH_WATCHED_GUILD_ID().clone(),
|
||||
chat_id: config::BROOCH_NOTIFICATION_CHAT_ID().clone(),
|
||||
bot: Bot::new(config::BROOCH_TELEGRAM_BOT_TOKEN().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn iteration_request(&self) -> Result<()> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let mut database = database::connect()
|
||||
.context("Non è stato possibile connettersi al database RYG.")?;
|
||||
|
||||
let data = query_guild_matches(&client, &self.guild_id).await
|
||||
.context("Non è stato possibile recuperare le ultime partite di Dota da STRATZ.")?;
|
||||
|
||||
let data = data.data
|
||||
.context("La richiesta è riuscita, ma la risposta ricevuta da STRATZ era vuota.")?;
|
||||
|
||||
let data = data.guild
|
||||
.context("La richiesta è riuscita, ma non sono state ricevute gilde da STRATZ.")?;
|
||||
|
||||
let guild_id: GuildId = data.id.clone()
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto l'ID della gilda da STRATZ.")?
|
||||
.into();
|
||||
|
||||
if guild_id != self.guild_id {
|
||||
anyhow::bail!("La richiesta è riuscita, ma STRATZ ha risposto con le informazioni della gilda sbagliata.");
|
||||
}
|
||||
|
||||
let mut matches = data.matches
|
||||
.context("La richiesta è riuscita, ma non sono state ricevute informazioni sulle partite della gilda da STRATZ.")?;
|
||||
|
||||
// Sort matches chronologically
|
||||
matches.sort_unstable_by_key(|o| o
|
||||
.to_owned()
|
||||
.map(|o| o
|
||||
.end_date_time
|
||||
.unwrap_or(0)
|
||||
)
|
||||
.unwrap_or(0)
|
||||
);
|
||||
|
||||
let mut results: Vec<Result<(i64, Option<String>)>> = vec![];
|
||||
|
||||
for r#match in matches.iter().filter_map(|o| o.to_owned()) {
|
||||
results.push(
|
||||
self.iteration_match(&mut database, r#match).await
|
||||
);
|
||||
}
|
||||
|
||||
let results: Vec<(i64, String)> = results
|
||||
.into_iter()
|
||||
.inspect(|f| match f {
|
||||
Err(e) => log::error!("Error while processing match: {e}"),
|
||||
Ok((match_id, None)) => log::debug!("Skipping: {match_id}"),
|
||||
_ => {}
|
||||
})
|
||||
.filter_map(|f| f.ok())
|
||||
.filter_map(|f| f.1.map(|s| (f.0, s)))
|
||||
.collect();
|
||||
|
||||
for result in results {
|
||||
let (match_id, text) = result;
|
||||
|
||||
let msg = self.bot.send_message(self.chat_id, text)
|
||||
.parse_mode(teloxide::types::ParseMode::Html)
|
||||
.disable_notification(true)
|
||||
.disable_web_page_preview(true)
|
||||
.await;
|
||||
|
||||
if let Err(e) = msg {
|
||||
log::error!("Error while sending notification for match {match_id}: {e}");
|
||||
continue
|
||||
}
|
||||
|
||||
{
|
||||
use diesel::prelude::*;
|
||||
use crate::database::schema::brooch_match::dsl::*;
|
||||
use crate::database::models::{BroochMatch};
|
||||
|
||||
let match_royalnet = BroochMatch { id: result.0 };
|
||||
|
||||
let result = diesel::insert_into(brooch_match)
|
||||
.values(&match_royalnet)
|
||||
.returning(BroochMatch::as_returning())
|
||||
.get_result(&mut database);
|
||||
|
||||
if let Err(e) = result {
|
||||
log::error!("Error while inserting in database match {match_id}: {e}");
|
||||
continue
|
||||
}
|
||||
|
||||
log::trace!("Inserted in database match {match_id}!");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn iteration_match(&self, database: &mut PgConnection, r#match: Match) -> Result<(i64, Option<String>)> {
|
||||
let match_id = r#match.id
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ l'ID della partita.")?;
|
||||
|
||||
let match_royalnet = {
|
||||
use diesel::prelude::*;
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use crate::database::schema::brooch_match::dsl::*;
|
||||
use crate::database::models::{BroochMatch};
|
||||
|
||||
brooch_match
|
||||
.filter(id.eq(match_id))
|
||||
.select(BroochMatch::as_select())
|
||||
.get_result(database)
|
||||
.optional()
|
||||
.context("Non è stato possibile recuperare la partita restituita da STRATZ dal database RYG.")?
|
||||
};
|
||||
|
||||
if match_royalnet.is_some() {
|
||||
log::trace!("Match result was already sent, skipping...");
|
||||
return Ok((match_id, None));
|
||||
};
|
||||
|
||||
let match_date = r#match.end_date_time
|
||||
.context("Non è stato ricevuto da STRATZ il momento di termine della partita.")?;
|
||||
|
||||
let match_date = chrono::Utc.timestamp_opt(match_date, 0)
|
||||
.earliest()
|
||||
.context("È stato ricevuto da STRATZ un momento di termine della partita non valido.")?;
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
|
||||
// How much time has passed since the match has ended?
|
||||
let match_offset = match_date - now;
|
||||
|
||||
let mut players: Vec<Player> = r#match.players
|
||||
.context("Non è stato ricevuto da STRATZ l'elenco dei giocatori della partita.")?
|
||||
.iter()
|
||||
.filter_map(|o| o.to_owned())
|
||||
.collect();
|
||||
|
||||
if players.len() < 1 {
|
||||
anyhow::bail!("È stato ricevuto da STRATZ un elenco vuoto di giocatori nella partita.");
|
||||
}
|
||||
|
||||
let match_side: MatchSide = 'side: {
|
||||
let players_teams = {
|
||||
let players_teams_inner: Vec<Option<bool>> = players.iter()
|
||||
.map(|o| o.is_radiant)
|
||||
.collect();
|
||||
|
||||
for player_team in players_teams_inner.iter() {
|
||||
if player_team.is_none() {
|
||||
player_team.context("Non è stata ricevuta da STRATZ la squadra di almeno un giocatore nella partita.")?;
|
||||
}
|
||||
}
|
||||
|
||||
let players_teams_inner: Vec<bool> = players_teams_inner
|
||||
.iter()
|
||||
.map(|o| o.unwrap())
|
||||
.collect();
|
||||
|
||||
players_teams_inner
|
||||
};
|
||||
|
||||
let mut predicted_team = None;
|
||||
|
||||
for player_team in players_teams {
|
||||
if predicted_team.is_none() {
|
||||
predicted_team = Some(player_team)
|
||||
}
|
||||
else if predicted_team.unwrap() != player_team {
|
||||
break 'side MatchSide::Both;
|
||||
}
|
||||
}
|
||||
|
||||
match predicted_team.unwrap() {
|
||||
true => MatchSide::Radiant,
|
||||
false => MatchSide::Dire,
|
||||
}
|
||||
};
|
||||
|
||||
// Is IMP available?
|
||||
let imp_is_ready = players.iter()
|
||||
.map(|o| o.imp)
|
||||
.map(|o| o.is_some())
|
||||
.all(|o| o);
|
||||
|
||||
// Have we waited too long for IMP to be calculated?
|
||||
let imp_wait_too_long = match_offset > Self::MAX_IMP_WAIT;
|
||||
|
||||
if !(imp_is_ready || imp_wait_too_long) {
|
||||
log::trace!("IMP is not ready, waiting a bit more...");
|
||||
// Let's wait some more.
|
||||
return Ok((match_id, None));
|
||||
}
|
||||
|
||||
let match_radiant_win = r#match.did_radiant_win
|
||||
.context("Non è stato ricevuto da STRATZ il vincitore della partita.")?;
|
||||
|
||||
let match_outcome = MatchOutcome::from(&match_side, match_radiant_win);
|
||||
|
||||
let match_outcome_emoji = match_outcome.emoji();
|
||||
|
||||
let match_type = r#match.lobby_type.clone()
|
||||
.context("Non è stato ricevuta da STRATZ il tipo della partita.")?;
|
||||
|
||||
let match_type_str = match match_type {
|
||||
LobbyTypeEnum::UNRANKED => "Normale",
|
||||
LobbyTypeEnum::PRACTICE => "Torneo",
|
||||
LobbyTypeEnum::TOURNAMENT => "The International",
|
||||
LobbyTypeEnum::TUTORIAL => "Tutorial",
|
||||
LobbyTypeEnum::COOP_VS_BOTS => "Co-op",
|
||||
LobbyTypeEnum::TEAM_MATCH => "Scontro di Clan",
|
||||
LobbyTypeEnum::SOLO_QUEUE => "Coda solitaria",
|
||||
LobbyTypeEnum::RANKED => "Classificata",
|
||||
LobbyTypeEnum::SOLO_MID => "Duello",
|
||||
LobbyTypeEnum::BATTLE_CUP => "Battle Cup",
|
||||
LobbyTypeEnum::EVENT => "Evento",
|
||||
LobbyTypeEnum::DIRE_TIDE => "Diretide",
|
||||
LobbyTypeEnum::Other(t) => anyhow::bail!("Il tipo di partita ricevuto da STRATZ è sconosciuto: {}", t)
|
||||
};
|
||||
|
||||
let match_mode = r#match.game_mode.clone()
|
||||
.context("Non è stata ricevuta da STRATZ la modalità della partita.")?;
|
||||
|
||||
let match_mode_str = match match_mode {
|
||||
GameModeEnumType::NONE => "Sandbox",
|
||||
GameModeEnumType::ALL_PICK => "All Pick",
|
||||
GameModeEnumType::CAPTAINS_MODE => "Captains Mode",
|
||||
GameModeEnumType::RANDOM_DRAFT => "Random Draft",
|
||||
GameModeEnumType::SINGLE_DRAFT => "Single Draft",
|
||||
GameModeEnumType::ALL_RANDOM => "All Random",
|
||||
GameModeEnumType::INTRO => "Tutorial",
|
||||
GameModeEnumType::THE_DIRETIDE => "Diretide",
|
||||
GameModeEnumType::REVERSE_CAPTAINS_MODE => "Reverse Captains",
|
||||
GameModeEnumType::THE_GREEVILING => "The Greeviling",
|
||||
GameModeEnumType::TUTORIAL => "Tutorial",
|
||||
GameModeEnumType::MID_ONLY => "Mid Only",
|
||||
GameModeEnumType::LEAST_PLAYED => "Least Played",
|
||||
GameModeEnumType::NEW_PLAYER_POOL => "New Player",
|
||||
GameModeEnumType::COMPENDIUM_MATCHMAKING => "Compendium",
|
||||
GameModeEnumType::CUSTOM => "Arcade",
|
||||
GameModeEnumType::CAPTAINS_DRAFT => "Captains Draft",
|
||||
GameModeEnumType::BALANCED_DRAFT => "Balanced Draft",
|
||||
GameModeEnumType::ABILITY_DRAFT => "Ability Draft",
|
||||
GameModeEnumType::EVENT => "Evento",
|
||||
GameModeEnumType::ALL_RANDOM_DEATH_MATCH => "All Random Deathmatch",
|
||||
GameModeEnumType::SOLO_MID => "Mid Duel",
|
||||
GameModeEnumType::ALL_PICK_RANKED => "All Draft",
|
||||
GameModeEnumType::TURBO => "Turbo",
|
||||
GameModeEnumType::MUTATION => "Mutation",
|
||||
GameModeEnumType::UNKNOWN => anyhow::bail!("La modalità di partita ricevuto da STRATZ è sconosciuta."),
|
||||
GameModeEnumType::Other(t) => anyhow::bail!("Il tipo di partita ricevuto da STRATZ è sconosciuta: {}", t)
|
||||
};
|
||||
|
||||
let match_duration = r#match.duration_seconds
|
||||
.context("Non è stata ricevuta da STRATZ la durata della partita.")?;
|
||||
|
||||
// Let's begin writing the message
|
||||
let mut text = format!(
|
||||
"{match_outcome_emoji} <a href=\"https://stratz.com/matches/{match_id}\"><b><u>Partita #{match_id}</u></b></a>\n\
|
||||
<b>{match_type_str}</b> · {match_mode_str} · <i>{match_duration}</i>\n\
|
||||
\n\
|
||||
",
|
||||
);
|
||||
|
||||
// Let's sort players by team...
|
||||
players.sort_unstable_by_key(|o| match o.is_radiant.unwrap() {
|
||||
true => 1,
|
||||
false => 2,
|
||||
});
|
||||
|
||||
for player in players {
|
||||
let player_steam = player.steam_account.clone()
|
||||
.context("Non è stato ricevuto da STRATZ l'account Steam di almeno uno dei giocatori della partita.")?;
|
||||
|
||||
let player_steam_id = player_steam.id
|
||||
.context("Non è stato ricevuto da STRATZ lo SteamID di almeno uno dei giocatori della partita.")?;
|
||||
|
||||
let player_steam_name = player_steam.name
|
||||
.context("Non è stato ricevuto da STRATZ il display name di almeno uno dei giocatori della partita.")?;
|
||||
|
||||
let player_hero = player.hero.clone()
|
||||
.context("Non è stato ricevuto da STRATZ l'eroe giocato da almeno uno dei giocatori della partita.")?;
|
||||
|
||||
let player_hero_name = player_hero.display_name
|
||||
.context("Non è stato ricevuto da STRATZ il nome dell'eroe giocato da almeno uno dei giocatori della partita.")?;
|
||||
|
||||
|
||||
let player_telegram = {
|
||||
use diesel::prelude::*;
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use crate::database::schema::steam::dsl::*;
|
||||
use crate::database::schema::users::dsl::*;
|
||||
use crate::database::schema::telegram::dsl::*;
|
||||
use crate::database::models::TelegramUser;
|
||||
|
||||
steam
|
||||
.filter(steam_id.eq(player_steam_id))
|
||||
.inner_join(users
|
||||
.inner_join(telegram)
|
||||
)
|
||||
.select(TelegramUser::as_select())
|
||||
.get_result(database)
|
||||
.optional()
|
||||
.ok()
|
||||
.flatten()
|
||||
};
|
||||
|
||||
let player_telegram_id = player_telegram
|
||||
.map(|t| t.telegram_id);
|
||||
|
||||
text.push_str(
|
||||
&match player_telegram_id {
|
||||
Some(player_telegram_id) => format!(
|
||||
"<a href=\"tg://user?id={player_telegram_id}\"><b>{player_steam_name}</b></a> ({player_hero_name})\n"
|
||||
),
|
||||
None => format!(
|
||||
"<b>{player_steam_name}</b> ({player_hero_name})\n"
|
||||
),
|
||||
});
|
||||
|
||||
let player_role: Option<Role> = player.role.clone();
|
||||
let player_lane: Option<Lane> = player.lane.clone();
|
||||
|
||||
if let Some(player_role) = player_role {
|
||||
if let Some(player_lane) = player_lane {
|
||||
text.push_str(
|
||||
match (player_role, player_lane) {
|
||||
(Role::CORE, Lane::SAFE_LANE) => "— 1️⃣ Safe Carry\n",
|
||||
(Role::CORE, Lane::MID_LANE) => "— 2️⃣ Mid Carry\n",
|
||||
(Role::CORE, Lane::OFF_LANE) => "— 3️⃣ Off Carry\n",
|
||||
(Role::LIGHT_SUPPORT, _) => "— 4️⃣ Soft Support\n",
|
||||
(Role::HARD_SUPPORT, _) => "— 5️⃣ Hard Support\n",
|
||||
(_, Lane::JUNGLE) => "— 🔼 Jungle\n",
|
||||
(_, Lane::ROAMING) => "— 🔀 Roaming\n",
|
||||
_ => "",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let player_imp = player.imp;
|
||||
|
||||
let player_imp_emoji = 'emoji: {
|
||||
if player_imp.is_none() {
|
||||
break 'emoji ""
|
||||
}
|
||||
let player_imp = player_imp.unwrap();
|
||||
if player_imp < -50 {
|
||||
"🔲"
|
||||
} else if player_imp < -25 {
|
||||
"⬛️"
|
||||
} else if player_imp < -18 {
|
||||
"◼️"
|
||||
} else if player_imp < -9 {
|
||||
"◾️"
|
||||
} else if player_imp < 0 {
|
||||
"▪️"
|
||||
} else if player_imp <= 9 {
|
||||
"▫️"
|
||||
} else if player_imp <= 18 {
|
||||
"◽️"
|
||||
} else if player_imp <= 25 {
|
||||
"◻️"
|
||||
} else if player_imp <= 50 {
|
||||
"⬜️"
|
||||
} else {
|
||||
"🔳"
|
||||
}
|
||||
};
|
||||
|
||||
let player_kills = player.kills
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ il numero di uccisioni di almeno uno dei giocatori delle partite.")?;
|
||||
|
||||
let player_deaths = player.deaths
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ il numero di morti di almeno uno dei giocatori delle partite.")?;
|
||||
|
||||
let player_assists = player.assists
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ il numero di aiuti di almeno uno dei giocatori delle partite.")?;
|
||||
|
||||
text.push_str(&match player_imp {
|
||||
Some(player_imp) => format!(
|
||||
"— {player_imp_emoji} {player_imp} IMP ({player_kills}/{player_deaths}/{player_assists})\n"
|
||||
),
|
||||
None => format!(
|
||||
"— ❔ {player_kills}/{player_deaths}/{player_assists}\n"
|
||||
),
|
||||
});
|
||||
|
||||
if match_outcome == MatchOutcome::Clash {
|
||||
let player_is_radiant = player.is_radiant.unwrap();
|
||||
|
||||
text.push_str(match (match_radiant_win, player_is_radiant) {
|
||||
(true, true) => "🟢 Vittoria!\n",
|
||||
(false, false) => "🟢 Vittoria!\n",
|
||||
(true, false) => "🟥 Sconfitta...\n",
|
||||
(false, true) => "🟥 Sconfitta...\n",
|
||||
})
|
||||
}
|
||||
|
||||
let player_stats = player.stats.clone()
|
||||
.context("La richiesta è riuscita, ma non sono state ricevute da STRATZ le statistiche di almeno uno dei giocatori delle partite.")?;
|
||||
|
||||
let player_buffs = player_stats.match_player_buff_event.clone()
|
||||
.unwrap_or_default();
|
||||
|
||||
for _buff in player_buffs.iter().filter_map(|s| s.to_owned()) {
|
||||
// TODO: Let's do this another time.
|
||||
}
|
||||
|
||||
text.push_str("\n")
|
||||
}
|
||||
|
||||
Ok((match_id, Some(text)))
|
||||
}
|
||||
}
|
||||
|
||||
impl RoyalnetService for BroochService {
|
||||
#[allow(unreachable_code)]
|
||||
async fn run(self) -> Result<Infallible> {
|
||||
loop {
|
||||
self.iteration_request().await?;
|
||||
|
||||
sleep(Duration::new(60 * 15, 0)).await;
|
||||
}
|
||||
|
||||
anyhow::bail!("Brooch service has exited.")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MatchSide {
|
||||
Radiant,
|
||||
Dire,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MatchOutcome {
|
||||
Victory,
|
||||
Defeat,
|
||||
Clash,
|
||||
}
|
||||
|
||||
impl MatchOutcome {
|
||||
pub fn from(side: &MatchSide, radiant_win: bool) -> Self {
|
||||
match (side, radiant_win) {
|
||||
(MatchSide::Both, _) => Self::Clash,
|
||||
(MatchSide::Radiant, true) => Self::Victory,
|
||||
(MatchSide::Radiant, false) => Self::Defeat,
|
||||
(MatchSide::Dire, true) => Self::Defeat,
|
||||
(MatchSide::Dire, false) => Self::Victory,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emoji(&self) -> &'static str {
|
||||
match self {
|
||||
MatchOutcome::Victory => "🟢",
|
||||
MatchOutcome::Defeat => "🟥",
|
||||
MatchOutcome::Clash => "🔶",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ use std::convert::Infallible;
|
|||
use anyhow::Result;
|
||||
|
||||
pub mod telegram;
|
||||
pub mod brooch;
|
||||
|
||||
pub trait RoyalnetService {
|
||||
async fn run(self) -> Result<Infallible>;
|
||||
|
|
|
@ -3,19 +3,5 @@ use micronfig::config;
|
|||
// Everything ok, RustRover?
|
||||
config! {
|
||||
TELEGRAM_BOT_TOKEN,
|
||||
TELEGRAM_NOTIFICATION_CHATID?: String > i64 -> ChatIdConversionHack -> teloxide::types::ChatId,
|
||||
}
|
||||
|
||||
struct ChatIdConversionHack(i64);
|
||||
|
||||
impl From<i64> for ChatIdConversionHack {
|
||||
fn from(value: i64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChatIdConversionHack> for teloxide::types::ChatId {
|
||||
fn from(value: ChatIdConversionHack) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
TELEGRAM_NOTIFICATION_CHATID?: String > i64 -> crate::utils::hacks::ChatIdConversionHack -> teloxide::types::ChatId,
|
||||
}
|
||||
|
|
5
src/stratz/config.rs
Normal file
5
src/stratz/config.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use micronfig::config;
|
||||
|
||||
config! {
|
||||
STRATZ_TOKEN: String,
|
||||
}
|
86
src/stratz/mod.rs
Normal file
86
src/stratz/mod.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use graphql_client::GraphQLQuery;
|
||||
use reqwest::Client;
|
||||
use thiserror::Error;
|
||||
|
||||
pub(self) mod config;
|
||||
|
||||
const STRATZ_GRAPHQL_API_URL: &str = "https://api.stratz.com/graphql";
|
||||
|
||||
// Bind these weird types used in the STRATZ API
|
||||
type Short = i16;
|
||||
type Long = i64;
|
||||
type Byte = u8;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct GuildId(pub i64);
|
||||
|
||||
impl From<i64> for GuildId {
|
||||
fn from(value: i64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(schema_path="src/stratz/schema.json", query_path="src/stratz/query_guild_matches.gql", response_derives="Debug, Clone")]
|
||||
pub struct GuildMatchesQuery;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum QueryError {
|
||||
#[error("GraphQL request failed")]
|
||||
Requesting,
|
||||
#[error("GraphQL response parsing failed")]
|
||||
Parsing,
|
||||
}
|
||||
|
||||
type GuildMatchesQueryResponse = graphql_client::Response<guild_matches_query::ResponseData>;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::LobbyTypeEnum as LobbyType;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::GameModeEnumType as GameMode;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::GuildMatchesQueryGuild as Guild;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::GuildMatchesQueryGuildMatches as Match;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayers as Player;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayersHero as Hero;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayersSteamAccount as Steam;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::MatchPlayerRoleType as Role;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::MatchLaneType as Lane;
|
||||
#[allow(unused_imports)]
|
||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayersStatsMatchPlayerBuffEvent as Buff;
|
||||
|
||||
|
||||
/// Get the latest 10 matches of a certain Dota 2 guild.
|
||||
pub async fn query_guild_matches(client: &Client, guild_id: &GuildId) -> Result<GuildMatchesQueryResponse, QueryError> {
|
||||
log::debug!("Querying guild matches with {client:?} for {guild_id:?}...");
|
||||
|
||||
log::trace!("Configuring query variables...");
|
||||
let params = guild_matches_query::Variables {
|
||||
guild_id: guild_id.0,
|
||||
};
|
||||
|
||||
log::trace!("Building query...");
|
||||
let body = GuildMatchesQuery::build_query(params);
|
||||
|
||||
log::trace!("Building API URL...");
|
||||
let url = format!("{}?jwt={}", STRATZ_GRAPHQL_API_URL, config::STRATZ_TOKEN());
|
||||
log::trace!("STRATZ API URL is: {url:?}");
|
||||
|
||||
log::trace!("Making request...");
|
||||
let response = client.post(url)
|
||||
.json(&body)
|
||||
.send().await
|
||||
.map_err(|_| QueryError::Requesting)?
|
||||
.json::<GuildMatchesQueryResponse>().await
|
||||
.map_err(|_| QueryError::Parsing)?;
|
||||
|
||||
log::trace!("Request successful!");
|
||||
Ok(response)
|
||||
}
|
37
src/stratz/query_guild_matches.gql
Normal file
37
src/stratz/query_guild_matches.gql
Normal file
|
@ -0,0 +1,37 @@
|
|||
query GuildMatchesQuery($guild_id: Int!) {
|
||||
guild(id: $guild_id) {
|
||||
id
|
||||
matches(take: 10) {
|
||||
id
|
||||
lobbyType
|
||||
gameMode
|
||||
durationSeconds
|
||||
endDateTime
|
||||
didRadiantWin
|
||||
players(steamAccountId: null) {
|
||||
isRadiant
|
||||
imp
|
||||
kills
|
||||
deaths
|
||||
assists
|
||||
lane
|
||||
role
|
||||
hero {
|
||||
displayName
|
||||
}
|
||||
steamAccount {
|
||||
id
|
||||
name
|
||||
}
|
||||
stats {
|
||||
matchPlayerBuffEvent {
|
||||
time
|
||||
itemId
|
||||
abilityId
|
||||
stackCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66979
src/stratz/schema.json
generated
Normal file
66979
src/stratz/schema.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
src/utils/hacks.rs
Normal file
27
src/utils/hacks.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use teloxide::types::ChatId;
|
||||
|
||||
pub struct ChatIdConversionHack(i64);
|
||||
|
||||
impl From<i64> for ChatIdConversionHack {
|
||||
fn from(value: i64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChatIdConversionHack> for ChatId {
|
||||
fn from(value: ChatIdConversionHack) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChatIdConversionHack> for i64 {
|
||||
fn from(value: ChatIdConversionHack) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChatId> for ChatIdConversionHack {
|
||||
fn from(value: ChatId) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
pub mod time;
|
||||
pub mod version;
|
||||
pub mod hacks;
|
||||
|
|
Loading…
Reference in a new issue