Compare commits

...

56 Commits
master ... new

Author SHA1 Message Date
dd615896bb
Support group filter 2023-01-13 14:25:45 +08:00
9dc07ec55f
chore: async msg decorator 2023-01-13 14:24:14 +08:00
pre-commit-ci[bot]
56363f091f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/isort: v5.11.3 → 5.11.4](https://github.com/PyCQA/isort/compare/v5.11.3...5.11.4)
2022-12-27 11:16:32 +08:00
pre-commit-ci[bot]
5ed4996905 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/isort: 5.11.1 → v5.11.3](https://github.com/PyCQA/isort/compare/5.11.1...v5.11.3)
2022-12-20 10:22:45 +08:00
pre-commit-ci[bot]
3fc56b93f5 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0)
- [github.com/PyCQA/isort: 5.10.1 → 5.11.1](https://github.com/PyCQA/isort/compare/5.10.1...5.11.1)
2022-12-13 11:53:11 +08:00
pre-commit-ci[bot]
2a145d2659 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0)
2022-11-29 09:40:44 +08:00
shejialuo
39f628a99f fix(docs): change the http bind address
It is dangerous to use `0.0.0.0` in the docs. So this commit changes the
address to localhost.
2022-11-08 17:08:35 +08:00
pre-commit-ci[bot]
0f8b06b129 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0)
2022-10-17 10:40:28 +08:00
pre-commit-ci[bot]
bf0a04e5fe [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.6.0 → 22.8.0](https://github.com/psf/black/compare/22.6.0...22.8.0)
2022-09-06 13:05:13 +08:00
pre-commit-ci[bot]
f94353f469 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-09-06 13:05:01 +08:00
8024f4e416 feat: group album 2022-09-06 13:05:01 +08:00
pre-commit-ci[bot]
838e759e07 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-08-21 15:34:44 +08:00
147f66cd91 fix: ActionFailed 2022-08-21 15:34:44 +08:00
pre-commit-ci[bot]
32547397c3 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-08-21 15:34:44 +08:00
7f14a3cb9e fix: join request 2022-08-21 15:34:44 +08:00
XYenon
a1781add55 fix receive file 2022-08-14 14:46:56 +08:00
pre-commit-ci[bot]
31c757344c [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 5.0.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/5.0.2...5.0.4)
2022-08-09 12:17:49 +08:00
7a3bccf8c0 fix: await async_download_group_file 2022-08-02 12:20:29 +08:00
a4da2927fa fix: Ignore qq guild message 2022-08-02 12:20:29 +08:00
pre-commit-ci[bot]
da6e06d1c0 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 4.0.1 → 5.0.2](https://github.com/PyCQA/flake8/compare/4.0.1...5.0.2)
2022-08-02 12:20:04 +08:00
XYenon
a114d91fee upgrade pdm to 2.0 2022-07-27 21:35:41 +08:00
XYenon
a1b7f88015 fix: add await 2022-07-27 21:35:41 +08:00
0honus0
10acc91fdb 使用新版配置格式 2022-07-27 21:32:28 +08:00
pre-commit-ci[bot]
0db46937ac [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.3.0 → 22.6.0](https://github.com/psf/black/compare/22.3.0...22.6.0)
2022-07-05 12:05:21 +08:00
XYenon
09072071ca
Merge pull request #36 from ehForwarderBot/dev/async
refactor to async
2022-06-03 14:04:42 +08:00
XYenon
1d826405ac fix: close file when error occurred 2022-05-28 23:12:50 +08:00
XYenon
7bf80e1787 fix: catch NetworkError 2022-05-28 18:51:54 +08:00
XYenon
3202ccd85c set quart.serving log level to warning 2022-05-28 17:04:15 +08:00
XYenon
d97a2ab2bf async process msg 2022-05-28 16:56:46 +08:00
XYenon
b86e558312 use request to download file 2022-05-28 16:05:26 +08:00
XYenon
265959ddda fix typo 2022-05-22 19:47:13 +08:00
XYenon
cd3fdc7d86 fix: RuntimeError: asyncio.run() cannot be called from a running event loop 2022-05-22 19:21:13 +08:00
XYenon
3818b6ffb4 fix: KeyError: 'in_group_info' 2022-05-22 18:36:42 +08:00
XYenon
b93a613e1b fix: TypeError: 'coroutine' object is not subscriptable 2022-05-09 17:20:27 +08:00
XYenon
4b23b770fb refactor to async 2022-05-02 18:34:48 +08:00
XYenon
d1e79d8ed0
Merge pull request #33 from ehForwarderBot/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-04-05 04:35:29 +08:00
pre-commit-ci[bot]
2b400f7ed5
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.1.0 → 22.3.0](https://github.com/psf/black/compare/22.1.0...22.3.0)
2022-04-04 19:41:00 +00:00
XYenon
d01b459fe8
Merge pull request #32 from ehForwarderBot/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-03-29 23:19:35 +08:00
pre-commit-ci[bot]
fb73069820
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.941 → v0.942](https://github.com/pre-commit/mirrors-mypy/compare/v0.941...v0.942)
2022-03-28 18:58:25 +00:00
XYenon
1478cb7b56
Merge pull request #30 from ehForwarderBot/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-03-22 09:52:16 +08:00
pre-commit-ci[bot]
4370e98d30
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.940 → v0.941](https://github.com/pre-commit/mirrors-mypy/compare/v0.940...v0.941)
2022-03-21 22:03:38 +00:00
XYenon
c8d30db88b
Merge pull request #29 from ehForwarderBot/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-03-15 09:50:13 +08:00
pre-commit-ci[bot]
2874307dcf
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.931 → v0.940](https://github.com/pre-commit/mirrors-mypy/compare/v0.931...v0.940)
2022-03-14 21:53:46 +00:00
XYenon
f6c508560c
Merge pull request #28 from omg-xtao/dev
feat(more notice): Group Admin Change and Member Restrict
2022-03-14 13:23:04 +08:00
omg-xtao
3f89606ab6
refactor: clean up 2022-03-14 13:13:26 +08:00
pre-commit-ci[bot]
7d4d3c4a0b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-03-12 11:52:26 +00:00
1dc82dcb9e
feat(more notice): Group Admin Change and Member Restrict 2022-03-12 19:49:03 +08:00
XYenon
b07c89d06b chore(lint): add mypy 2022-03-09 22:15:00 +08:00
XYenon
2c2ff9de1e chore(version): bump version to 2.2.3 2022-03-09 21:05:29 +08:00
XYenon
661dc91104
Merge pull request #27 from omg-xtao:omg-xtao-patch-1
feat(recall notice): support msg recall notice
2022-03-09 21:03:11 +08:00
omg-xtao
4c6880b342
refactor: clean up 2022-03-09 20:56:45 +08:00
omg-xtao
aeee9738a4
feat(recall notice): support msg recall notice 2022-03-09 19:52:21 +08:00
XYenon
d9bf0da5a4
Update __init__.py 2022-03-08 21:18:43 +08:00
XYenon
4d5dffcd7a
Merge pull request #26 from omg-xtao/omg-xtao-patch-1
Support flash picture and Fix GroupJoinRequest
2022-03-08 21:17:45 +08:00
omg-xtao
a08633715c
fix(GroupJoinRequest): fix build msg author error 2022-03-08 20:10:35 +08:00
omg-xtao
dfb1421f84
feat(flash picture): support flash picture 2022-03-08 20:06:10 +08:00
9 changed files with 1303 additions and 1073 deletions

View File

@ -1,17 +1,17 @@
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.1.0 rev: 22.12.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 4.0.1 rev: 6.0.0
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
- flake8-bugbear - flake8-bugbear
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.10.1 rev: 5.11.4
hooks: hooks:
- id: isort - id: isort

View File

@ -38,12 +38,8 @@ efb-qq-plugin-go-cqhttp 是 efb-qq-slave 的插件,需要配合 efb-qq-slave
servers: servers:
# HTTP 通信设置 # HTTP 通信设置
- http: - http:
# 是否关闭正向 HTTP 服务器 # HTTP监听地址
disabled: false address: 127.0.0.1:5700
# 服务端监听地址
host: 127.0.0.1
# 服务端监听端口
port: 5700
# 反向 HTTP 超时时间, 单位秒 # 反向 HTTP 超时时间, 单位秒
# 最小值为 5小于 5 将会忽略本项设置 # 最小值为 5小于 5 将会忽略本项设置
timeout: 5 timeout: 5

View File

@ -20,10 +20,10 @@ class ChatManager:
channel=self.channel, uid=ChatID("__error_chat__"), name="Chat Missing" channel=self.channel, uid=ChatID("__error_chat__"), name="Chat Missing"
) )
def build_efb_chat_as_private(self, context): async def build_efb_chat_as_private(self, context):
uid = context["user_id"] uid = context["user_id"]
if "sender" not in context or "nickname" not in context["sender"]: if "sender" not in context or "nickname" not in context["sender"]:
i: dict = self.channel.QQClient.get_stranger_info(uid) i: dict = await self.channel.QQClient.get_stranger_info(uid)
chat_name = "" chat_name = ""
if i: if i:
chat_name = i["nickname"] chat_name = i["nickname"]
@ -37,13 +37,13 @@ class ChatManager:
) )
return efb_chat return efb_chat
def build_or_get_efb_member(self, chat: Chat, context): async def build_or_get_efb_member(self, chat: Chat, context):
member_uid = context["user_id"] member_uid = context["user_id"]
with contextlib.suppress(KeyError): with contextlib.suppress(KeyError):
return chat.get_member(str(member_uid)) return chat.get_member(str(member_uid))
chat_name = "" chat_name = ""
if "nickname" not in context: if "nickname" not in context:
i: dict = self.channel.QQClient.get_stranger_info(member_uid) i: dict = await self.channel.QQClient.get_stranger_info(member_uid)
chat_name = "" chat_name = ""
if i: if i:
chat_name = i["nickname"] chat_name = i["nickname"]
@ -55,20 +55,20 @@ class ChatManager:
uid=str(member_uid), uid=str(member_uid),
) )
def build_efb_chat_as_group(self, context, update_member=False): # Should be cached async def build_efb_chat_as_group(self, context, update_member=False): # Should be cached
is_discuss = False if context["message_type"] == "group" else True is_discuss = context["message_type"] != "group"
chat_uid = context["discuss_id"] if is_discuss else context["group_id"] chat_uid = context["discuss_id"] if is_discuss else context["group_id"]
efb_chat = GroupChat(channel=self.channel, uid=str(chat_uid)) efb_chat = GroupChat(channel=self.channel, uid=str(chat_uid))
if not is_discuss: if not is_discuss:
efb_chat.uid = "group" + "_" + str(chat_uid) efb_chat.uid = "group" + "_" + str(chat_uid)
i = self.channel.QQClient.get_group_info(chat_uid) i = await self.channel.QQClient.get_group_info(chat_uid)
if i is not None: if i is not None:
efb_chat.name = str(i["group_name"]) if "group_name" not in context else str(context["group_name"]) efb_chat.name = str(i["group_name"]) if "group_name" not in context else str(context["group_name"])
else: else:
efb_chat.name = str(chat_uid) efb_chat.name = str(chat_uid)
efb_chat.vendor_specific = {"is_discuss": False} efb_chat.vendor_specific = {"is_discuss": False}
if update_member: if update_member:
members = self.channel.QQClient.get_group_member_list(chat_uid, False) members = await self.channel.QQClient.get_group_member_list(chat_uid, False)
if members: if members:
for member in members: for member in members:
efb_chat.add_member( efb_chat.add_member(

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +1,46 @@
import asyncio
import base64 import base64
import html import html
import json import json
import logging import logging
import sys import sys
from typing import TYPE_CHECKING
import magic import magic
from ehforwarderbot import Chat, Message, MsgType from ehforwarderbot import Chat, Message, MsgType
from ehforwarderbot.message import LinkAttribute, LocationAttribute, Substitutions from ehforwarderbot.message import LinkAttribute, LocationAttribute, Substitutions
from . import GoCQHttp
from .Utils import cq_get_image, download_file, download_voice from .Utils import cq_get_image, download_file, download_voice
if TYPE_CHECKING:
from .GoCQHttp import GoCQHttp
class QQMsgProcessor: class QQMsgProcessor:
inst: GoCQHttp inst: "GoCQHttp"
logger: logging.Logger = logging.getLogger(__name__) logger: logging.Logger = logging.getLogger(__name__)
def __init__(self, instance: GoCQHttp): def __init__(self, instance: "GoCQHttp"):
self.inst = instance self.inst = instance
self._ = instance._
pass
def qq_image_wrapper(self, data, chat: Chat = None): async def qq_image_wrapper(self, data, _: Chat = None):
efb_msg = Message() efb_msg = Message()
if "url" not in data: if "url" not in data:
efb_msg.type = MsgType.Text efb_msg.type = MsgType.Text
efb_msg.text = self._("[Image Source missing]") efb_msg.text = "[Image Source missing]"
return [efb_msg] return [efb_msg]
efb_msg.file = cq_get_image(data["url"]) # flash picture
if data.get("type", "") == "flash":
data["url"] = (
f"https://gchat.qpic.cn/gchatpic_new/1/1-1-" f'{data["file"].replace(".image", "").upper()}/0?term=3%27'
)
efb_msg.text = "Send a flash picture."
efb_msg.file = await cq_get_image(data["url"])
if efb_msg.file is None: if efb_msg.file is None:
efb_msg.type = MsgType.Text efb_msg.type = MsgType.Text
efb_msg.text = self._("[Download image failed, please check on your QQ client]") efb_msg.text = "[Download image failed, please check on your QQ client]"
return [efb_msg] return [efb_msg]
efb_msg.type = MsgType.Image efb_msg.type = MsgType.Image
@ -46,11 +55,11 @@ class QQMsgProcessor:
efb_msg.type = MsgType.Animation efb_msg.type = MsgType.Animation
return [efb_msg] return [efb_msg]
def qq_record_wrapper(self, data, chat: Chat = None): # Experimental! async def qq_record_wrapper(self, data, _: Chat = None): # Experimental!
efb_msg = Message() efb_msg = Message()
try: try:
efb_msg.type = MsgType.Audio efb_msg.type = MsgType.Audio
efb_msg.file = download_voice(data["url"]) efb_msg.file = await download_voice(data["url"])
mime = magic.from_file(efb_msg.file.name, mime=True) mime = magic.from_file(efb_msg.file.name, mime=True)
if isinstance(mime, bytes): if isinstance(mime, bytes):
mime = mime.decode() mime = mime.decode()
@ -58,11 +67,11 @@ class QQMsgProcessor:
efb_msg.mime = mime efb_msg.mime = mime
except Exception: except Exception:
efb_msg.type = MsgType.Unsupported efb_msg.type = MsgType.Unsupported
efb_msg.text = self._("[Voice Message] Please check it on your QQ") efb_msg.text = "[Voice Message] Please check it on your QQ"
logging.getLogger(__name__).exception("Failed to download voice") logging.getLogger(__name__).exception("Failed to download voice")
return [efb_msg] return [efb_msg]
def qq_share_wrapper(self, data, chat: Chat = None): def qq_share_wrapper(self, data, _: Chat = None):
efb_msg = Message( efb_msg = Message(
type=MsgType.Link, type=MsgType.Link,
text="", text="",
@ -75,7 +84,7 @@ class QQMsgProcessor:
) )
return [efb_msg] return [efb_msg]
def qq_location_wrapper(self, data, chat: Chat = None): def qq_location_wrapper(self, data, _: Chat = None):
efb_msg = Message( efb_msg = Message(
text=data["content"], text=data["content"],
type=MsgType.Location, type=MsgType.Location,
@ -83,23 +92,23 @@ class QQMsgProcessor:
) )
return [efb_msg] return [efb_msg]
def qq_shake_wrapper(self, data, chat: Chat = None): def qq_shake_wrapper(self, _, __: Chat = None):
efb_msg = Message(type=MsgType.Text, text=self._("[Your friend shakes you!]")) efb_msg = Message(type=MsgType.Text, text=("[Your friend shakes you!]"))
return [efb_msg] return [efb_msg]
def qq_contact_wrapper(self, data, chat: Chat = None): def qq_contact_wrapper(self, data, _: Chat = None):
uid = data["id"] uid = data["id"]
contact_type = data["type"] contact_type = data["type"]
efb_msg = Message( efb_msg = Message(
type=MsgType.Text, type=MsgType.Text,
text=self._("Chat Recommendation Received\nID: {}\nType: {}").format(uid, contact_type), text=("Chat Recommendation Received\nID: {}\nType: {}").format(uid, contact_type),
) )
return [efb_msg] return [efb_msg]
def qq_bface_wrapper(self, data, chat: Chat = None): def qq_bface_wrapper(self, _, __: Chat = None):
efb_msg = Message( efb_msg = Message(
type=MsgType.Unsupported, type=MsgType.Unsupported,
text=self._("[Here comes the BigFace Emoji, please check it on your phone]"), text=("[Here comes the BigFace Emoji, please check it on your phone]"),
) )
return [efb_msg] return [efb_msg]
@ -107,33 +116,33 @@ class QQMsgProcessor:
# todo this function's maybe not necessary? # todo this function's maybe not necessary?
pass pass
def qq_sign_wrapper(self, data, chat: Chat = None): def qq_sign_wrapper(self, data, _: Chat = None):
location = self._("at {}").format(data["location"]) if "location" in data else self._("at Unknown Place") location = ("at {}").format(data["location"]) if "location" in data else ("at Unknown Place")
title = "" if "title" not in data else (self._("with title {}").format(data["title"])) title = "" if "title" not in data else (("with title {}").format(data["title"]))
efb_msg = Message( efb_msg = Message(
type=MsgType.Text, type=MsgType.Text,
text=self._("signed in {location} {title}").format(title=title, location=location), text=("signed in {location} {title}").format(title=title, location=location),
) )
return [efb_msg] return [efb_msg]
def qq_rich_wrapper(self, data: dict, chat: Chat = None): # Buggy, Help needed async def qq_rich_wrapper(self, data: dict, chat: Chat = None): # Buggy, Help needed
efb_messages = list() efb_messages = list()
efb_msg = Message( efb_msg = Message(
type=MsgType.Unsupported, type=MsgType.Unsupported,
text=self._("[Here comes the Rich Text, dumping...] \n"), text=("[Here comes the Rich Text, dumping...] \n"),
) )
for key, value in data.items(): for key, value in data.items():
efb_msg.text += key + ": " + value + "\n" efb_msg.text += key + ": " + value + "\n"
efb_messages.append(efb_msg) efb_messages.append(efb_msg)
# Optimizations for rich messages # Optimizations for rich messages
# Group Broadcast # Group Broadcast
_ = self.qq_group_broadcast_wrapper(data, chat) _ = await self.qq_group_broadcast_wrapper(data, chat)
if _ is not None: if _ is not None:
efb_messages.append(_) efb_messages.append(_)
return efb_messages return efb_messages
def qq_music_wrapper(self, data, chat: Chat = None): def qq_music_wrapper(self, data, _: Chat = None):
efb_msg = Message() efb_msg = Message()
if data["type"] == "163": # Netease Cloud Music if data["type"] == "163": # Netease Cloud Music
efb_msg.type = MsgType.Text efb_msg.type = MsgType.Text
@ -183,7 +192,7 @@ class QQMsgProcessor:
efb_msg.filename = data["filename"] efb_msg.filename = data["filename"]
return efb_msg return efb_msg
def qq_group_broadcast_wrapper(self, data, chat: Chat = None): async def qq_group_broadcast_wrapper(self, data, chat: Chat):
try: try:
at_list = {} at_list = {}
content_data = json.loads(data["content"]) content_data = json.loads(data["content"])
@ -202,21 +211,21 @@ class QQMsgProcessor:
data["url"] = "http://gdynamic.qpic.cn/gdynamic/{}/628".format( data["url"] = "http://gdynamic.qpic.cn/gdynamic/{}/628".format(
content_data["mannounce"]["pic"][0]["url"] content_data["mannounce"]["pic"][0]["url"]
) )
efb_message = self.qq_image_wrapper(data)[0] efb_message = (await self.qq_image_wrapper(data))[0]
efb_message.text = text efb_message.text = text
efb_message.substitutions = Substitutions(at_list) efb_message.substitutions = Substitutions(at_list)
return [efb_message] return [efb_message]
else: else:
return self.qq_text_simple_wrapper(text, at_list) return self.qq_text_simple_wrapper(text, at_list)
except Exception: except Exception:
return self.qq_group_broadcast_alternative_wrapper(data) return asyncio.run(self.qq_group_broadcast_alternative_wrapper(data, chat))
def qq_group_broadcast_alternative_wrapper(self, data, chat: Chat = None): async def qq_group_broadcast_alternative_wrapper(self, data, chat: Chat):
try: try:
at_list = {} at_list = {}
content_data = json.loads(data["content"]) content_data = json.loads(data["content"])
group_id = content_data["mannounce"]["gc"] group_id = content_data["mannounce"]["gc"]
notice_raw_data = self.inst.coolq_api_query("_get_group_notice", group_id=group_id) notice_raw_data = await self.inst.coolq_api_query("_get_group_notice", group_id=group_id)
notice_data = json.loads(notice_raw_data) notice_data = json.loads(notice_raw_data)
title_data = html.unescape(notice_data[0]["msg"]["title"]) title_data = html.unescape(notice_data[0]["msg"]["title"])
text_data = html.unescape(notice_data[0]["msg"]["text"]) text_data = html.unescape(notice_data[0]["msg"]["text"])
@ -231,7 +240,7 @@ class QQMsgProcessor:
if "pics" in html.unescape(notice_data[0]["msg"]): # Picture Attached if "pics" in html.unescape(notice_data[0]["msg"]): # Picture Attached
# Assuming there's only one picture # Assuming there's only one picture
data["url"] = "http://gdynamic.qpic.cn/gdynamic/{}/628".format(notice_data[0]["msg"]["pics"][0]["id"]) data["url"] = "http://gdynamic.qpic.cn/gdynamic/{}/628".format(notice_data[0]["msg"]["pics"][0]["id"])
efb_message = self.qq_image_wrapper(data)[0] efb_message = (await self.qq_image_wrapper(data))[0]
efb_message.text = text efb_message.text = text
efb_message.substitutions = Substitutions(at_list) efb_message.substitutions = Substitutions(at_list)
return [efb_message] return [efb_message]
@ -240,13 +249,13 @@ class QQMsgProcessor:
except Exception: except Exception:
return None return None
def qq_xml_wrapper(self, data, chat: Chat = None): def qq_xml_wrapper(self, data, _: Chat = None):
efb_msg = Message() efb_msg = Message()
efb_msg.type = MsgType.Text efb_msg.type = MsgType.Text
efb_msg.text = data["data"] efb_msg.text = data["data"]
return [efb_msg] return [efb_msg]
def qq_json_wrapper(self, data, chat: Chat = None): def qq_json_wrapper(self, data, _: Chat = None):
efb_msg = Message() efb_msg = Message()
efb_msg.type = MsgType.Text efb_msg.type = MsgType.Text
efb_msg.text = data["data"] efb_msg.text = data["data"]
@ -289,6 +298,17 @@ class QQMsgProcessor:
preview=meta_detail1["preview"], preview=meta_detail1["preview"],
) )
# Tencent group photo upload
elif dict_data["app"] == "com.tencent.groupphoto":
album_name = dict_data["meta"]["albumData"]["title"]
photo_urls = ["https://" + i["url"] for i in dict_data["meta"]["albumData"]["pics"]]
efb_msg.text = "【群相册】\n\n{}\n\n{}".format(album_name, "\n".join(photo_urls))
# Tencent group photo album create
elif dict_data["app"] == "com.tencent.qzone.albumShare":
album_name = dict_data["meta"]["albumData"]["title"]
efb_msg.text = "【群相册】\n\n{}".format(album_name)
# Shared third-party Apps # Shared third-party Apps
elif dict_data["app"] == "com.tencent.structmsg": elif dict_data["app"] == "com.tencent.structmsg":
meta_view = dict_data["meta"][dict_data["view"]] meta_view = dict_data["meta"][dict_data["view"]]
@ -317,14 +337,14 @@ class QQMsgProcessor:
return [efb_msg] return [efb_msg]
def qq_video_wrapper(self, data, chat: Chat = None): async def qq_video_wrapper(self, data, _: Chat = None):
res = download_file(data["url"]) res = await download_file(data["url"])
mime = magic.from_file(res.name, mime=True) mime = magic.from_file(res.name, mime=True)
if isinstance(mime, bytes): if isinstance(mime, bytes):
mime = mime.decode() mime = mime.decode()
efb_msg = Message(type=MsgType.Video, file=res, filename=res.name, mime=mime) efb_msg = Message(type=MsgType.Video, file=res, filename=res.name, mime=mime)
return [efb_msg] return [efb_msg]
def qq_unsupported_wrapper(self, data, chat: Chat = None): def qq_unsupported_wrapper(self, data, _: Chat = None):
efb_msg = Message(type=MsgType.Unsupported, text=data) efb_msg = Message(type=MsgType.Unsupported, text=data)
return [efb_msg] return [efb_msg]

View File

@ -1,13 +1,13 @@
import logging import logging
import tempfile import tempfile
import urllib.request from typing import IO, Optional, Union
from gettext import translation
from urllib.error import ContentTooShortError, HTTPError, URLError
import httpx
import pilk import pilk
import pydub import pydub
from ehforwarderbot import Message, coordinator from ehforwarderbot import Message, coordinator
from pkg_resources import resource_filename
logger = logging.getLogger(__name__)
# created by JogleLew and jqqqqqqqqqq, optimized based on Tim's emoji support, updated by xzsk2 to mobileqq v8.8.11 # created by JogleLew and jqqqqqqqqqq, optimized based on Tim's emoji support, updated by xzsk2 to mobileqq v8.8.11
qq_emoji_list = { qq_emoji_list = {
@ -639,34 +639,52 @@ qq_sface_list = {
39: "[赞]", 39: "[赞]",
40: "[眨眼]", 40: "[眨眼]",
} }
translator = translation(
"efb_qq_slave",
resource_filename("efb_qq_slave", "Clients/CoolQ/locale"),
fallback=True,
)
_ = translator.gettext
ngettext = translator.ngettext
def cq_get_image(image_link: str) -> tempfile: # Download image from QQ async def async_get_file(url: str) -> IO:
file = tempfile.NamedTemporaryFile() temp_file = tempfile.NamedTemporaryFile()
try: try:
urllib.request.urlretrieve(image_link, file.name) async with httpx.AsyncClient() as client:
except (URLError, HTTPError, ContentTooShortError) as e: resp = await client.get(url)
logging.getLogger(__name__).warning("Image download failed.") temp_file.write(resp.content)
logging.getLogger(__name__).warning(str(e)) if temp_file.seek(0, 2) <= 0:
return None raise EOFError("File downloaded is Empty")
else: temp_file.seek(0)
if file.seek(0, 2) <= 0: except Exception as e:
temp_file.close()
raise e
return temp_file
def sync_get_file(url: str) -> IO:
temp_file = tempfile.NamedTemporaryFile()
try:
resp = httpx.get(url)
temp_file.write(resp.content)
if temp_file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty") raise EOFError("File downloaded is Empty")
file.seek(0) temp_file.seek(0)
return file except Exception as e:
temp_file.close()
raise e
return temp_file
async def cq_get_image(image_link: str) -> Optional[IO]: # Download image from QQ
try:
return await async_get_file(image_link)
except Exception as e:
logger.warning("File download failed.")
logger.warning(str(e))
return None
def async_send_messages_to_master(msg: Message): def async_send_messages_to_master(msg: Message):
coordinator.send_message(msg) try:
if msg.file: coordinator.send_message(msg)
msg.file.close() finally:
if msg.file:
msg.file.close()
def process_quote_text(text, max_length): # Simple wrapper for processing quoted text def process_quote_text(text, max_length): # Simple wrapper for processing quoted text
@ -706,80 +724,64 @@ def param_spliter(str_param):
return param return param
def download_file(download_url): async def download_file(download_url: str) -> Union[IO, str]:
file = tempfile.NamedTemporaryFile()
try: try:
opener = urllib.request.build_opener() return await async_get_file(download_url)
urllib.request.install_opener(opener) except Exception as e:
urllib.request.urlretrieve(download_url, file.name) logger.warning("Error occurs when downloading files: " + str(e))
except (URLError, HTTPError, ContentTooShortError) as e: return "Error occurs when downloading files: " + str(e)
logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e))
return _("Error occurs when downloading files: ") + str(e)
else:
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
file.seek(0)
return file
def download_user_avatar(uid: str): def download_user_avatar(uid: str):
file = tempfile.NamedTemporaryFile()
url = "https://q1.qlogo.cn/g?b=qq&nk={}&s=0".format(uid) url = "https://q1.qlogo.cn/g?b=qq&nk={}&s=0".format(uid)
try: try:
opener = urllib.request.build_opener() return sync_get_file(url)
urllib.request.install_opener(opener) except Exception as e:
urllib.request.urlretrieve(url, file.name) logger.warning("Error occurs when downloading files: " + str(e))
except (URLError, HTTPError, ContentTooShortError) as e: raise
logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e))
return _("Error occurs when downloading files: ") + str(e)
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
file.seek(0)
return file
def download_group_avatar(uid: str): def download_group_avatar(uid: str):
file = tempfile.NamedTemporaryFile()
url = "https://p.qlogo.cn/gh/{}/{}/".format(uid, uid) url = "https://p.qlogo.cn/gh/{}/{}/".format(uid, uid)
try: try:
opener = urllib.request.build_opener() return sync_get_file(url)
urllib.request.install_opener(opener)
urllib.request.urlretrieve(url, file.name)
except (URLError, HTTPError, ContentTooShortError) as e:
logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e))
return _("Error occurs when downloading files: ") + str(e)
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
file.seek(0)
return file
def download_voice(voice_url: str):
origin_file = tempfile.NamedTemporaryFile()
try:
opener = urllib.request.build_opener()
urllib.request.install_opener(opener)
urllib.request.urlretrieve(voice_url, origin_file.name)
except Exception as e: except Exception as e:
logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e)) logger.warning("Error occurs when downloading files: " + str(e))
origin_file.close() raise
raise e
finally:
opener.close() async def download_voice(voice_url: str):
if origin_file.seek(0, 2) <= 0: origin_file, audio_file = None, None
origin_file.close() try:
raise EOFError("File downloaded is Empty") origin_file = await async_get_file(voice_url)
origin_file.seek(0) silk_header = origin_file.read(10)
silk_header = origin_file.read(10) origin_file.seek(0)
origin_file.seek(0) if b"#!SILK_V3" in silk_header:
if b"#!SILK_V3" in silk_header: with tempfile.NamedTemporaryFile() as pcm_file:
with tempfile.NamedTemporaryFile() as pcm_file: pilk.decode(origin_file.name, pcm_file.name)
pilk.decode(origin_file.name, pcm_file.name) audio_file = tempfile.NamedTemporaryFile()
pydub.AudioSegment.from_raw(file=pcm_file, sample_width=2, frame_rate=24000, channels=1).export(
audio_file, format="ogg", codec="libopus", parameters=["-vbr", "on"]
)
else:
audio_file = origin_file
except Exception as e:
if origin_file:
origin_file.close() origin_file.close()
audio_file = tempfile.NamedTemporaryFile() if audio_file:
pydub.AudioSegment.from_raw(file=pcm_file, sample_width=2, frame_rate=24000, channels=1).export( audio_file.close()
audio_file, format="ogg", codec="libopus", parameters=["-vbr", "on"] logger.warning("Error occurs when downloading files: " + str(e))
) raise
else:
audio_file = origin_file
return audio_file return audio_file
def strf_time(seconds: int) -> str:
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
text = ""
text += f"{days}d" if days else ""
text += f"{hours}h" if hours else ""
text += f"{minutes}m" if minutes else ""
text += f"{seconds}s" if seconds else ""
return text

View File

@ -1,3 +1,3 @@
from . import GoCQHttp # noqa: F401 from . import GoCQHttp # noqa: F401
__version__ = "2.2.1" __version__ = "3.0.2"

1116
pdm.lock

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,14 @@ dependencies = [
"efb-qq-slave @ git+https://github.com/milkice233/efb-qq-slave@master", "efb-qq-slave @ git+https://github.com/milkice233/efb-qq-slave@master",
"ehforwarderbot~=2.1.1", "ehforwarderbot~=2.1.1",
"PyYAML~=6.0", "PyYAML~=6.0",
"requests~=2.27.1",
"python-magic~=0.4.25", "python-magic~=0.4.25",
"Pillow~=9.0.1", "Pillow~=9.0.1",
"cqhttp~=1.3.1", "aiocqhttp~=1.4.3",
"CherryPy~=18.6.1", "quart~=0.17.0",
"hypercorn~=0.13.2",
"pilk~=0.0.2", "pilk~=0.0.2",
"pydub~=0.25.1", "pydub~=0.25.1",
"httpx>=0.23.3",
] ]
requires-python = ">=3.7" requires-python = ">=3.7"
license = { text = "AGPL-3.0-only" } license = { text = "AGPL-3.0-only" }
@ -56,7 +57,7 @@ build-backend = "pdm.pep517.api"
version = { from = "efb_qq_plugin_go_cqhttp/__init__.py" } version = { from = "efb_qq_plugin_go_cqhttp/__init__.py" }
[tool.pdm.dev-dependencies] [tool.pdm.dev-dependencies]
dev = ["pre-commit", "efb-telegram-master~=2.2.4"] dev = ["efb-telegram-master~=2.2.4"]
[tool.black] [tool.black]
line-length = 120 line-length = 120