Add references (removed inner git)

This commit is contained in:
hongz
2026-02-03 01:03:29 +08:00
parent 89d4260eb8
commit a44621558c
18 changed files with 2437 additions and 1 deletions

Submodule references/matrix-bot-chat-reference deleted from 972a538356

View File

@@ -0,0 +1,3 @@
__pycache__/config.cpython-311.pyc
services/__pycache__/matrix_service.cpython-311.pyc

View File

@@ -0,0 +1,9 @@
FROM python:3.11-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
# 设定环境变量
# ENV PYTHONUNBUFFERED=1
CMD ["python", "main.py"]

View File

@@ -0,0 +1,59 @@
# config.py
MATRIX_SERVER = "https://person.bnicetome.top"
USERNAME = "bots1"
PASSWORD = "bots1@bots1"
# 房间配置
ROOM_IDS = [
"!jTyjRTQNpAQaHGEjer:person.bnicetome.top", # 调试房间(未加密)
"!QDamBNNgTvbmFNXEFo:person.bnicetome.top", # 用户使用房间(未加密)
]
# 默认房间ID向后兼容
ROOM_ID = ROOM_IDS[0]
# E2EE配置
STORE_PATH = "store" # 存储密钥和会话信息的目录
DEVICE_ID = "BOTDEVICE2" # 设备ID
DEVICE_NAME = "MatrixBot" # 设备名称
# xAI API配置
XAI_API_KEY = (
"xai-Irz5BaEdnraGMHeFmIuTWx85srNRX5rrmXiRofaroFHrXh4dWGynC0B6hhwhUeU70Is5rSow1lHiSSVu"
)
XAI_MODEL_DIYNAME = "马斯克的X平台的AI(GROK新起之秀有免费额度推荐使用)"
XAI_BASE_URL = "https://api.x.ai/v1"
XAI_DAILY_LIMIT = 25 # 每天多少条
# OpenRouter API配置
OPENROUTER_API_KEY = (
"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"
)
OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions"
# AI模型配置
OPENROUTER_MODELS = {
# "qwen-7b": {
# "model": "qwen/qwen-2-7b-instruct:free",
# "name": "通义千问2-7B(阿里巴巴基础免费版,适合问日常简单问题)",
# "daily_limit": -1 # 无限制
# },
"qwen-72b": {
"model": "qwen/qwen-2.5-72b-instruct",
"name": "通义千问2.5-72B(阿里巴巴升级版付费模型,适合一些复杂问题,写作,文案)",
"daily_limit": 50 # 每天多少条
},
"chatgpt-4o": {
"model": "openai/chatgpt-4o-latest",
"name": "OPENAI ChatGPT-4o最新版(世界公认最强大AI知识最丰富目前最强的超大模型专业性问题和更正确的回答)",
"daily_limit": 20 # 每天多少条
}
}
# 默认模型(用于向后兼容)
OPENROUTER_MODEL = OPENROUTER_MODELS["qwen-72b"]["model"]
# AI对话配置
AI_CHAT_TIMEOUT_MINUTES = 15 # AI对话闲置超时时间分钟
AI_RESPONSE_TIMEOUT_MINUTES = 5 # AI响应超时时间分钟

View File

@@ -0,0 +1 @@
{"access_token": "syt_Ym90czE_gbQAFJKVPLGyukPyWBZh_26VocT", "device_id": "BOTDEVICE", "user_id": "@bots1:person.bnicetome.top"}

View File

@@ -0,0 +1,7 @@
version: '3.8'
services:
myapp:
build: .
restart: always
volumes:
- .:/app # 将当前目录挂载到容器的 /app 目录

View File

@@ -0,0 +1,53 @@
# main.py
import asyncio
from services.matrix_service import MatrixService
async def command_listener(matrix_service):
while True:
print("Command Menu:")
print("1. Send Message")
print("q. Quit")
user_input = input("Enter command: ")
if user_input == "1":
message = input("Enter message to send: ")
await matrix_service.send_message(message)
print("Message sent!")
elif user_input == "q":
print("Exiting command listener...")
break
else:
print("Invalid command. Please try again.")
async def main():
matrix_service = MatrixService()
try:
# 登录(会自动加入配置的所有房间)
await matrix_service.login()
# 先进行一次同步以确保房间状态正确
print("正在同步房间状态...")
await matrix_service.client.sync()
# 启动同步任务
sync_task = asyncio.create_task(matrix_service.sync())
# 等待同步任务完成(实际上会一直运行)
await sync_task
except KeyboardInterrupt:
print("\n正在关闭服务...")
await matrix_service.close()
except Exception as e:
print(f"发生错误: {e}")
await matrix_service.close()
finally:
await matrix_service.close()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,5 @@
Flask
matrix-nio[e2e]
requests
schedule
openai

View File

@@ -0,0 +1,32 @@
import asyncio
from nio import AsyncClient, MatrixRoom, RoomMessageText
async def message_callback(room: MatrixRoom, event: RoomMessageText) -> None:
print(
f"Message received in room {room.display_name}\n"
f"{room.user_name(event.sender)} | {event.body}"
)
async def main() -> None:
client = AsyncClient("https://matrix.example.org", "@alice:example.org")
client.add_event_callback(message_callback, RoomMessageText)
print(await client.login("my-secret-password"))
# "Logged in as @alice:example.org device id: RANDOMDID"
# If you made a new room and haven't joined as that user, you can use
# await client.join("your-room-id")
await client.room_send(
# Watch out! If you join an old room you'll see lots of old messages
room_id="!my-fave-room:example.org",
message_type="m.room.message",
content={"msgtype": "m.text", "body": "Hello world!"},
)
await client.sync_forever(timeout=30000) # milliseconds
asyncio.run(main())

View File

@@ -0,0 +1,332 @@
import asyncio
import json
import os
import sys
from typing import Optional
import aiofiles
from nio import (
AsyncClient,
ClientConfig,
InviteEvent,
LoginResponse,
MatrixRoom,
RoomMessageText,
crypto,
exceptions,
)
# This is a fully-documented example of how to do manual verification with nio,
# for when you already know the device IDs of the users you want to trust. If
# you want live verification using emojis, the process is more complicated and
# will be covered in another example.
# We're building on the restore_login example here to preserve device IDs and
# therefore preserve trust; if @bob trusts @alice's device ID ABC and @alice
# restarts this program, loading the same keys, @bob will preserve trust. If
# @alice logged in again @alice would have new keys and a device ID XYZ, and
# @bob wouldn't trust it.
# The store is where we want to place encryption details like our keys, trusted
# devices and blacklisted devices. Here we place it in the working directory,
# but if you deploy your program you might consider /var or /opt for storage
STORE_FOLDER = "nio_store/"
# This file is for restoring login details after closing the program, so you
# can preserve your device ID. If @alice logged in every time instead, @bob
# would have to re-verify. See the restoring login example for more into.
SESSION_DETAILS_FILE = "credentials.json"
# Only needed for this example, this is who @alice will securely
# communicate with. We need all the device IDs of this user so we can consider
# them "trusted". If an unknown device shows up (like @bob signs into their
# account on another device), this program will refuse to send a message in the
# room. Try it!
BOB_ID = "@bob:example.org"
BOB_DEVICE_IDS = [
# You can find these in Riot under Settings > Security & Privacy.
# They may also be called "session IDs". You'll want to add ALL of them here
# for the one other user in your encrypted room
"URDEVICEID",
]
# the ID of the room you want your bot to join and send commands in.
# This can be a direct message or room; Matrix treats them the same
ROOM_ID = "!myfavouriteroom:example.org"
ALICE_USER_ID = "@alice:example.org"
ALICE_HOMESERVER = "https://matrix.example.org"
ALICE_PASSWORD = "hunter2"
class CustomEncryptedClient(AsyncClient):
def __init__(
self,
homeserver,
user="",
device_id="",
store_path="",
config=None,
ssl=None,
proxy=None,
):
# Calling super.__init__ means we're running the __init__ method
# defined in AsyncClient, which this class derives from. That does a
# bunch of setup for us automatically
super().__init__(
homeserver,
user=user,
device_id=device_id,
store_path=store_path,
config=config,
ssl=ssl,
proxy=proxy,
)
# if the store location doesn't exist, we'll make it
if store_path and not os.path.isdir(store_path):
os.mkdir(store_path)
# auto-join room invites
self.add_event_callback(self.cb_autojoin_room, InviteEvent)
# print all the messages we receive
self.add_event_callback(self.cb_print_messages, RoomMessageText)
async def login(self) -> None:
"""Log in either using the global variables or (if possible) using the
session details file.
NOTE: This method kinda sucks. Don't use these kinds of global
variables in your program; it would be much better to pass them
around instead. They are only used here to minimise the size of the
example.
"""
# Restore the previous session if we can
# See the "restore_login.py" example if you're not sure how this works
if os.path.exists(SESSION_DETAILS_FILE) and os.path.isfile(
SESSION_DETAILS_FILE
):
try:
async with aiofiles.open(SESSION_DETAILS_FILE) as f:
contents = await f.read()
config = json.loads(contents)
self.access_token = config["access_token"]
self.user_id = config["user_id"]
self.device_id = config["device_id"]
# This loads our verified/blacklisted devices and our keys
self.load_store()
print(
f"Logged in using stored credentials: {self.user_id} on {self.device_id}"
)
except OSError as err:
print(f"Couldn't load session from file. Logging in. Error: {err}")
except json.JSONDecodeError:
print("Couldn't read JSON file; overwriting")
# We didn't restore a previous session, so we'll log in with a password
if not self.user_id or not self.access_token or not self.device_id:
# this calls the login method defined in AsyncClient from nio
resp = await super().login(ALICE_PASSWORD)
if isinstance(resp, LoginResponse):
print("Logged in using a password; saving details to disk")
self.__write_details_to_disk(resp)
else:
print(f"Failed to log in: {resp}")
sys.exit(1)
def trust_devices(self, user_id: str, device_list: Optional[str] = None) -> None:
"""Trusts the devices of a user.
If no device_list is provided, all of the users devices are trusted. If
one is provided, only the devices with IDs in that list are trusted.
Arguments:
user_id {str} -- the user ID whose devices should be trusted.
Keyword Arguments:
device_list {Optional[str]} -- The full list of device IDs to trust
from that user (default: {None})
"""
print(f"{user_id}'s device store: {self.device_store[user_id]}")
# The device store contains a dictionary of device IDs and known
# OlmDevices for all users that share a room with us, including us.
# We can only run this after a first sync. We have to populate our
# device store and that requires syncing with the server.
for device_id, olm_device in self.device_store[user_id].items():
if device_list and device_id not in device_list:
# a list of trusted devices was provided, but this ID is not in
# that list. That's an issue.
print(
f"Not trusting {device_id} as it's not in {user_id}'s pre-approved list."
)
continue
if user_id == self.user_id and device_id == self.device_id:
# We cannot explicitly trust the device @alice is using
continue
self.verify_device(olm_device)
print(f"Trusting {device_id} from user {user_id}")
def cb_autojoin_room(self, room: MatrixRoom, event: InviteEvent):
"""Callback to automatically joins a Matrix room on invite.
Arguments:
room {MatrixRoom} -- Provided by nio
event {InviteEvent} -- Provided by nio
"""
self.join(room.room_id)
room = self.rooms[ROOM_ID]
print(f"Room {room.name} is encrypted: {room.encrypted}")
async def cb_print_messages(self, room: MatrixRoom, event: RoomMessageText):
"""Callback to print all received messages to stdout.
Arguments:
room {MatrixRoom} -- Provided by nio
event {RoomMessageText} -- Provided by nio
"""
if event.decrypted:
encrypted_symbol = "🛡 "
else:
encrypted_symbol = "⚠️ "
print(
f"{room.display_name} |{encrypted_symbol}| {room.user_name(event.sender)}: {event.body}"
)
async def send_hello_world(self):
# Now we send an encrypted message that @bob can read, although it will
# appear to be "unverified" when they see it, because @bob has not verified
# the device @alice is sending from.
# We'll leave that as an exercise for the reader.
try:
await self.room_send(
room_id=ROOM_ID,
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": "Hello, this message is encrypted",
},
)
except exceptions.OlmUnverifiedDeviceError:
print("These are all known devices:")
device_store: crypto.DeviceStore = device_store # noqa: F821
[
print(
f"\t{device.user_id}\t {device.device_id}\t {device.trust_state}\t {device.display_name}"
)
for device in device_store
]
sys.exit(1)
@staticmethod
def __write_details_to_disk(resp: LoginResponse) -> None:
"""Writes login details to disk so that we can restore our session later
without logging in again and creating a new device ID.
Arguments:
resp {LoginResponse} -- the successful client login response.
"""
with open(SESSION_DETAILS_FILE, "w") as f:
json.dump(
{
"access_token": resp.access_token,
"device_id": resp.device_id,
"user_id": resp.user_id,
},
f,
)
async def run_client(client: CustomEncryptedClient) -> None:
"""A basic encrypted chat application using nio."""
# This is our own custom login function that looks for a pre-existing config
# file and, if it exists, logs in using those details. Otherwise it will log
# in using a password.
await client.login()
# Here we create a coroutine that we can call in asyncio.gather later,
# along with sync_forever and any other API-related coroutines you'd like
# to do.
async def after_first_sync():
# We'll wait for the first firing of 'synced' before trusting devices.
# client.synced is an asyncio event that fires any time nio syncs. This
# code doesn't run in a loop, so it only fires once
print("Awaiting sync")
await client.synced.wait()
# In practice, you want to have a list of previously-known device IDs
# for each user you want to trust. Here, we require that list as a
# global variable
client.trust_devices(BOB_ID, BOB_DEVICE_IDS)
# In this case, we'll trust _all_ of @alice's devices. NOTE that this
# is a SUPER BAD IDEA in practice, but for the purpose of this example
# it'll be easier, since you may end up creating lots of sessions for
# @alice as you play with the script
client.trust_devices(ALICE_USER_ID)
await client.send_hello_world()
# We're creating Tasks here so that you could potentially write other
# Python coroutines to do other work, like checking an API or using another
# library. All of these Tasks will be run concurrently.
# For more details, check out https://docs.python.org/3/library/asyncio-task.html
# ensure_future() is for Python 3.5 and 3.6 compatibility. For 3.7+, use
# asyncio.create_task()
after_first_sync_task = asyncio.ensure_future(after_first_sync())
# We use full_state=True here to pull any room invites that occurred or
# messages sent in rooms _before_ this program connected to the
# Matrix server
sync_forever_task = asyncio.ensure_future(
client.sync_forever(30000, full_state=True)
)
await asyncio.gather(
# The order here IS significant! You have to register the task to trust
# devices FIRST since it awaits the first sync
after_first_sync_task,
sync_forever_task,
)
async def main():
# By setting `store_sync_tokens` to true, we'll save sync tokens to our
# store every time we sync, thereby preventing reading old, previously read
# events on each new sync.
# For more info, check out https://matrix-nio.readthedocs.io/en/latest/nio.html#asyncclient
config = ClientConfig(store_sync_tokens=True)
client = CustomEncryptedClient(
ALICE_HOMESERVER,
ALICE_USER_ID,
store_path=STORE_FOLDER,
config=config,
ssl=False,
proxy="http://localhost:8080",
)
try:
await run_client(client)
except (asyncio.CancelledError, KeyboardInterrupt):
await client.close()
# Run the main coroutine, which instantiates our custom subclass, trusts all the
# devices, and syncs forever (or until your press Ctrl+C)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass

View File

@@ -0,0 +1,63 @@
要实现你的需求,我们可以利用 matrix-nio 的持久化功能,通过将加密会话信息保存到本地存储,确保 BOT 重启后能够恢复与私聊房间的加密连接并继续通信。下面是基于 matrix-nio 最新文档的实现步骤:
1. 配置持久化存储
matrix-nio 提供 CryptoStore 接口来管理加密会话的存储。可以使用 SqliteStore 作为持久化存储方式,确保所有会话状态在 BOT 重启后可以恢复。
python
from nio import AsyncClient, AsyncClientConfig, SqliteStore
# 配置持久化存储的路径
store_path = "store/" # 存储密钥信息的目录
db_path = store_path + "nio_store.db"
# 配置客户端
client = AsyncClient(
homeserver,
username,
device_id=device_id,
store_path=store_path,
config=AsyncClientConfig(encryption_enabled=True, store=SqliteStore(db_path))
)
2. 确保加密功能开启
启用端到端加密E2EE功能并确认私聊房间的密钥交换成功。AsyncClientConfig 中的 encryption_enabled 参数设置为 True。
3. 使用同步方法以保存加密状态
配置好客户端后,执行初次同步以加载房间状态和成员数据。这一步非常关键,因为 matrix-nio 的加密依赖初次同步的数据。
python
await client.sync_forever(timeout=30000, full_state=True)
4. 处理加密消息
为私聊房间建立加密通信,并处理重新连接后的消息解密。
python
@client.add_event_callback(RoomEncryptedEvent, RoomEncryptedEvent)
async def encrypted_message_handler(room, event):
# 确保消息解密后再做处理
decrypted_event = await client.decrypt_event(event)
if decrypted_event:
print("Decrypted message: ", decrypted_event.body)
else:
print("Failed to decrypt message.")
5. 关闭并保存会话状态
在 BOT 关闭时,确保会话和加密状态都保存至 STORE 目录中的数据库。
python
async def close_client():
await client.close()
print("Client closed and session saved.")
6. 重启时加载存储的加密状态
当 BOT 重启时matrix-nio 会自动从指定的 STORE 路径中加载加密状态,无需额外的恢复步骤。
这样,你的程序在与用户私聊房间建立加密连接后,会将关键的加密信息存入 STORE 目录,确保即使 BOT 重启也能够从存储中恢复这些状态,继续进行加密的私聊通信。

View File

@@ -0,0 +1,41 @@
It appears that SqliteStore is no longer available directly in matrix-nio's recent versions, which means the documentation might not reflect current capabilities accurately. Heres an alternative approach to set up a persistence layer using DefaultCryptoStore in matrix-nio for handling encryption without relying on SqliteStore.
Heres how to set it up:
Install the Required E2E Encryption Dependencies: Make sure the encryption dependencies are installed:
bash
pip install 'matrix-nio[e2e]'
Configure DefaultCryptoStore: The DefaultCryptoStore can be used to persist encryption keys, allowing your bot to retain its encryption state across restarts. Heres a code snippet to configure it:
python
from nio import AsyncClient, DefaultCryptoStore
import os
# Set up your bot's storage directory
store_path = "store" # Ensure this directory exists
# Initialize the AsyncClient with the DefaultCryptoStore for E2E encryption
client = AsyncClient(
"https://your.matrix.server",
"your_bot_username",
store_path=store_path
)
# Configure the client to use the default crypto store
client.crypto_store = DefaultCryptoStore(
client.user_id,
client.device_id,
store_path
)
# Now your client will store encryption keys under `store_path`
Sync and Persist Keys: After setting up, the bot should automatically save keys during communication. Make sure your store_path directory is persistent across reboots, as the stored data within it allows the bot to pick up encrypted chats seamlessly on restart.
Run the Client and Sync: To maintain encrypted communication, call client.sync_forever() or client.sync() in your main loop as usual. The DefaultCryptoStore will manage loading and saving encryption keys without needing SqliteStore.
This setup should persist encryption keys across sessions. Let me know if you encounter further issues, and I can guide you through additional configuration steps if necessary.

View File

@@ -0,0 +1,53 @@
To implement a Python function using OpenRouters API to send content to an AI model, receive results, and track the costs, you can follow this approach:
1. **API Request for Chat Completion**:
- Youll need to send a `POST` request to `https://openrouter.ai/api/v1/chat/completions`, including the model identifier, your message content, and your API key in the headers.
- The request body should include the AI model you want to use (e.g., `"openai/gpt-3.5-turbo"`) and the `messages` array.
2. **Tracking Costs**:
- The response will contain `usage` data, which includes `prompt_tokens`, `completion_tokens`, and `total_tokens`. This data allows you to calculate the cost based on token usage directly from the response.
- Alternatively, to get detailed cost data, OpenRouter provides the `/api/v1/generation` endpoint. You can use the `id` from your initial response to query for specific stats, such as exact token counts and the calculated cost.
3. **Checking Account Balance**:
- Currently, OpenRouter doesnt appear to provide a direct API endpoint for account balance checks within their documented API endpoints, so this may need to be monitored from their platform dashboard directly.
Heres a basic Python function demonstrating the API call, token tracking, and cost retrieval:
```python
import requests
def query_openrouter(content, model="openai/gpt-3.5-turbo"):
url = "https://openrouter.ai/api/v1/chat/completions"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {
"model": model,
"messages": [{"role": "user", "content": content}]
}
response = requests.post(url, headers=headers, json=data)
result = response.json()
if "usage" in result:
usage_data = result["usage"]
cost_query_url = f"https://openrouter.ai/api/v1/generation?id={result['id']}"
cost_response = requests.get(cost_query_url, headers=headers)
cost_data = cost_response.json()
return {
"reply": result["choices"][0]["message"]["content"],
"prompt_tokens": usage_data["prompt_tokens"],
"completion_tokens": usage_data["completion_tokens"],
"total_cost": cost_data["data"]["total_cost"]
}
else:
return {"error": "No usage data available in the response"}
# Example usage
result = query_openrouter("What is the meaning of life?")
print(result)
```
For further details, OpenRouters official documentation outlines more on usage and token tracking options.

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
import asyncio
import getpass
import json
import os
import sys
import aiofiles
from nio import AsyncClient, LoginResponse
CONFIG_FILE = "credentials.json"
# Check out main() below to see how it's done.
def write_details_to_disk(resp: LoginResponse, homeserver) -> None:
"""Writes the required login details to disk so we can log in later without
using a password.
Arguments:
resp {LoginResponse} -- the successful client login response.
homeserver -- URL of homeserver, e.g. "https://matrix.example.org"
"""
# open the config file in write-mode
with open(CONFIG_FILE, "w") as f:
# write the login details to disk
json.dump(
{
"homeserver": homeserver, # e.g. "https://matrix.example.org"
"user_id": resp.user_id, # e.g. "@user:example.org"
"device_id": resp.device_id, # device ID, 10 uppercase letters
"access_token": resp.access_token, # cryptogr. access token
},
f,
)
async def main() -> None:
# If there are no previously-saved credentials, we'll use the password
if not os.path.exists(CONFIG_FILE):
print(
"First time use. Did not find credential file. Asking for "
"homeserver, user, and password to create credential file."
)
homeserver = "https://matrix.example.org"
homeserver = input(f"Enter your homeserver URL: [{homeserver}] ")
if not (homeserver.startswith("https://") or homeserver.startswith("http://")):
homeserver = "https://" + homeserver
user_id = "@user:example.org"
user_id = input(f"Enter your full user ID: [{user_id}] ")
device_name = "matrix-nio"
device_name = input(f"Choose a name for this device: [{device_name}] ")
client = AsyncClient(homeserver, user_id)
pw = getpass.getpass()
resp = await client.login(pw, device_name=device_name)
# check that we logged in successfully
if isinstance(resp, LoginResponse):
write_details_to_disk(resp, homeserver)
else:
print(f'homeserver = "{homeserver}"; user = "{user_id}"')
print(f"Failed to log in: {resp}")
sys.exit(1)
print(
"Logged in using a password. Credentials were stored.",
"Try running the script again to login with credentials.",
)
# Otherwise the config file exists, so we'll use the stored credentials
else:
# open the file in read-only mode
async with aiofiles.open(CONFIG_FILE) as f:
contents = await f.read()
config = json.loads(contents)
client = AsyncClient(config["homeserver"])
client.access_token = config["access_token"]
client.user_id = config["user_id"]
client.device_id = config["device_id"]
# Now we can send messages as the user
room_id = "!myfavouriteroomid:example.org"
room_id = input(f"Enter room id for test message: [{room_id}] ")
await client.room_send(
room_id,
message_type="m.room.message",
content={"msgtype": "m.text", "body": "Hello world!"},
)
print("Logged in using stored credentials. Sent a test message.")
# Either way we're logged in here, too
await client.close()
asyncio.run(main())

View File

@@ -0,0 +1,400 @@
#!/usr/bin/env python3
"""verify_with_emoji.py A sample program to demo Emoji verification.
# Objectives:
- Showcase the emoji verification using matrix-nio SDK
- This sample program tries to show the key steps involved in performing
an emoji verification.
- It does so only for incoming request, outgoing emoji verification request
are similar but not shown in this sample program
# Prerequisites:
- You must have matrix-nio and components for end-to-end encryption installed
See: https://github.com/poljar/matrix-nio
- You must have created a Matrix account already,
and have username and password ready
- You must have already joined a Matrix room with someone, e.g. yourself
- This other party initiates an emoji verification with you
- You are using this sample program to accept this incoming emoji verification
and follow the protocol to successfully verify the other party's device
# Use Cases:
- Apply similar code in your Matrix bot
- Apply similar code in your Matrix client
- Just to learn about Matrix and the matrix-nio SDK
# Running the Program:
- Change permissions to allow execution
`chmod 755 ./verify_with_emoji.py`
- Optionally create a store directory, if not it will be done for you
`mkdir ./store/`
- Run the program as-is, no changes needed
`./verify_with_emoji.py`
- Run it as often as you like
# Sample Screen Output when Running Program:
$ ./verify_with_emoji.py
First time use. Did not find credential file. Asking for
homeserver, user, and password to create credential file.
Enter your homeserver URL: [https://matrix.example.org] matrix.example.org
Enter your full user ID: [@user:example.org] @user:example.org
Choose a name for this device: [matrix-nio] verify_with_emoji
Password:
Logged in using a password. Credentials were stored.
On next execution the stored login credentials will be used.
This program is ready and waiting for the other party to initiate an emoji
verification with us by selecting "Verify by Emoji" in their Matrix client.
[('', 'Anchor'), ('☎️', 'Telephone'), ('😀', 'Smiley'), ('😀', 'Smiley'),
('☂️', 'Umbrella'), ('', 'Anchor'), ('☎️', 'Telephone')]
Do the emojis match? (Y/N) y
Match! Device will be verified by accepting verification.
sas.we_started_it = False
sas.sas_accepted = True
sas.canceled = False
sas.timed_out = False
sas.verified = True
sas.verified_devices = ['DEVICEIDXY']
Emoji verification was successful.
Hit Control-C to stop the program or initiate another Emoji verification
from another device or room.
"""
import asyncio
import getpass
import json
import os
import sys
import traceback
import aiofiles
from nio import (
AsyncClient,
AsyncClientConfig,
KeyVerificationCancel,
KeyVerificationEvent,
KeyVerificationKey,
KeyVerificationMac,
KeyVerificationStart,
LocalProtocolError,
LoginResponse,
ToDeviceError,
)
# file to store credentials in case you want to run program multiple times
CONFIG_FILE = "credentials.json" # login credentials JSON file
# directory to store persistent data for end-to-end encryption
STORE_PATH = "./store/" # local directory
class Callbacks:
"""Class to pass client to callback methods."""
def __init__(self, client):
"""Store AsyncClient."""
self.client = client
async def to_device_callback(self, event): # noqa
"""Handle events sent to device."""
try:
client = self.client
if isinstance(event, KeyVerificationStart): # first step
"""first step: receive KeyVerificationStart
KeyVerificationStart(
source={'content':
{'method': 'm.sas.v1',
'from_device': 'DEVICEIDXY',
'key_agreement_protocols':
['curve25519-hkdf-sha256', 'curve25519'],
'hashes': ['sha256'],
'message_authentication_codes':
['hkdf-hmac-sha256', 'hmac-sha256'],
'short_authentication_string':
['decimal', 'emoji'],
'transaction_id': 'SomeTxId'
},
'type': 'm.key.verification.start',
'sender': '@user2:example.org'
},
sender='@user2:example.org',
transaction_id='SomeTxId',
from_device='DEVICEIDXY',
method='m.sas.v1',
key_agreement_protocols=[
'curve25519-hkdf-sha256', 'curve25519'],
hashes=['sha256'],
message_authentication_codes=[
'hkdf-hmac-sha256', 'hmac-sha256'],
short_authentication_string=['decimal', 'emoji'])
"""
if "emoji" not in event.short_authentication_string:
print(
"Other device does not support emoji verification "
f"{event.short_authentication_string}."
)
return
resp = await client.accept_key_verification(event.transaction_id)
if isinstance(resp, ToDeviceError):
print(f"accept_key_verification failed with {resp}")
sas = client.key_verifications[event.transaction_id]
todevice_msg = sas.share_key()
resp = await client.to_device(todevice_msg)
if isinstance(resp, ToDeviceError):
print(f"to_device failed with {resp}")
elif isinstance(event, KeyVerificationCancel): # anytime
"""at any time: receive KeyVerificationCancel
KeyVerificationCancel(source={
'content': {'code': 'm.mismatched_sas',
'reason': 'Mismatched authentication string',
'transaction_id': 'SomeTxId'},
'type': 'm.key.verification.cancel',
'sender': '@user2:example.org'},
sender='@user2:example.org',
transaction_id='SomeTxId',
code='m.mismatched_sas',
reason='Mismatched short authentication string')
"""
# There is no need to issue a
# client.cancel_key_verification(tx_id, reject=False)
# here. The SAS flow is already cancelled.
# We only need to inform the user.
print(
f"Verification has been cancelled by {event.sender} "
f'for reason "{event.reason}".'
)
elif isinstance(event, KeyVerificationKey): # second step
"""Second step is to receive KeyVerificationKey
KeyVerificationKey(
source={'content': {
'key': 'SomeCryptoKey',
'transaction_id': 'SomeTxId'},
'type': 'm.key.verification.key',
'sender': '@user2:example.org'
},
sender='@user2:example.org',
transaction_id='SomeTxId',
key='SomeCryptoKey')
"""
sas = client.key_verifications[event.transaction_id]
print(f"{sas.get_emoji()}")
yn = input("Do the emojis match? (Y/N) (C for Cancel) ")
if yn.lower() == "y":
print(
"Match! The verification for this " "device will be accepted."
)
resp = await client.confirm_short_auth_string(event.transaction_id)
if isinstance(resp, ToDeviceError):
print(f"confirm_short_auth_string failed with {resp}")
elif yn.lower() == "n": # no, don't match, reject
print(
"No match! Device will NOT be verified "
"by rejecting verification."
)
resp = await client.cancel_key_verification(
event.transaction_id, reject=True
)
if isinstance(resp, ToDeviceError):
print(f"cancel_key_verification failed with {resp}")
else: # C or anything for cancel
print("Cancelled by user! Verification will be " "cancelled.")
resp = await client.cancel_key_verification(
event.transaction_id, reject=False
)
if isinstance(resp, ToDeviceError):
print(f"cancel_key_verification failed with {resp}")
elif isinstance(event, KeyVerificationMac): # third step
"""Third step is to receive KeyVerificationMac
KeyVerificationMac(
source={'content': {
'mac': {'ed25519:DEVICEIDXY': 'SomeKey1',
'ed25519:SomeKey2': 'SomeKey3'},
'keys': 'SomeCryptoKey4',
'transaction_id': 'SomeTxId'},
'type': 'm.key.verification.mac',
'sender': '@user2:example.org'},
sender='@user2:example.org',
transaction_id='SomeTxId',
mac={'ed25519:DEVICEIDXY': 'SomeKey1',
'ed25519:SomeKey2': 'SomeKey3'},
keys='SomeCryptoKey4')
"""
sas = client.key_verifications[event.transaction_id]
try:
todevice_msg = sas.get_mac()
except LocalProtocolError as e:
# e.g. it might have been cancelled by ourselves
print(
f"Cancelled or protocol error: Reason: {e}.\n"
f"Verification with {event.sender} not concluded. "
"Try again?"
)
else:
resp = await client.to_device(todevice_msg)
if isinstance(resp, ToDeviceError):
print(f"to_device failed with {resp}")
print(
f"sas.we_started_it = {sas.we_started_it}\n"
f"sas.sas_accepted = {sas.sas_accepted}\n"
f"sas.canceled = {sas.canceled}\n"
f"sas.timed_out = {sas.timed_out}\n"
f"sas.verified = {sas.verified}\n"
f"sas.verified_devices = {sas.verified_devices}\n"
)
print(
"Emoji verification was successful!\n"
"Hit Control-C to stop the program or "
"initiate another Emoji verification from "
"another device or room."
)
else:
print(
f"Received unexpected event type {type(event)}. "
f"Event is {event}. Event will be ignored."
)
except BaseException:
print(traceback.format_exc())
def write_details_to_disk(resp: LoginResponse, homeserver) -> None:
"""Write the required login details to disk.
It will allow following logins to be made without password.
Arguments:
---------
resp : LoginResponse - successful client login response
homeserver : str - URL of homeserver, e.g. "https://matrix.example.org"
"""
# open the config file in write-mode
with open(CONFIG_FILE, "w") as f:
# write the login details to disk
json.dump(
{
"homeserver": homeserver, # e.g. "https://matrix.example.org"
"user_id": resp.user_id, # e.g. "@user:example.org"
"device_id": resp.device_id, # device ID, 10 uppercase letters
"access_token": resp.access_token, # cryptogr. access token
},
f,
)
async def login() -> AsyncClient:
"""Handle login with or without stored credentials."""
# Configuration options for the AsyncClient
client_config = AsyncClientConfig(
max_limit_exceeded=0,
max_timeouts=0,
store_sync_tokens=True,
encryption_enabled=True,
)
# If there are no previously-saved credentials, we'll use the password
if not os.path.exists(CONFIG_FILE):
print(
"First time use. Did not find credential file. Asking for "
"homeserver, user, and password to create credential file."
)
homeserver = "https://matrix.example.org"
homeserver = input(f"Enter your homeserver URL: [{homeserver}] ")
if not (homeserver.startswith("https://") or homeserver.startswith("http://")):
homeserver = "https://" + homeserver
user_id = "@user:example.org"
user_id = input(f"Enter your full user ID: [{user_id}] ")
device_name = "matrix-nio"
device_name = input(f"Choose a name for this device: [{device_name}] ")
if not os.path.exists(STORE_PATH):
os.makedirs(STORE_PATH)
# Initialize the matrix client
client = AsyncClient(
homeserver,
user_id,
store_path=STORE_PATH,
config=client_config,
)
pw = getpass.getpass()
resp = await client.login(password=pw, device_name=device_name)
# check that we logged in successfully
if isinstance(resp, LoginResponse):
write_details_to_disk(resp, homeserver)
else:
print(f'homeserver = "{homeserver}"; user = "{user_id}"')
print(f"Failed to log in: {resp}")
sys.exit(1)
print(
"Logged in using a password. Credentials were stored. "
"On next execution the stored login credentials will be used."
)
# Otherwise the config file exists, so we'll use the stored credentials
else:
# open the file in read-only mode
async with aiofiles.open(CONFIG_FILE) as f:
contents = await f.read()
config = json.loads(contents)
# Initialize the matrix client based on credentials from file
client = AsyncClient(
config["homeserver"],
config["user_id"],
device_id=config["device_id"],
store_path=STORE_PATH,
config=client_config,
)
client.restore_login(
user_id=config["user_id"],
device_id=config["device_id"],
access_token=config["access_token"],
)
print("Logged in using stored credentials.")
return client
async def main() -> None:
"""Login and wait for and perform emoji verify."""
client = await login()
# Set up event callbacks
callbacks = Callbacks(client)
client.add_to_device_callback(callbacks.to_device_callback, (KeyVerificationEvent,))
# Sync encryption keys with the server
# Required for participating in encrypted rooms
if client.should_upload_keys:
await client.keys_upload()
print(
"This program is ready and waiting for the other party to initiate "
'an emoji verification with us by selecting "Verify by Emoji" '
"in their Matrix client."
)
await client.sync_forever(timeout=30000, full_state=True)
try:
asyncio.run(main())
except Exception:
print(traceback.format_exc())
sys.exit(1)
except KeyboardInterrupt:
print("Received keyboard interrupt.")
sys.exit(0)

View File

@@ -0,0 +1,15 @@
curl https://api.x.ai/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer xai-Irz5BaEdnraGMHeFmIuTWx85srNRX5rrmXiRofaroFHrXh4dWGynC0B6hhwhUeU70Is5rSow1lHiSSVu" -d '{
"messages": [
{
"role": "system",
"content": "You are a test assistant."
},
{
"role": "user",
"content": "Testing. Just say hi and hello world and nothing else."
}
],
"model": "grok-beta",
"stream": false,
"temperature": 0
}'

View File

@@ -0,0 +1,18 @@
import os
from openai import OpenAI
XAI_API_KEY = os.getenv("XAI_API_KEY")
client = OpenAI(
api_key=XAI_API_KEY,
base_url="https://api.x.ai/v1",
)
completion = client.chat.completions.create(
model="grok-beta",
messages=[
{"role": "system", "content": "You are Grok, a chatbot inspired by the Hitchhikers Guide to the Galaxy."},
{"role": "user", "content": "What is the meaning of life, the universe, and everything?"},
],
)
print(completion.choices[0].message)

File diff suppressed because it is too large Load Diff