From 246af8ec200125cebb72f2ae976fe2f272d0ebab Mon Sep 17 00:00:00 2001 From: jdp5949 Date: Sat, 6 Jun 2026 17:51:30 -0400 Subject: [PATCH] feat: add status command (Python) + document all CLI commands and per-language usage (v0.3.0) - Python gains 'status' (@SpamBot check), live-tested - docs hub: full command table, per-language CLI invocation, per-language library snippets - README: status command + link to per-language usage --- README.md | 6 +++ docs/index.md | 88 ++++++++++++++++++++++++++++++++++++++----- pyproject.toml | 2 +- tests/test_smoke.py | 4 +- tg_ringer/__init__.py | 2 +- tg_ringer/cli.py | 14 +++++++ tg_ringer/client.py | 10 +++++ 7 files changed, 114 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9c968c4..bd31f0e 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,14 @@ echo "piped body" | tg-ringer msg @someuser # Who am I logged in as? tg-ringer whoami + +# Check anti-spam status via @SpamBot +tg-ringer status ``` +Full command reference and **per-language usage** (Go/Node/Rust/Java invocation + +library snippets): **https://jdp5949.github.io/tg-ringer/#usage** + ### In scripts ```bash diff --git a/docs/index.md b/docs/index.md index 8e06bdc..cacbfe9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -107,21 +107,55 @@ That's it. `login` walks you through setup the first time; later runs just sign ## Usage -### CLI +### Commands (all languages) + +| Command | What it does | +|---------|--------------| +| `login` | Interactive setup (api_id/api_hash) + sign in | +| `init` | (Re)configure credentials only | +| `config` | Show current config (api_hash masked) | +| `call TARGET [secs]` | Ring a user/number, then hang up | +| `msg TARGET TEXT…` | Send a direct message | +| `whoami` | Show the logged-in account | +| `status` | Check anti-spam state via `@SpamBot` | + +`TARGET` = `@username`, numeric id, or `+E164` phone number. + +> `status` is available in Python, Go, Rust, and Java (Rust/Java call the Go binary). +> Node gains it in an upcoming release. + +### Running the CLI in each language + +```bash +# Python (pip install tg-ringer) +tg-ringer call +15551234567 + +# Go (downloaded binary or `go install`) +tg-ringer call +15551234567 + +# Rust (downloaded binary or `cargo install`) +tg-ringer call +15551234567 + +# Node (npm install github:jdp5949/tg-ringer-js) +npx tg-ringer call +15551234567 + +# Java (downloaded runnable jar) +java -jar tg-ringer-java-0.1.0.jar call +15551234567 +``` + +Examples (any CLI): ```bash -tg-ringer init # (re)configure credentials interactively -tg-ringer config # show current config (api_hash masked) -tg-ringer call +15551234567 # ring a number -tg-ringer call @someuser --seconds 30 -tg-ringer call # ring the default target +tg-ringer login # first-time setup + sign in +tg-ringer call # ring the default target (TG_TARGET) +tg-ringer call @someuser 30 # ring @someuser for 30s tg-ringer msg +15551234567 "deploy finished" -echo "piped body" | tg-ringer msg @someuser -tg-ringer whoami +tg-ringer status # is my account flagged? ``` -### Python +### Library usage per language +**Python** ```python import asyncio from tg_ringer import TgCaller @@ -134,6 +168,42 @@ async def main(): asyncio.run(main()) ``` +**Go** +```go +import "github.com/jdp5949/tg-ringer-go/ringer" + +ringer.Run(ctx, cfg, func(ctx context.Context, c *ringer.Client) error { + _, err := c.Ring(ctx, "+15551234567", 20) + return err +}) +``` + +**Node / TypeScript** +```ts +import { TgRinger } from "tg-ringer"; + +const r = new TgRinger({ apiId, apiHash, session }); +await r.connect(); +await r.ring("+15551234567", 20); +await r.message("+15551234567", "heads up"); +await r.disconnect(); +``` + +**Rust** +```rust +tg_ringer::ring("+15551234567", 20)?; +tg_ringer::message("+15551234567", "heads up")?; +``` + +**Java** +```java +import io.github.jdp5949.tgringer.TgRinger; + +TgRinger tg = new TgRinger(); +tg.ring("+15551234567", 20); +tg.message("+15551234567", "heads up"); +``` + ### Configuration `tg-ringer login`/`init` save everything for you to `~/.config/tg-ringer/config` diff --git a/pyproject.toml b/pyproject.toml index f3980a3..e456e27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "tg-ringer" -version = "0.2.0" +version = "0.3.0" description = "Ring (call) and message any Telegram user from your own account — urgent alerts via a real Telegram call." readme = "README.md" requires-python = ">=3.9" diff --git a/tests/test_smoke.py b/tests/test_smoke.py index e4c38f0..8683f02 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -36,7 +36,9 @@ def test_cli_requires_subcommand(): assert exc.value.code != 0 -@pytest.mark.parametrize("sub", ["init", "login", "call", "msg", "whoami", "config"]) +@pytest.mark.parametrize( + "sub", ["init", "login", "call", "msg", "whoami", "config", "status"] +) def test_subcommand_help(sub, capsys): with pytest.raises(SystemExit) as exc: cli.main([sub, "--help"]) diff --git a/tg_ringer/__init__.py b/tg_ringer/__init__.py index b4c046e..4b0ffc4 100644 --- a/tg_ringer/__init__.py +++ b/tg_ringer/__init__.py @@ -20,4 +20,4 @@ async def main(): from .client import TgCaller __all__ = ["TgCaller"] -__version__ = "0.2.0" +__version__ = "0.3.0" diff --git a/tg_ringer/cli.py b/tg_ringer/cli.py index 084d320..0efa08b 100644 --- a/tg_ringer/cli.py +++ b/tg_ringer/cli.py @@ -6,6 +6,7 @@ tg-ringer call TARGET [-s N] ring a user/number for N seconds tg-ringer msg TARGET TEXT send a direct message tg-ringer whoami show the logged-in userbot account + tg-ringer status check anti-spam status via @SpamBot tg-ringer config show current config (api_hash masked) Config is read from (first wins): environment variables, then @@ -176,6 +177,16 @@ async def go(tg): _run(go) +def cmd_status(_args) -> None: + async def go(tg): + print("asking @SpamBot ...") + reply = await tg.spam_status() + print("---") + print(reply) + + _run(go) + + def main(argv=None) -> None: p = argparse.ArgumentParser( prog="tg-ringer", @@ -204,6 +215,9 @@ def main(argv=None) -> None: sub.add_parser("whoami", help="show logged-in account").set_defaults( func=cmd_whoami ) + sub.add_parser("status", help="check anti-spam status via @SpamBot").set_defaults( + func=cmd_status + ) args = p.parse_args(argv) args.func(args) diff --git a/tg_ringer/client.py b/tg_ringer/client.py index f4611b5..5285091 100644 --- a/tg_ringer/client.py +++ b/tg_ringer/client.py @@ -115,3 +115,13 @@ async def message(self, target, text: str) -> int: async def whoami(self): """Return the logged-in userbot account (Telethon User).""" return await self.client.get_me() + + async def spam_status(self) -> str: + """Ask ``@SpamBot`` for this account's anti-spam status; return its reply.""" + import asyncio + + await self.client.send_message("SpamBot", "/start") + await asyncio.sleep(3) + async for msg in self.client.iter_messages("SpamBot", limit=1): + return msg.text or "(no reply yet — try again in a moment)" + return "(no reply yet — try again in a moment)"