You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
blivechat/blcsdk/api.py

157 lines
4.8 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- coding: utf-8 -*-
import asyncio
import logging
import os
import re
from typing import *
import aiohttp
from . import (
__version__,
client as cli,
exc,
handlers,
models,
)
__all__ = (
'init',
'shut_down',
'set_msg_handler',
'is_sdk_version_compatible',
)
logger = logging.getLogger('blcsdk')
# 环境变量
_base_url = ''
"""HTTP API的URL"""
_token = ''
"""插件认证用的token"""
# 初始化消息
_init_future: Optional[asyncio.Future] = None
"""初始化消息的future"""
_init_msg: Optional[dict] = None
"""初始化消息,包含版本等信息"""
# 其他和blivechat通信用的对象
_http_session: Optional[aiohttp.ClientSession] = None
"""插件请求专用的HTTP客户端"""
_plugin_client: Optional[cli.BlcPluginClient] = None
"""插件客户端"""
_msg_handler: Optional[handlers.HandlerInterface] = None
"""插件消息处理器"""
_msg_handler_wrapper: Optional['_HandlerWrapper'] = None
"""用于SDK处理一些消息然后转发给插件消息处理器"""
async def init():
"""
初始化SDK
在调用除了set_msg_handler以外的其他接口之前必须先调用这个。如果抛出任何异常应该退出当前程序
"""
try:
global _base_url, _token, _init_future, _init_msg, _http_session, _plugin_client, _msg_handler_wrapper
if _init_future is not None:
raise exc.InitError('Cannot call init() again')
_init_future = asyncio.get_running_loop().create_future()
# 初始化环境变量信息
blc_port = int(os.environ['BLC_PORT'])
_base_url = f'http://localhost:{blc_port}'
blc_ws_url = f'ws://localhost:{blc_port}/api/plugin/websocket'
_token = os.environ['BLC_TOKEN']
_http_session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=10),
headers={'Authorization': f'Bearer {_token}'},
)
# 连接blivechat
_msg_handler_wrapper = _HandlerWrapper()
_plugin_client = cli.BlcPluginClient(blc_ws_url, session=_http_session)
_plugin_client.set_handler(_msg_handler_wrapper)
_plugin_client.start()
# 等待初始化消息
_init_msg = await _init_future
logger.debug('SDK initialized, _init_msg=%s', _init_msg)
except exc.InitError:
raise
except Exception as e:
raise exc.InitError(f'Error in init(): {e}') from e
async def shut_down():
"""退出程序之前建议调用"""
if _plugin_client is not None:
await _plugin_client.stop_and_close()
if _http_session is not None:
await _http_session.close()
def set_msg_handler(handler: Optional[handlers.HandlerInterface]):
"""
设置消息处理器
注意消息处理器和网络协程运行在同一个协程如果处理消息耗时太长会阻塞接收消息。如果是CPU密集型的任务建议将消息推到线程池处理
如果是IO密集型的任务应该使用async函数并且在handler里使用create_task创建新的协程
:param handler: 消息处理器
"""
global _msg_handler
_msg_handler = handler
class _HandlerWrapper(handlers.HandlerInterface):
"""用于SDK处理一些消息然后转发给插件消息处理器"""
def handle(self, client: cli.BlcPluginClient, command: dict):
if not _init_future.done():
if command['cmd'] == models.Command.BLC_INIT:
_init_future.set_result(command['data'])
if _msg_handler is not None:
_msg_handler.handle(client, command)
def on_client_stopped(self, client: cli.BlcPluginClient, exception: Optional[Exception]):
if not _init_future.done():
if exception is not None:
_init_future.set_exception(exception)
else:
_init_future.set_exception(exc.InitError('Connection closed before init msg'))
if _msg_handler is not None:
_msg_handler.on_client_stopped(client, exception)
def is_sdk_version_compatible():
"""
检查SDK版本和blivechat的版本是否兼容
如果不兼容,建议退出当前程序。如果继续执行有可能不能正常工作
"""
if _init_msg is None:
raise exc.SdkError('Please call init() first')
major_ver_pattern = r'(\d+)\.\d+\.\d+'
remote_ver = _init_msg['sdkVersion']
m = re.match(major_ver_pattern, remote_ver)
if m is None:
raise exc.SdkError(f"Bad remote version format: {remote_ver}")
remote_major_ver = m[1]
m = re.match(major_ver_pattern, __version__)
if m is None:
raise exc.SdkError(f"Bad local version format: {__version__}")
local_major_ver = m[1]
res = remote_major_ver == local_major_ver
if not res:
logger.warning('SDK version is not compatible, remote=%s, local=%s', remote_ver, __version__)
return res