Skip to main content

SecureChat SDK 4.2

Python code that joins SecureChat as a real, end-to-end encrypted client. It holds its own keys, connects through your relay with TLS pinned to the server, and speaks the same post-quantum protocol as the apps.

There are two ways to use it, and they are separate things:

🤖 Bot — Bot 👤 User session — Client
What it is A separate bot account (its name ends in bot) Your own account, running as one more of your devices
Signs in with A bot token from the app (Menu → My Bots) A session file you create once with your username + passphrase
People see Messages from @yourbot Messages from you (they show on your phone and PC too)
Style Like aiogram: handlers, filters, keyboards, FSM Like Telethon: events, send_message, iter_messages
Can Answer anyone who writes to it, show buttons and menus, guard groups as an admin Everything you can do: read your history, write to anyone, join groups
Can't Read chats it isn't in, or touch anyone's account Send bot keyboards or buttons (only bots can)
Use it for Menus, shops, games, support desks, group guards, notifications Auto-replies, archiving and searching your chats, personal automation

Rule of thumb: building something for other people: make a Bot. Automating your own account: use Client.


Contents


Install

pip install -r securechat_sdk/requirements.txt        # pynacl, websockets, cryptography

Python 3.10+. Put the securechat_sdk folder next to your script (or on PYTHONPATH), then from securechat_sdk import Bot or from securechat_sdk import Client, events.

Connecting to the server

There is nothing to configure. Like the apps, the SDK is locked to the SecureChat server: its address is built in (encoded in securechat_sdk/server_address.py: the file holds no readable address, every lock writes different bytes, and an edited file is refused), and bots and sessions connect only there.

  • They go through the relay, with TLS pinned to the server's certificate. An intercepted or tampered connection stops with AuthError before anything is sent.
  • There is no server= setting and no environment variable for it: SC_SERVER is ignored, and server= with another address raises an error.


🤖 Bots

1. Get a token

In the app: Menu → My Bots → New bot, then open the bot and choose API token → Copy. A token looks like mybot:AbC…. Anyone who has it is the bot, so treat it like a password.

bot = Bot("mybot:AbC…")            # directly (fine for a quick test; don't commit it)
bot = Bot.from_env()               # from the BOT_TOKEN environment variable, or a .bot_token file next to the script

Revoke in My Bots rotates the token. The old one stops working at once: a running bot gets TokenRevoked and stops.

2. Your first bot

from securechat_sdk import Bot, F, InlineKeyboardBuilder
from securechat_sdk.formatting import bold

bot = Bot.from_env()

@bot.command("start", description="Say hi")            # also adds /start to the bot's "/" menu
async def start(msg):
    kb = InlineKeyboardBuilder()
    kb.button(text="👍 Yes", callback_data="yes", style="success")
    kb.button(text="Docs", url="https://example.org")
    await msg.answer(f"Hi {bold(msg.from_user.full_name)}! Ready?", reply_markup=kb.as_markup())

@bot.callback_query(F.data == "yes")
async def yes(cb):
    await cb.answer("Saved")                           # a small toast in the app
    await cb.edit_message_text("You said yes ✅")

@bot.message(F.text)
async def echo(msg):
    await msg.reply(msg.text)

bot.run()
export BOT_TOKEN="mybot:AbC…"            # Windows: set BOT_TOKEN=mybot:AbC…
python my_bot.py

3. Handlers

A handler is an async function with a decorator. For each incoming event, handlers are tried in the order they were registered, and the first one whose filters match runs.

Decorator Runs for
@bot.message(*filters) every incoming message: text, photo, file, sticker, a shared contact…
@bot.command("start", "help", description="…") /start, /start args, /start@yourbot args; with description=, the command also goes into the "/" menu
@bot.callback_query(*filters) a press on one of the bot's inline buttons
@bot.chat_member(*filters) every group event: join, leave, add, remove, ban, unban, mute, unmute, promote, demote, pin, unpin, unpin_all, title, photo, slow
@bot.new_chat_members(*filters) shortcut: join / add
@bot.left_chat_member(*filters) shortcut: leave / remove / ban
@bot.on_start after every (re)connect (a good place for set_commands)
@bot.on_error async def (event, exc): a handler raised

Several filters in one decorator must all match (AND). Inside a handler, raise SkipHandler passes the event on to the next matching handler.

What a handler can ask for. The first argument is the event. Add parameters by name and the SDK fills them in:

async def handler(msg): ...
async def handler(msg, state): ...               # FSMContext for this user (section 11)
async def handler(msg, bot): ...                 # the Bot
async def handler(msg, state, bot): ...

4. Filters

Each line below is a filter; pass it to a decorator, e.g. @bot.message(F.text == "hi"):

from securechat_sdk import F, Command, Text, Regexp

F.text == "hi"                                  # exact text
F.text.startswith("!")                          # also .endswith(), .contains(), .in_([...])
F.photo                                         # truthy = present (F.document, F.sticker, F.contact, F.file …)
F.chat.type == "private"                        # attribute paths work: F.from_user.username == "alice"
Command("help")                                 # what @bot.command("help") uses
Text("Yes", "No")                               # one of these texts
Regexp(r"^order (\d+)$")                        # the match is in msg.regexp_match
(F.text == "x") & (F.chat.type == "group")      # combine with &  |  ~
lambda m: len(m.text) > 100                     # any function (sync or async) works as a filter

@bot.message(F.text.startswith("!"), F.chat.type == "group")     # several filters: all must match
async def group_command(msg): ...

F comparisons: ==, !=, <, >, <=, >=, .is_(), .in_(), .startswith(), .endswith(), .contains().

5. The message object

Message (what handlers receive):

Attribute Meaning
message_id / id the message's id
chat Chat(id, type): id is the sender's username in a private chat, "g:<id>" in a group; type is "private" or "group"; .is_private, .is_group
from_user User(username, display, is_bot): .full_name, .mention (@name), .id (= username)
text the text (the caption for a file)
ts when it was sent (unix ms)
reply_to the message it replies to (a dict), or None
file, photo, document a File (photo = an image, document = anything else), or None
caption the text of a file message
sticker {"p": pack, "i": item, "e": emoji, "m": mime} for stickers / GIFs
command, args for commands: CommandObject(command, args, raw); msg.args is the text after /start (deep-link payloads)
regexp_match set by Regexp(...)
contact Contact(user_id, first_name): the user shared their profile (request_contact button)
users_shared UsersShared(request_id, users), .user_ids (request_users button)
chat_shared ChatShared(request_id, chat_id, title, username) (request_chat button)
Method Does
await msg.answer(text, reply_markup=None, parse_mode=…) sends to the same chat
await msg.reply(text, reply_markup=None) the same, as a reply to this message
await msg.answer_photo(photo, caption="", reply_markup=None) sends a picture (bytes or a path)
await msg.edit_text(text, reply_markup=…) edits it (only the bot's own messages)
await msg.edit_reply_markup(reply_markup) changes or removes its inline buttons
await msg.delete() deletes it for everyone
await msg.react("👍") a reaction
await msg.typing() shows "typing…" for a few seconds

6. Sending, editing, deleting

All are methods of Bot; chat_id is a username or "g:<id>":

Method Notes
send_message(chat_id, text, reply_markup=None, reply_to=None, parse_mode=…) returns the sent Message
send_file(chat_id, file, caption="", filename=None, reply_markup=None, reply_to=None) file = bytes or a path; encrypted before upload. send_photo / send_document are the same method
send_sticker(chat_id, pack, item_id=None, emoji=None, reply_to=None) by the pack's link; picks item_id, else the first matching emoji, else the first sticker
send_chat_action(chat_id, "typing") "typing…"
edit_message_text(chat_id, message_id, text, reply_markup=UNSET, parse_mode=UNSET) UNSET keeps the current keyboard
edit_message_reply_markup(chat_id, message_id, reply_markup=None) None removes the buttons
delete_message(chat_id, message_id) the bot's own, or anyone's as a group admin
react(chat_id, message_id, emoji)

7. Formatting

By default (parse_mode="md") text is rendered as SecureChat markup:

You write You get
**bold** bold
__italic__ italic
~~strike~~ strike
||spoiler|| a hidden spoiler
`code` and ```pre``` monospace
[text](https://…) a link
> quote a quote

Helpers in securechat_sdk.formatting: bold, italic, strike, spoiler, code, pre(text, lang), quote, link(label, url), mention(username) and escape(text). Always escape() text that comes from users before you put it in markup. Bot(token, parse_mode=None) (or parse_mode=None on one call) sends text exactly as written.

8. Keyboards and buttons

Inline buttons (under a message):

from securechat_sdk import InlineKeyboardMarkup, InlineKeyboardButton, InlineKeyboardBuilder, WebAppInfo

kb = InlineKeyboardMarkup().row(
    InlineKeyboardButton("Buy", callback_data="buy", style="success", icon="🛒"),
    InlineKeyboardButton("Cancel", callback_data="no", style="danger"))
kb.row(InlineKeyboardButton("Open site", url="https://example.org"),
       InlineKeyboardButton("Copy code", copy_text="ABC-123"))
kb.row(InlineKeyboardButton("Search", switch_inline_query_current_chat="cats"),   # puts "@yourbot cats" in the box
       InlineKeyboardButton("Web app", web_app=WebAppInfo("https://example.org")))

# or the builder:
b = InlineKeyboardBuilder()
for n in range(1, 7):
    b.button(text=str(n), callback_data=f"n:{n}")
b.adjust(3)                                    # rows of 3 (the last size repeats: adjust(2, 3) = 2, then 3, 3…)
await msg.answer("Pick one", reply_markup=b.as_markup())

Each button takes exactly one action:

Action What it does
callback_data= sends the bot a callback (≤ 64 chars)
url= opens a web link, or jumps to a user / group / channel (@username, sc://username, sc://join/CODE)
copy_text= copies text
web_app= opens a page after a confirm
switch_inline_query_current_chat= puts @yourbot … in the input box

Styles: primary (blue), success (green), danger (red); blue / green / red work too. icon= is one emoji. Limits: 10 rows × 8 buttons, 64 characters of text.

Reply keyboards (instead of the phone's keyboard):

from securechat_sdk import (ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove, ForceReply,
                            KeyboardButtonRequestUsers, KeyboardButtonRequestChat)

menu = ReplyKeyboardMarkup([
    [KeyboardButton("📋 Menu", style="primary"), KeyboardButton("Share my profile", request_contact=True)],
    [KeyboardButton("Pick friends", request_users=KeyboardButtonRequestUsers(1, max_quantity=3)),
     KeyboardButton("Pick a group", request_chat=KeyboardButtonRequestChat(2))],
], is_persistent=True, one_time_keyboard=False, input_field_placeholder="Choose…")
await msg.answer("Main menu", reply_markup=menu)

await msg.answer("Bye", reply_markup=ReplyKeyboardRemove())                       # take it away
await msg.answer("Your name?", reply_markup=ForceReply(input_field_placeholder="Name"))  # the app opens a reply

@bot.message(F.contact)
async def got_contact(msg): ...        # msg.contact.user_id / .first_name
@bot.message(F.users_shared)
async def got_users(msg): ...          # msg.users_shared.request_id / .user_ids
@bot.message(F.chat_shared)
async def got_chat(msg): ...           # msg.chat_shared.request_id / .chat_id ("g:…") / .title

A reply keyboard stays in the user's app (kept in their encrypted vault) until you send ReplyKeyboardRemove(). A one-time keyboard hides after a press but stays behind the ⌨ button.

9. Callback queries (button presses)

@bot.callback_query(F.data.startswith("n:"))
async def picked(cb):
    n = cb.data.split(":")[1]
    await cb.answer(f"You picked {n}")                     # toast; show_alert=True for a dialog
    await cb.edit_message_text(f"Picked **{n}**")          # edit the message the button was on

CallbackQuery:

  • Attributes: id, data, from_user, chat, message_id, message.
  • Methods:
    • answer(text="", show_alert=False, url=""): url= makes the app open a link or jump to @username.
    • edit_message_text(text, reply_markup=…)
    • edit_message_reply_markup(markup)

A press you don't answer() is answered for you (the spinner on the button stops). Presses are end-to-end encrypted to the bot: the server never learns what users press.

Since SDK 4.2, a press only counts for a message this bot sent buttons on, in the chat it sent them to (others are dropped before your handler runs). Still check cb.from_user yourself when a button is meant for one person.

10. Files and stickers

@bot.message(F.photo)
async def photo(msg):
    data = await msg.photo.download()                      # bytes (downloaded and decrypted)
    await msg.answer(f"{msg.photo.name}: {len(data)} bytes")

await bot.send_file(chat, "report.pdf", caption="Monthly report")
await msg.answer_photo(open("cat.jpg", "rb").read(), caption="🐱")

await bot.send_sticker(chat, "cats_by_alice", emoji="😺")
@bot.message(F.sticker)
async def sticker(msg): await msg.answer(f"nice {msg.sticker['e']}")

File:

  • Attributes: file_id, name, size, mime, is_image.
  • Method: await file.download(), which returns the decrypted bytes.
  • The server only ever stores ciphertext.

11. Conversations (FSM)

Remember where each user is in a multi-step dialog:

from securechat_sdk import SkipHandler, State, StatesGroup

class Order(StatesGroup):
    name = State()
    address = State()

@bot.command("order")
async def order(msg, state):
    await state.set_state(Order.name)
    await msg.answer("Your name?")

@bot.message(lambda m: True)
async def steps(msg, state):
    step = await state.get_state()
    if step == str(Order.name):
        await state.update_data(name=msg.text)
        await state.set_state(Order.address)
        await msg.answer("Address?")
    elif step == str(Order.address):
        data = await state.get_data()
        await state.clear()
        await msg.answer(f"Thanks {data['name']}, shipping to {msg.text}")
    else:
        raise SkipHandler                         # not in a dialog: let other handlers have it

FSMContext methods:

  • set_state(state), get_state()
  • update_data(**kw), get_data()
  • clear() / finish()

bot.fsm(username) gets one for any user. The state lives in memory; store anything that must survive a restart yourself (e.g. in SQLite, as examples/links_bot.py does).

12. Groups and channels

Add the bot to a group like any member. Then:

  • Private groups: messages are end-to-end encrypted to the bot too.
  • Public groups / channels: it reads and posts through the server history like everyone else.

Either way msg.chat.id is "g:<id>". For most of the moderation methods below, make the bot an admin (group info → Members → the bot → Make admin).

Method Does
get_chat(chat) ChatInfo: title, kind (group / channel), public, handle, description, owner, administrators, member_count, pinned, slow_mode, members (private groups)
get_chat_member_count(chat) / get_chat_administrators(chat)
get_chat_member(chat, user) ChatMember: status (owner / admin / member / banned / left), .is_admin, .is_muted, muted_until
ban_chat_member(chat, user) / unban_chat_member banned users can't come back, even with a link
kick_chat_member(chat, user) removed, may rejoin
mute_chat_member(chat, user, seconds=None) / unmute_chat_member None = until unmuted
restrict_chat_member(chat, user, until_date=None, can_send_messages=False) Telegram-style alias of mute
promote_chat_member(chat, user, is_admin=True) owner-only on the server side
add_chat_members(chat, ["bob", "carol"]) returns those whose privacy settings only allow invite links
delete_message(chat, message_id) anyone's message
pin_chat_message / unpin_chat_message(chat, mid) / unpin_all_chat_messages(chat)
set_chat_title / set_chat_description / set_chat_photo(chat, path_or_bytes)
set_slow_mode(chat, seconds) 0, 10, 30, 60, 300, 900 or 3600
export_chat_invite_link(chat, reset=False) "sc:join/…"
delete_service_message(chat, event_id) removes "Alice joined" and similar
leave_chat(chat)

Group events come as ChatMemberUpdated:

  • Attributes: event ("join", "leave", "ban" …), user (who it happened to), actor (who did it), chat, until, title, event_id, .joined, .left.
  • Methods: await ev.answer(text), and await ev.delete() to remove the service message from the chat.
@bot.new_chat_members()
async def welcome(ev):
    await ev.answer(f"Welcome @{ev.user.username}!")

@bot.chat_member()
async def tidy(ev):                               # keep the group clean of "joined / left" lines
    if ev.event in ("join", "add", "leave", "remove"):
        await ev.delete()

Group events arrive while the bot is online; they aren't queued. See examples/guard_bot.py for a full group guard (captcha, link filter, flood control, admin commands).

13. Routers, middleware, errors

Split a big bot into parts:

from securechat_sdk import Router

shop = Router("shop")

@shop.command("buy")
async def buy(msg): ...

bot.include_router(shop)                          # routers can include routers

Middleware wraps every handler of its router (and the routers below it):

@bot.middleware
async def timing(handler, event, data):           # data: {"bot", "event", "state"}; add your own keys
    import time
    t = time.time()
    try:
        return await handler(event, data)
    finally:
        print(f"{type(event).__name__} took {time.time() - t:.3f}s")

@bot.on_error
async def oops(event, exc):
    print("handler failed:", exc)

14. The bot's profile and command menu

@bot.on_start
async def setup():
    await bot.set_commands({"start": "Open the menu", "help": "How it works"})   # or [("start", "…"), …]
    await bot.set_my_name("Pizza Bot")
    await bot.set_my_description("Order pizza in two taps")
    await bot.set_my_photo("logo.png")

Commands registered with @bot.command(..., description=…) are published automatically; set_commands replaces that list.

15. Running a bot

  • bot.run() blocks and reconnects by itself. Ctrl+C or SIGTERM shut it down cleanly, after the encryption state has been saved.
  • In your own asyncio program: await bot.run_async() and await bot.stop().
  • Encryption state: bots speak protocol v3 (a post-quantum handshake, then a new key for every message). Their session keys are kept encrypted in ./securechat_state/<bot>.e2e. Choose the folder with Bot(token, state_dir="…") or SC_STATE_DIR.
    • Keep the folder between restarts, and out of git and shared backups.
    • Deleting it is safe: the bot publishes fresh keys and senders' apps re-send whatever it couldn't read.
  • As a service: run it under systemd, NSSM or Docker with BOT_TOKEN and a persistent state_dir.
  • Privacy: a bot sees the plaintext of the chats it is in. That is what adding a bot means.

👤 User sessions

Client runs as your own account, like Telethon or Pyrogram:

  • You sign in once with your username and passphrase; the SDK saves a session file. From then on the script runs without the passphrase.
  • The session is one more of your devices. It appears in the app under Settings → Devices, and you can log it out there any time.
  • It receives every message end-to-end encrypted for itself.
  • What it sends shows on your phone and PC too.

1. Create a session file

python -m securechat_sdk.login me                    # asks: username, passphrase, a password for the file -> me.session
python -m securechat_sdk.login me --no-password      # a file WITHOUT a password (not recommended)
python -m securechat_sdk.login me --recovery         # sign in with your recovery key instead of the passphrase
python -m securechat_sdk.login me --name "Home server"   # how it's named in Settings → Devices
python -m securechat_sdk.login me --logout           # log the session out and delete the files

Or let the script do it: client.start() asks in the terminal the first time.

The session file is password-protected by default. A script then opens it with the same password: Client("me", session_password="…"), or the SC_SESSION_PASSWORD environment variable. In a terminal it asks. A script without a terminal (a service) and without the password stops with AuthError instead of guessing.

2. Your first script

from securechat_sdk import Client, events

client = Client("me")                                # uses me.session

@client.on(events.NewMessage(incoming=True, pattern=r"(?i)^ping$"))
async def pong(event):
    await event.reply("pong 🏓")

@client.on(events.NewMessage(outgoing=True, pattern=r"^!chats$"))
async def chats(event):                              # type "!chats" in any chat, from your phone or PC
    dialogs = await client.get_dialogs()
    await event.respond("\n".join(f"{d.name} · {d.unread_count} unread" for d in dialogs[:10]))

client.start()
client.run_until_disconnected()

Async style:

import asyncio
from securechat_sdk import Client

async def main():
    async with Client("me") as client:
        await client.send_message("alice", "Hello from Python 👋")
        async for m in client.iter_messages("alice", limit=20):
            print(m.date, m.sender_id, m.text)

asyncio.run(main())

TelegramClient is the same class under another name, for people coming from Telethon.

3. Events

Register with @client.on(EventType(...)) or client.add_event_handler(fn, EventType(...)); client.remove_event_handler(fn) removes one. Every event type takes:

  • chats=: one chat id or a list
  • from_users=: one username or a list
  • func=: a function that gets the event and returns True to accept it
Event Fires when The event has
events.NewMessage(incoming=, outgoing=, pattern=, …) a message arrives (incoming) or you send one, from here or your other devices (outgoing); both by default message, text, chat_id, sender_id, out, is_private, is_group, id, pattern_match; reply(), respond(), delete(), mark_read(), download_media(), get_sender(), get_chat()
events.MessageEdited(...) a message was edited (same options) the same; text is the new text
events.MessageDeleted() messages were deleted chat_id, deleted_ids, deleted_id, sender_id
events.ChatAction() group events: joins, leaves, adds, bans, title / photo changes chat_id, action, user_id, by, user_joined, user_added, user_left, user_kicked, new_title, raw
events.UserUpdate() someone is typing, or comes online / goes away user_id, chat_id, typing, online
events.Raw() every message from the server, as a dict (advanced) the dict

pattern= is a regex string, a compiled pattern or a function. Matching uses re.match, from the start of the text. raise StopPropagation (also events.StopPropagation) in a handler stops the handlers after it.

4. Client methods

Connection

Method
Client(session="securechat", session_password=None, device_name=None) (session_password defaults to SC_SESSION_PASSWORD) session: a name (me → me.session) or a path
start(username=None, passphrase=None, recovery_key=None) signs in the first time (asking for what's missing), then connects
sign_in(username, passphrase=None, recovery_key=None) writes the session file only
connect() / disconnect() with an existing session file
run_until_disconnected() blocks while events are handled (run_until_disconnected_async() in async code)
log_out() removes this session from your devices and deletes its files

Reading

Method Returns
get_me() your User
get_entity("alice" | "@handle" | "g:<id>") a User or a Chat
get_dialogs(limit=None) your chats as Dialogs, newest first
iter_messages(entity, limit=100, search=None, from_user=None, reverse=False) an async iterator of Messages, newest first. History comes from your end-to-end encrypted vault; public groups and channels come from the server's history
get_messages(entity, limit=100, …) the same, as a list
get_participants(chat) a group's members as Users
download_media(message, file=None) bytes, or saves into file (a path or a folder) and returns the path

Writing

Method
send_message(entity, text, reply_to=None, silent=False, parse_mode="md") silent=True = no notification sound
send_file(entity, file, caption="", reply_to=None, file_name=None, voice_note=False) bytes or a path
edit_message(entity, message, text) your own messages
delete_messages(entity, message_ids) one id / message, or a list
send_reaction(entity, message, emoji)
send_read_acknowledge(entity, message=None) marks the chat read (the sender sees ✓✓)
action(entity, "typing") "typing…" for a few seconds
join_chat("@handle" | "sc:join/CODE") returns the Chat; raises if the group needs approval (the request is sent)
leave_chat(chat)

5. Objects

Message

  • Attributes: id, chat_id, sender_id, text (also raw_text, message), date (UTC datetime), out (sent by you), reply_to_msg_id, file, sticker, media, edit_date, silent, fwd_from, is_private, is_group, is_reply.
  • Methods: reply(), respond(), edit(), delete(), react(), mark_read(), download_media(), get_sender(), get_chat().

User: id / username, display / first_name, handle (the @username), bot.

Chat (groups and channels): id ("g:<id>"), gid, title, kind (group / channel), public, handle, participants_count, broadcast (True for channels).

Dialog: entity (a User or Chat), id, name / title, message (the last one), date, unread_count, is_user, is_group, is_channel.

6. Keeping the session safe

me.session holds your identity key. Anyone with the file can read and send as you.

  • It's written readable by you only. Keep it out of git, chats and cloud folders.
  • It's password-protected by default (Argon2id), so a copied file is useless without the password. A file made with --no-password logs a warning every time it's used.
  • End-to-end session keys live next to it in me.session.e2e, encrypted with a key derived from your identity.
  • Lost the machine? Open the app, go to Settings → Devices and log the session out. It stops working at once.
  • Secret chats stay on the device that started them, so a session doesn't see them (the same as the apps).
  • Keys are pinned (SDK 4.2). The first key seen for each person is kept in the encrypted state file. If the server later reports a different key, the SDK keeps using the pinned one, logs a warning, and doesn't encrypt to the new key. Once you've checked the new key yourself (its safety number in the app), call client._t.accept_new_key(user, pub).
  • Someone who has used the advanced (v3) encryption with you can't be quietly switched back to v2: such messages are refused and their app is asked to send them again.
  • download_media(msg, folder) keeps the sender's file name inside folder (no paths, never overwrites).

Chat ids

You mean Write
a person or a bot their username: "alice" ("@alice" and sc://alice work in the Client)
a group or channel "g:<group id>", e.g. msg.chat.id, event.chat_id, ChatShared.chat_id
a public group by link (Client) get_entity("@handle"), join_chat("@handle")

Errors

All errors derive from SecureChatError:

Error Meaning
AuthError the token / session / passphrase was refused, or the server's pin didn't match the address
TokenRevoked (an AuthError) the bot's owner revoked the token. Make a new one and restart
SecureChatError anything else the server refused (no such user / group, not allowed …)

Environment variables

Variable Used by Meaning
BOT_TOKEN Bot.from_env() the token (or put it alone in a .bot_token file next to the script)
SC_STATE_DIR Bot where the bot keeps its encrypted session keys (default ./securechat_state)
SC_SESSION_PASSWORD Client, login the session file's password (for scripts that run unattended)

Examples

File Shows
examples/echo_bot.py the smallest bot: echo, a reply keyboard, photos and documents
examples/menu_bot.py inline keyboards: a +/- counter, URL and copy buttons, removing a keyboard
examples/links_bot.py categories and links in SQLite, a form with FSM, inline keyboards
examples/guard_bot.py a group guard: captcha for newcomers, a link filter, flood control, /ban /mute /pin /slow
examples/user_client.py a user session: !ping, !chats, welcoming people who join your groups

Troubleshooting

Problem Fix
'str' object has no attribute 'on_start' You wrote bot = "mybot:…". It must be bot = Bot("mybot:…")
AuthError right away Wrong or old token (copy it again from My Bots), or the connection to the server was intercepted
only connects to the SecureChat server it is locked to Remove server= from Bot(...) / Client(...): the address is built in
TokenRevoked The token was revoked in My Bots. Copy the new one
The bot doesn't see messages in a group Is it a member? Moderation methods also need it to be an admin
Moving the bot to another machine Copy its state_dir folder along. Without it the bot still works: it publishes new keys and senders' apps re-send what it couldn't read
me.session is password-protected Pass session_password= or set SC_SESSION_PASSWORD (a service has no terminal to ask in)
no session file me.session Run python -m securechat_sdk.login me first, or call client.start()
The session stopped working It was logged out in Settings → Devices. Create a new one

Metadata

Release files for securechat-sdk 4.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for securechat-sdk 4.2.2
File Size Uploaded Provenance Actions
securechat_sdk-4.2.2.tar.gz 79.1 kB Without provenance Details for securechat_sdk-4.2.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for securechat-sdk 4.2.2
File Interpreter ABI Platform Actions
securechat_sdk-4.2.2-py3-none-any.whl Python 3 none any Details for securechat_sdk-4.2.2-py3-none-any.whl

Total release size: 155.0 kB

Release files / securechat_sdk-4.2.2.tar.gz

Download URL securechat_sdk-4.2.2.tar.gz
Size 79.1 kB
Tags Source
SHA-256 checksum
How to use checksums
86b10a3c92d6940a7d98947ffb2e9b2b59fced9dbc5c0d2bf0d30b7abda8990a
BLAKE2b-256 checksum
How to use checksums
6657e9f2e8f4664ff26b3926f1c09c2de3435baa6122e545ff352a1a4eefbbe0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.7

Release files / securechat_sdk-4.2.2-py3-none-any.whl

Download URL securechat_sdk-4.2.2-py3-none-any.whl
Size 75.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b99f39ff1748986c311f8823e3007ace680ce75b0a6a932e3e8659171359fc96
BLAKE2b-256 checksum
How to use checksums
1981889e7fb1b26606f5d0be5ae0d57a7795ba9fe3bbda78362c41dcb729fb3b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.7

Security for securechat-sdk 4.2.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page