Add references (removed inner git)
This commit is contained in:
Submodule references/matrix-bot-chat-reference deleted from 972a538356
3
references/matrix-bot-chat-reference/.gitignore
vendored
Normal file
3
references/matrix-bot-chat-reference/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
__pycache__/config.cpython-311.pyc
|
||||||
|
services/__pycache__/matrix_service.cpython-311.pyc
|
||||||
9
references/matrix-bot-chat-reference/Dockerfile
Normal file
9
references/matrix-bot-chat-reference/Dockerfile
Normal 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"]
|
||||||
59
references/matrix-bot-chat-reference/config.py
Normal file
59
references/matrix-bot-chat-reference/config.py
Normal 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响应超时时间(分钟)
|
||||||
1
references/matrix-bot-chat-reference/credentials.json
Normal file
1
references/matrix-bot-chat-reference/credentials.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"access_token": "syt_Ym90czE_gbQAFJKVPLGyukPyWBZh_26VocT", "device_id": "BOTDEVICE", "user_id": "@bots1:person.bnicetome.top"}
|
||||||
7
references/matrix-bot-chat-reference/docker-compose.yml
Normal file
7
references/matrix-bot-chat-reference/docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
myapp:
|
||||||
|
build: .
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- .:/app # 将当前目录挂载到容器的 /app 目录
|
||||||
53
references/matrix-bot-chat-reference/main.py
Normal file
53
references/matrix-bot-chat-reference/main.py
Normal 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())
|
||||||
5
references/matrix-bot-chat-reference/requirements.txt
Normal file
5
references/matrix-bot-chat-reference/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Flask
|
||||||
|
matrix-nio[e2e]
|
||||||
|
requests
|
||||||
|
schedule
|
||||||
|
openai
|
||||||
32
references/matrix-bot-chat-reference/sample/basic_client.py
Normal file
32
references/matrix-bot-chat-reference/sample/basic_client.py
Normal 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())
|
||||||
@@ -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
|
||||||
63
references/matrix-bot-chat-reference/sample/matrix_store.txt
Normal file
63
references/matrix-bot-chat-reference/sample/matrix_store.txt
Normal 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 重启也能够从存储中恢复这些状态,继续进行加密的私聊通信。
|
||||||
@@ -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. Here’s an alternative approach to set up a persistence layer using DefaultCryptoStore in matrix-nio for handling encryption without relying on SqliteStore.
|
||||||
|
|
||||||
|
Here’s 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. Here’s 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.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
To implement a Python function using OpenRouter’s 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**:
|
||||||
|
- You’ll 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 doesn’t 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.
|
||||||
|
|
||||||
|
Here’s 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, OpenRouter’s official documentation outlines more on usage and token tracking options.
|
||||||
104
references/matrix-bot-chat-reference/sample/restore_login.py
Normal file
104
references/matrix-bot-chat-reference/sample/restore_login.py
Normal 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())
|
||||||
400
references/matrix-bot-chat-reference/sample/verify_with_emoji.py
Normal file
400
references/matrix-bot-chat-reference/sample/verify_with_emoji.py
Normal 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)
|
||||||
15
references/matrix-bot-chat-reference/sample/xAi的API交互方法.txt
Normal file
15
references/matrix-bot-chat-reference/sample/xAi的API交互方法.txt
Normal 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
|
||||||
|
}'
|
||||||
@@ -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)
|
||||||
1242
references/matrix-bot-chat-reference/services/matrix_service.py
Normal file
1242
references/matrix-bot-chat-reference/services/matrix_service.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user