Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
88 changes: 79 additions & 9 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
2 changes: 1 addition & 1 deletion tg_ringer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ async def main():
from .client import TgCaller

__all__ = ["TgCaller"]
__version__ = "0.2.0"
__version__ = "0.3.0"
14 changes: 14 additions & 0 deletions tg_ringer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions tg_ringer/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Loading