Skip to content

Latest commit

 

History

403 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CI Build @DickGrowerBot MAU

A game bot for group chats that let its users grow their virtual "dicks" every day for some random count of centimeters (including negative values) and compete with friends and other chat members.

Additional mechanics

(compared with some competitors)

  • The Dick of the Day daily contest to grow a randomly chosen dick for a bit more.
  • A way to play the game without the necessity to add the bot into a group (via inline queries with a callback button).
  • Import from @pipisabot and @kraft28_bot (not tested! help of its users is required).
  • PvP fights with statistics.

Soon (but not very, I guess)

  • an option to show mercy and return the award for the battle back;
  • support for those who loses battles the most;
  • more perks;
  • achievements;
  • referral promo codes;
  • global monthly events;
  • a shop.

Features

  • true system random from the environment's chaos by usage of the get_random() syscall (BCryptGenRandom on Windows, or other alternatives on different OSes);
  • localizations for English, Russian, Italian, Persian, and Chinese (Simplified & Traditional), switchable per chat via /language — and per user too when the optional user-service is enabled;
  • Prometheus-like metrics, including per-function request/error/latency metrics via autometrics;
  • OpenTelemetry distributed tracing (OTLP/gRPC), exportable to a collector such as Jaeger;
  • /support to reach the owner without exposing an email or a personal account, and SQL functions to answer a data deletion request by hand — see Support requests and data deletion in the wiki;
  • can be restricted for use in specific topics only;
  • optional self-destruction of the bot's own messages (and of the commands behind them, where the bot is an administrator) to keep a busy chat readable — configured per message group with the MSG_SELFDESTRUCT_* variables of .env.example, and by each chat's administrators for their own chat with /cleanup, which offers them a choice of delays per message group.

Technical stuff

Requirements to run

  • PostgreSQL;
  • [optional] Docker (it makes the configuration a lot easier);
  • [optional] Task to start everything with a single command (see Running locally below);
  • [optional] the user-service microservice for cross-bot user languages (see user-service integration below);
  • [for webhook mode] a frontal proxy server with TLS support (nginx-proxy, for example).

How to build the application?

cargo build/cargo check type-check SQL queries at compile time via sqlx. Unless you're relying on the offline query cache (see below), this means your local database schema must already match migrations/sqlx::migrate! only applies migrations automatically when the bot itself starts, not at build time. If a build fails with confusing SQL-query type-mismatch errors after pulling or adding a migration, apply pending migrations first (requires sqlx-cli, cargo install sqlx-cli):

cargo sqlx migrate run

Running locally

The usual dev setup runs the infrastructure in Docker (PostgreSQL, and optionally the user-service) while you run the bot itself as a local binary. docker-compose.override.yml forwards the containers' ports to localhost, so the binary reaches PostgreSQL at localhost:5432 and the user-service at localhost:${USER_SERVICE_GRPC_PORT}.

Copy the example config first: cp .env.example .env (then fill in TELOXIDE_TOKEN).

With Task installed, one command brings the infra up, migrates the database and starts the bot:

task run
Task What it does
task infra Start the full infra — PostgreSQL + user-service — in Docker (ports forwarded to localhost); alias for task infra:full
task infra:min Start only PostgreSQL (no user-service)
task run task infra, apply migrations, then run the bot with cargo run
task migrate Apply pending DB migrations (cargo sqlx migrate run)
task up Build and start the whole stack (bot included) in Docker
task down Stop and remove the Docker stack
task infra:down Stop just the infra containers

Without Task, the equivalent of task run is:

docker compose up -d --wait postgres user-service
cargo sqlx migrate run
cargo run

docker-compose.override.yml is loaded automatically by any docker compose command; it's what forwards the container ports to localhost for the local binary. task up/down opt out of it (via -f docker-compose.yml) to run the whole stack in Docker — but note that running the bot inside Docker needs a Docker-oriented .env (POSTGRES_HOST=postgres, and GRPC_ADDR_USER_SERVICE=user-service:${USER_SERVICE_GRPC_PORT} if the integration is on), which is the opposite of the localhost values .env.example ships for the local-binary flow.

Tracing / observability

The bot logs and traces through tracing, with structured fields: a log message is a constant and the values around it (chat_id, uid, error, …) are separate fields, so the log database can filter and count them instead of matching text. RUST_LOG controls the verbosity, of both the console and the export.

Console output is always on — it is the fallback when the observability stack is not configured or cannot be reached. Two more variables send the data to that stack, each optional and independent:

  • OTEL_EXPORTER_OTLP_ENDPOINT — the spans, over OTLP/gRPC;
  • OTEL_EXPORTER_OTLP_LOGS_ENDPOINT — the log records, over OTLP/HTTP (the full URL, path included). The exported records carry the trace and span ids of the span they were written in, put there by the SDK, so a log line and a trace can be matched.

docker-compose.yml bundles an optional observability stack, gated behind the tracing Compose profile: Jaeger all-in-one for the spans and VictoriaLogs for the records. The task infra/infra:full tasks start both automatically (they name them, which activates the profile), and docker-compose.override.yml forwards their ports to localhost — so in the local-binary dev flow the Jaeger UI is at http://localhost:16686, OTLP at localhost:4317, and the VictoriaLogs UI at http://localhost:9428/select/vmui/. For the full-stack-in-Docker flow (task up, which skips the override), enable it with COMPOSE_PROFILES=tracing; there they stay on the Compose network and the in-Docker bot reaches them at jaeger:4317 and victoria-logs:9428. Set the two variables accordingly — see .env.example, which ships both forms commented out.

The trace of an update is rooted at the handler that processes it, not at the HTTP request that brought it in: the webhook handler only parses the update and hands it to the dispatcher, so an HTTP span would be empty. Outbound user-service (gRPC) calls are auto-instrumented, and the trace context propagates into the user-service.

Independently of tracing, the /metrics endpoint (port 8080) exposes Prometheus metrics: HTTP metrics from axum-prometheus, the bot's own domain counters, and per-function request/error/latency metrics generated by autometrics on the handlers and repository methods.

Every call the bot makes to the Telegram Bot API is measured as well: telegram_request_duration_seconds{method,outcome} records how long it took (the failed ones included, so a timeout doesn't go missing), it gets a client span of its own in the trace, and a request the API rejects without a proper error code is logged together with the payload that caused it.

The cache

Everything the bot keeps briefly — cached settings, locks, unfinished dialogues — lives in one store instead of a map per feature. Almost nothing there is a source of truth, so a store that is down, slow or absent costs only the work it would have saved: the bot starts and runs either way.

Without REDIS_HOST the values stay in the process, which is all a single instance needs and not a degraded mode. A server adds a dialogue that survives a restart, and correctness once there is more than one instance, for a round trip on each miss. The container runs Valkey, the BSD-licensed fork; the protocol is Redis, which the variables are named after, and .env.example describes them.

user-service integration

user-service is a small gRPC microservice that stores a user's preferred interface language and shares it across all of SadBot.Dev's bots. When enabled, the bot offers a personal /language command in private chats and honours each user's saved language. When disabled, languages come from Telegram as before and /language is hidden from private chats — the chat-wide /language for group admins keeps working regardless, since it's stored in the bot's own database.

docker-compose.yml bundles the user-service container, gated behind the user-service Compose profile — the task infra/infra:full tasks start it automatically (they name it, which activates its profile), while a bare docker compose up / task up starts it only when COMPOSE_PROFILES=user-service is set. It shares the bot's PostgreSQL server but uses its own userservicedb database, provisioned by postgres/init-user-service-db.sh (which only runs on a fresh data volume — on an existing ./data, create the database once by hand; the script's header shows the command).

Enable the integration by setting GRPC_ADDR_USER_SERVICE in .env, choosing the host by where the bot runs:

  • bot as a local binary (infra in Docker): GRPC_ADDR_USER_SERVICE=localhost:${USER_SERVICE_GRPC_PORT};
  • bot inside docker-compose: GRPC_ADDR_USER_SERVICE=user-service:${USER_SERVICE_GRPC_PORT}.

See the user-service block in .env.example for the related variables (cache TTLs, gRPC timeout, and the service's database credentials).

How to rebuild .sqlx queries?

(to build the application without a running RDBMS)

cargo sqlx prepare -- --tests

Adjustment hints

It's most probably you want to change the value of the GROW_SHRINK_RATIO environment variable to make the players upset and disappointed more or less often.

The reward for playing every day is STREAK_BONUS_RATIO_PER_DAY (0.05 by default): each consecutive day multiplies the rolled value by that much more, counting up to STREAK_BONUS_MAX_DAYS (20) days, so three weeks of playing doubles it. A negative roll is multiplied just the same. Setting either variable to 0 turns the perk off, as does DISABLE_STREAK.

How to disable a command?

Most of the command can be hidden from both lists: command hints and inline results. To do so, specify an environment variable like DISABLE_CMD_STATS (where STATS is a command key) with any value. Don't forget to pass this variable to the container by adding it to the docker-compose.yml file!

About

Who has the biggest dick ever? A game bot for Telegram

Topics

Resources

Stars

136 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages