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