Compare commits
No commits in common. "256c4d429a95b6a2bdc9aabeda5406f6494bf99b" and "2d9ae53d8970435fc291ccf58a51b960de1609fe" have entirely different histories.
256c4d429a
...
2d9ae53d89
@ -95,7 +95,7 @@ async def enter_password(evt: CommandEvent) -> None:
|
|||||||
except MForbidden:
|
except MForbidden:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert evt.sender.command_status
|
assert(evt.sender.command_status)
|
||||||
req = {
|
req = {
|
||||||
"uuid": await evt.sender.get_uuid(),
|
"uuid": await evt.sender.get_uuid(),
|
||||||
"form": {
|
"form": {
|
||||||
@ -125,7 +125,7 @@ async def enter_password(evt: CommandEvent) -> None:
|
|||||||
|
|
||||||
|
|
||||||
async def enter_dv_code(evt: CommandEvent) -> None:
|
async def enter_dv_code(evt: CommandEvent) -> None:
|
||||||
assert evt.sender.command_status
|
assert(evt.sender.command_status)
|
||||||
req: dict = evt.sender.command_status["req"]
|
req: dict = evt.sender.command_status["req"]
|
||||||
passcode = evt.content.body
|
passcode = evt.content.body
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
|
@ -21,7 +21,7 @@ from asyncpg import Record
|
|||||||
from attr import dataclass, field
|
from attr import dataclass, field
|
||||||
|
|
||||||
from mautrix.types import EventID, RoomID
|
from mautrix.types import EventID, RoomID
|
||||||
from mautrix.util.async_db import Database, Scheme
|
from mautrix.util.async_db import Database
|
||||||
|
|
||||||
from ..kt.types.bson import Long
|
from ..kt.types.bson import Long
|
||||||
|
|
||||||
@ -101,30 +101,6 @@ class Message:
|
|||||||
"VALUES ($1, $2, $3, $4, $5, $6, $7)"
|
"VALUES ($1, $2, $3, $4, $5, $6, $7)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def bulk_create(
|
|
||||||
cls,
|
|
||||||
ktid: str,
|
|
||||||
kt_chat: int,
|
|
||||||
kt_receiver: int,
|
|
||||||
event_ids: list[EventID],
|
|
||||||
timestamp: int,
|
|
||||||
mx_room: RoomID,
|
|
||||||
) -> None:
|
|
||||||
if not event_ids:
|
|
||||||
return
|
|
||||||
columns = [col.strip('"') for col in cls.columns.split(", ")]
|
|
||||||
records = [
|
|
||||||
(mxid, mx_room, ktid, index, kt_chat, kt_receiver, timestamp)
|
|
||||||
for index, mxid in enumerate(event_ids)
|
|
||||||
]
|
|
||||||
async with cls.db.acquire() as conn, conn.transaction():
|
|
||||||
if cls.db.scheme == Scheme.POSTGRES:
|
|
||||||
await conn.copy_records_to_table("message", records=records, columns=columns)
|
|
||||||
else:
|
|
||||||
await conn.executemany(cls._insert_query, records)
|
|
||||||
|
|
||||||
|
|
||||||
async def insert(self) -> None:
|
async def insert(self) -> None:
|
||||||
q = self._insert_query
|
q = self._insert_query
|
||||||
await self.db.execute(
|
await self.db.execute(
|
||||||
|
@ -23,7 +23,7 @@ with any other potential backend.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, cast, Type, Optional, Union
|
from typing import TYPE_CHECKING, cast, Type, Optional, Union
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
import logging
|
import logging
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
@ -65,12 +65,6 @@ if TYPE_CHECKING:
|
|||||||
from ...user import User
|
from ...user import User
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def sandboxed_get(url: URL) -> _RequestContextManager:
|
|
||||||
async with ClientSession() as sess, sess.get(url) as resp:
|
|
||||||
yield resp
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Consider defining an interface for this, with node/native backend as swappable implementations
|
# TODO Consider defining an interface for this, with node/native backend as swappable implementations
|
||||||
# TODO If no state is stored, consider using free functions instead of classmethods
|
# TODO If no state is stored, consider using free functions instead of classmethods
|
||||||
class Client:
|
class Client:
|
||||||
@ -160,7 +154,6 @@ class Client:
|
|||||||
self,
|
self,
|
||||||
url: Union[str, URL],
|
url: Union[str, URL],
|
||||||
headers: Optional[dict[str, str]] = None,
|
headers: Optional[dict[str, str]] = None,
|
||||||
sandbox: bool = False,
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> _RequestContextManager:
|
) -> _RequestContextManager:
|
||||||
# TODO Is auth ever needed?
|
# TODO Is auth ever needed?
|
||||||
@ -170,8 +163,6 @@ class Client:
|
|||||||
**(headers or {}),
|
**(headers or {}),
|
||||||
}
|
}
|
||||||
url = URL(url)
|
url = URL(url)
|
||||||
if sandbox:
|
|
||||||
return sandboxed_get(url)
|
|
||||||
return self.http.get(url, headers=headers, **kwargs)
|
return self.http.get(url, headers=headers, **kwargs)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@ -213,7 +204,7 @@ class Client:
|
|||||||
profile_req_struct = await self._api_user_request_result(
|
profile_req_struct = await self._api_user_request_result(
|
||||||
ProfileReqStruct,
|
ProfileReqStruct,
|
||||||
"get_profile",
|
"get_profile",
|
||||||
user_id=user_id.serialize(),
|
user_id=user_id.serialize()
|
||||||
)
|
)
|
||||||
return profile_req_struct.profile
|
return profile_req_struct.profile
|
||||||
|
|
||||||
@ -228,7 +219,7 @@ class Client:
|
|||||||
return await self._api_user_request_result(
|
return await self._api_user_request_result(
|
||||||
ResultListType(UserInfoUnion),
|
ResultListType(UserInfoUnion),
|
||||||
"get_participants",
|
"get_participants",
|
||||||
channel_props=channel_props.serialize(),
|
channel_props=channel_props.serialize()
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_chats(self, channel_props: ChannelProps, sync_from: Long | None, limit: int | None) -> list[Chatlog]:
|
async def get_chats(self, channel_props: ChannelProps, sync_from: Long | None, limit: int | None) -> list[Chatlog]:
|
||||||
@ -237,7 +228,7 @@ class Client:
|
|||||||
"get_chats",
|
"get_chats",
|
||||||
channel_props=channel_props.serialize(),
|
channel_props=channel_props.serialize(),
|
||||||
sync_from=sync_from.serialize() if sync_from else None,
|
sync_from=sync_from.serialize() if sync_from else None,
|
||||||
limit=limit,
|
limit=limit
|
||||||
)
|
)
|
||||||
|
|
||||||
async def list_friends(self) -> FriendListStruct:
|
async def list_friends(self) -> FriendListStruct:
|
||||||
@ -251,7 +242,7 @@ class Client:
|
|||||||
Chatlog,
|
Chatlog,
|
||||||
"send_message",
|
"send_message",
|
||||||
channel_props=channel_props.serialize(),
|
channel_props=channel_props.serialize(),
|
||||||
text=text,
|
text=text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,14 +13,6 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
##from .web_client import *
|
"""
|
||||||
##from .xvc import * # as xvc
|
from .auth_api_client import *
|
||||||
#from .struct import * # as struct
|
"""
|
||||||
#from .auth_api_client import *
|
|
||||||
##from .service_api_client import *
|
|
||||||
##from .oauth_api_client import *
|
|
||||||
##from .attachment_api_client import *
|
|
||||||
##from .open_upload_api_client import *
|
|
||||||
##from .web_api_util import * # as webApiUtil
|
|
||||||
##from .header_util import * # as headerUtil
|
|
||||||
##from .service_api_util import * # as serviceApiUtil
|
|
||||||
|
@ -19,7 +19,8 @@ from attr import dataclass
|
|||||||
|
|
||||||
from mautrix.types import SerializableAttrs
|
from mautrix.types import SerializableAttrs
|
||||||
|
|
||||||
from .mention import *
|
from ..bson import Long
|
||||||
|
from .mention import MentionStruct
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
@ -27,28 +28,9 @@ class Attachment(SerializableAttrs):
|
|||||||
shout: Optional[bool] = None
|
shout: Optional[bool] = None
|
||||||
mentions: Optional[list[MentionStruct]] = None
|
mentions: Optional[list[MentionStruct]] = None
|
||||||
urls: Optional[list[str]] = None
|
urls: Optional[list[str]] = None
|
||||||
url: Optional[str] = None # NOTE Added since this may have replaced urls
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PathAttachment(Attachment):
|
class PathAttachment(Attachment):
|
||||||
path: str
|
path: str
|
||||||
s: int
|
s: int
|
||||||
|
|
||||||
|
|
||||||
from .media import *
|
|
||||||
from .reply import *
|
|
||||||
from .media import *
|
|
||||||
from .emoticon import *
|
|
||||||
#from .voip import *
|
|
||||||
from .contact import *
|
|
||||||
from .map import *
|
|
||||||
from .post import *
|
|
||||||
from .openlink import *
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Attachment",
|
|
||||||
"PathAttachment",
|
|
||||||
]
|
|
||||||
# TODO What about the import *s?
|
|
@ -19,7 +19,7 @@ from attr import dataclass
|
|||||||
|
|
||||||
from mautrix.types import SerializableAttrs
|
from mautrix.types import SerializableAttrs
|
||||||
|
|
||||||
from ...bson import Long
|
from ..bson import Long
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -27,8 +27,3 @@ class MentionStruct(SerializableAttrs):
|
|||||||
at: list[int]
|
at: list[int]
|
||||||
len: int
|
len: int
|
||||||
user_id: Union[Long, int]
|
user_id: Union[Long, int]
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"MentionStruct",
|
|
||||||
]
|
|
@ -13,6 +13,7 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from .attachment import *
|
"""
|
||||||
from .chat_type import *
|
from .chat_type import *
|
||||||
from .chat import *
|
from .chat import *
|
||||||
|
"""
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from attr import dataclass
|
|
||||||
|
|
||||||
from ...bson import Long
|
|
||||||
from . import Attachment
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ProfileAttachment(Attachment):
|
|
||||||
userId: Union[int, Long]
|
|
||||||
nickName: str
|
|
||||||
fullProfileImageUrl: str
|
|
||||||
profileImageUrl: str
|
|
||||||
statusMessage: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ContactAttachment(Attachment):
|
|
||||||
name: str
|
|
||||||
url: str
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"ProfileAttachment",
|
|
||||||
"ContactAttachment",
|
|
||||||
]
|
|
@ -1,37 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from attr import dataclass
|
|
||||||
|
|
||||||
from . import Attachment
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class EmoticonAttachment(Attachment):
|
|
||||||
path: str
|
|
||||||
name: str
|
|
||||||
type: str
|
|
||||||
alt: str
|
|
||||||
s: Optional[int] = None
|
|
||||||
sound: Optional[str] = None
|
|
||||||
width: Optional[int] = None
|
|
||||||
height: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"EmoticonAttachment",
|
|
||||||
]
|
|
@ -1,31 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
from attr import dataclass
|
|
||||||
|
|
||||||
from . import Attachment
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MapAttachment(Attachment):
|
|
||||||
lat: int
|
|
||||||
lng: int
|
|
||||||
a: str
|
|
||||||
c: bool
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"MapAttachment",
|
|
||||||
]
|
|
@ -1,93 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from attr import dataclass
|
|
||||||
|
|
||||||
from . import Attachment
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MediaKeyAttachment(Attachment):
|
|
||||||
k: str
|
|
||||||
url: str
|
|
||||||
s: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class PhotoAttachment(Attachment):
|
|
||||||
w: int
|
|
||||||
h: int
|
|
||||||
thumbnailUrl: str
|
|
||||||
thumbnailWidth: int
|
|
||||||
thumbnailHeight: int
|
|
||||||
cs: str
|
|
||||||
mt: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MultiPhotoAttachment(Attachment):
|
|
||||||
kl: list[str]
|
|
||||||
wl: list[int]
|
|
||||||
hl: list[int]
|
|
||||||
csl: list[str]
|
|
||||||
imageUrls: list[str]
|
|
||||||
thumbnailUrls: list[str]
|
|
||||||
thumbnailWidths: list[int]
|
|
||||||
thumbnailHeights: list[int]
|
|
||||||
sl: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class VideoAttachment(Attachment):
|
|
||||||
tk: str
|
|
||||||
w: int
|
|
||||||
h: int
|
|
||||||
cs: str
|
|
||||||
d: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class FileAttachment(Attachment):
|
|
||||||
name: str
|
|
||||||
size: int
|
|
||||||
expire: int
|
|
||||||
cs: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AudioAttachment(Attachment):
|
|
||||||
d: int
|
|
||||||
expire: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LongTextAttachment(Attachment):
|
|
||||||
path: str
|
|
||||||
k: str
|
|
||||||
s: int
|
|
||||||
sd: bool
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"MediaKeyAttachment",
|
|
||||||
"PhotoAttachment",
|
|
||||||
"MultiPhotoAttachment",
|
|
||||||
"VideoAttachment",
|
|
||||||
"FileAttachment",
|
|
||||||
"AudioAttachment",
|
|
||||||
"LongTextAttachment",
|
|
||||||
]
|
|
@ -1,30 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
from attr import dataclass
|
|
||||||
|
|
||||||
from .post import PostAttachment
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OpenScheduleAttachment(PostAttachment):
|
|
||||||
scheduleId: int
|
|
||||||
title: str
|
|
||||||
eventAt: int
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"OpenScheduleAttachment",
|
|
||||||
]
|
|
@ -1,141 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
from typing import Optional, Union
|
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
from attr import dataclass
|
|
||||||
|
|
||||||
from mautrix.types import SerializableAttrs
|
|
||||||
|
|
||||||
from ...bson import Long
|
|
||||||
from . import Attachment
|
|
||||||
from .emoticon import EmoticonAttachment
|
|
||||||
|
|
||||||
|
|
||||||
class KnownPostItemType(IntEnum):
|
|
||||||
TEXT = 1
|
|
||||||
FOOTER = 2
|
|
||||||
HEADER = 3
|
|
||||||
EMOTICON = 4
|
|
||||||
IMAGE = 5
|
|
||||||
VIDEO = 6
|
|
||||||
FILE = 7
|
|
||||||
SCHEDULE = 8
|
|
||||||
VOTE = 9
|
|
||||||
SCRAP = 10
|
|
||||||
|
|
||||||
PostItemType = Union[KnownPostItemType, int]
|
|
||||||
|
|
||||||
|
|
||||||
class KnownPostSubItemType(IntEnum):
|
|
||||||
pass
|
|
||||||
|
|
||||||
PostSubItemType = Union[KnownPostSubItemType, int]
|
|
||||||
|
|
||||||
|
|
||||||
class KnownPostFooterStyle(IntEnum):
|
|
||||||
ARTICLE = 1
|
|
||||||
SCHEDULE = 2
|
|
||||||
SCHEDULE_ANSWER = 3
|
|
||||||
VOTE = 4
|
|
||||||
VOTE_RESULT = 5
|
|
||||||
|
|
||||||
PostFooterStyle = Union[KnownPostFooterStyle, int]
|
|
||||||
|
|
||||||
|
|
||||||
class PostItem:
|
|
||||||
@dataclass
|
|
||||||
class Unknown(SerializableAttrs):
|
|
||||||
t: PostItemType
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class Text(Unknown):
|
|
||||||
t = KnownPostItemType.TEXT
|
|
||||||
ct: str
|
|
||||||
jct: str
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class Header(Unknown):
|
|
||||||
t = KnownPostItemType.HEADER
|
|
||||||
st: int
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class UDict(SerializableAttrs):
|
|
||||||
id: Union[int, Long]
|
|
||||||
|
|
||||||
u: Optional[UDict]
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class Image(Unknown):
|
|
||||||
t = KnownPostItemType.IMAGE
|
|
||||||
tt: Optional[str] = None
|
|
||||||
th: list[str]
|
|
||||||
g: Optional[bool] = None
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class Emoticon(Unknown):
|
|
||||||
t = KnownPostItemType.EMOTICON
|
|
||||||
ct: EmoticonAttachment
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class Vote(Unknown):
|
|
||||||
t = KnownPostItemType.VOTE
|
|
||||||
st: int
|
|
||||||
tt: str
|
|
||||||
ittpe: Optional[str] = None
|
|
||||||
its: list[dict]
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class Video(Unknown):
|
|
||||||
t = KnownPostItemType.VIDEO
|
|
||||||
th: str
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class File(Unknown):
|
|
||||||
t = KnownPostItemType.FILE
|
|
||||||
tt: str
|
|
||||||
c: int
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class Footer(Unknown):
|
|
||||||
t = KnownPostItemType.FOOTER
|
|
||||||
st: PostFooterStyle
|
|
||||||
url: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class PostAttachment(Attachment):
|
|
||||||
subtype: Optional[PostSubItemType] = None
|
|
||||||
os: list[PostItem.Unknown]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class VoteAttachment(PostAttachment):
|
|
||||||
voteId: int
|
|
||||||
title: str
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"KnownPostItemType",
|
|
||||||
"PostItemType",
|
|
||||||
"KnownPostSubItemType",
|
|
||||||
"PostSubItemType",
|
|
||||||
"KnownPostFooterStyle",
|
|
||||||
"PostFooterStyle",
|
|
||||||
"PostItem",
|
|
||||||
"PostAttachment",
|
|
||||||
"VoteAttachment",
|
|
||||||
]
|
|
@ -1,40 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from attr import dataclass
|
|
||||||
|
|
||||||
from ...bson import Long
|
|
||||||
from . import Attachment
|
|
||||||
from ..chat_type import ChatType
|
|
||||||
from .mention import MentionStruct
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class ReplyAttachment(Attachment):
|
|
||||||
attach_only: bool
|
|
||||||
attach_type: int
|
|
||||||
src_linkId: Optional[Long] = None
|
|
||||||
src_logId: Long
|
|
||||||
src_mentions: list[MentionStruct]
|
|
||||||
src_message: str
|
|
||||||
src_type: ChatType
|
|
||||||
src_userId: Long
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"ReplyAttachment",
|
|
||||||
]
|
|
@ -13,30 +13,16 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from typing import Optional, Union, Type
|
from typing import Optional, Union
|
||||||
|
|
||||||
from attr import dataclass
|
from attr import dataclass
|
||||||
|
|
||||||
from mautrix.types import SerializableAttrs, JSON
|
from mautrix.types import SerializableAttrs
|
||||||
|
|
||||||
from ..bson import Long
|
from ..bson import Long
|
||||||
from ..user.channel_user import ChannelUser
|
from ..user.channel_user import ChannelUser
|
||||||
from .attachment import (
|
from ..attachment import Attachment
|
||||||
Attachment,
|
from .chat_type import ChatType
|
||||||
PhotoAttachment,
|
|
||||||
VideoAttachment,
|
|
||||||
ContactAttachment,
|
|
||||||
AudioAttachment,
|
|
||||||
EmoticonAttachment,
|
|
||||||
OpenScheduleAttachment,
|
|
||||||
VoteAttachment,
|
|
||||||
MapAttachment,
|
|
||||||
ProfileAttachment,
|
|
||||||
FileAttachment,
|
|
||||||
PostAttachment,
|
|
||||||
ReplyAttachment,
|
|
||||||
)
|
|
||||||
from .chat_type import ChatType, KnownChatType
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -49,36 +35,6 @@ class Chat(ChatTypeComponent):
|
|||||||
attachment: Optional[Attachment] = None
|
attachment: Optional[Attachment] = None
|
||||||
supplement: Optional[dict] = None
|
supplement: Optional[dict] = None
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialize(cls, data: JSON) -> "Chat":
|
|
||||||
if "attachment" in data:
|
|
||||||
attachment = _attachment_type_map.get(int(data["type"]), Attachment).deserialize(data["attachment"])
|
|
||||||
del data["attachment"]
|
|
||||||
else:
|
|
||||||
attachment = None
|
|
||||||
obj = super().deserialize(data)
|
|
||||||
obj.attachment = attachment
|
|
||||||
return obj
|
|
||||||
|
|
||||||
# TODO More
|
|
||||||
_attachment_type_map: dict[KnownChatType, Type[Attachment]] = {
|
|
||||||
KnownChatType.PHOTO: PhotoAttachment,
|
|
||||||
KnownChatType.VIDEO: VideoAttachment,
|
|
||||||
KnownChatType.CONTACT: ContactAttachment,
|
|
||||||
KnownChatType.AUDIO: AudioAttachment,
|
|
||||||
KnownChatType.DITEMEMOTICON: EmoticonAttachment,
|
|
||||||
KnownChatType.SCHEDULE: OpenScheduleAttachment,
|
|
||||||
KnownChatType.VOTE: VoteAttachment,
|
|
||||||
KnownChatType.MAP: MapAttachment,
|
|
||||||
KnownChatType.PROFILE: ProfileAttachment,
|
|
||||||
KnownChatType.FILE: FileAttachment,
|
|
||||||
KnownChatType.POST: PostAttachment,
|
|
||||||
KnownChatType.REPLY: ReplyAttachment,
|
|
||||||
KnownChatType.OPEN_SCHEDULE: OpenScheduleAttachment,
|
|
||||||
KnownChatType.OPEN_VOTE: VoteAttachment,
|
|
||||||
KnownChatType.OPEN_POST: PostAttachment,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TypedChat(Chat, ChatTypeComponent):
|
class TypedChat(Chat, ChatTypeComponent):
|
||||||
|
@ -15,16 +15,7 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Any, AsyncGenerator, Pattern, cast
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
|
||||||
AsyncGenerator,
|
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
Pattern,
|
|
||||||
cast,
|
|
||||||
)
|
|
||||||
from io import BytesIO
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@ -33,13 +24,9 @@ from mautrix.appservice import IntentAPI
|
|||||||
from mautrix.bridge import BasePortal, NotificationDisabler, async_getter_lock
|
from mautrix.bridge import BasePortal, NotificationDisabler, async_getter_lock
|
||||||
from mautrix.errors import MatrixError
|
from mautrix.errors import MatrixError
|
||||||
from mautrix.types import (
|
from mautrix.types import (
|
||||||
AudioInfo,
|
|
||||||
ContentURI,
|
ContentURI,
|
||||||
EncryptedFile,
|
|
||||||
EventID,
|
EventID,
|
||||||
EventType,
|
EventType,
|
||||||
FileInfo,
|
|
||||||
ImageInfo,
|
|
||||||
LocationMessageEventContent,
|
LocationMessageEventContent,
|
||||||
MediaMessageEventContent,
|
MediaMessageEventContent,
|
||||||
Membership,
|
Membership,
|
||||||
@ -48,9 +35,7 @@ from mautrix.types import (
|
|||||||
RoomID,
|
RoomID,
|
||||||
TextMessageEventContent,
|
TextMessageEventContent,
|
||||||
UserID,
|
UserID,
|
||||||
VideoInfo,
|
|
||||||
)
|
)
|
||||||
from mautrix.util import ffmpeg, magic, variation_selector
|
|
||||||
from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus
|
from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus
|
||||||
from mautrix.util.simple_lock import SimpleLock
|
from mautrix.util.simple_lock import SimpleLock
|
||||||
|
|
||||||
@ -65,10 +50,7 @@ from .formatter import kakaotalk_to_matrix, matrix_to_kakaotalk
|
|||||||
from .kt.types.bson import Long
|
from .kt.types.bson import Long
|
||||||
from .kt.types.channel.channel_info import ChannelInfo
|
from .kt.types.channel.channel_info import ChannelInfo
|
||||||
from .kt.types.channel.channel_type import KnownChannelType, ChannelType
|
from .kt.types.channel.channel_type import KnownChannelType, ChannelType
|
||||||
from .kt.types.chat import Chatlog, KnownChatType
|
from .kt.types.chat.chat import Chatlog
|
||||||
from .kt.types.chat.attachment import (
|
|
||||||
PhotoAttachment,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .kt.client.types import UserInfoUnion, PortalChannelInfo, ChannelProps
|
from .kt.client.types import UserInfoUnion, PortalChannelInfo, ChannelProps
|
||||||
from .kt.client.errors import CommandException
|
from .kt.client.errors import CommandException
|
||||||
@ -176,18 +158,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
NotificationDisabler.puppet_cls = p.Puppet
|
NotificationDisabler.puppet_cls = p.Puppet
|
||||||
NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"]
|
NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"]
|
||||||
|
|
||||||
# TODO More
|
|
||||||
cls._message_handler_type_map: dict[
|
|
||||||
KnownChatType,
|
|
||||||
Callable[[Portal, u.User, IntentAPI, Chatlog], Awaitable[list[EventID]]]
|
|
||||||
] = {
|
|
||||||
KnownChatType.TEXT: cls._handle_remote_text,
|
|
||||||
KnownChatType.PHOTO: cls._handle_remote_photo,
|
|
||||||
#KnownChatType.VIDEO: cls._handle_remote_video,
|
|
||||||
#KnownChatType.AUDIO: cls._handle_remote_audio,
|
|
||||||
#KnownChatType.FILE: cls._handle_remote_file,
|
|
||||||
}
|
|
||||||
|
|
||||||
# region DB conversion
|
# region DB conversion
|
||||||
|
|
||||||
async def delete(self) -> None:
|
async def delete(self) -> None:
|
||||||
@ -298,8 +268,9 @@ class Portal(DBPortal, BasePortal):
|
|||||||
await self.save()
|
await self.save()
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _reupload_remote_file(
|
async def _reupload_kt_file(
|
||||||
cls,
|
cls,
|
||||||
url: str,
|
url: str,
|
||||||
source: u.User,
|
source: u.User,
|
||||||
@ -307,19 +278,20 @@ class Portal(DBPortal, BasePortal):
|
|||||||
*,
|
*,
|
||||||
filename: str | None = None,
|
filename: str | None = None,
|
||||||
encrypt: bool = False,
|
encrypt: bool = False,
|
||||||
|
referer: str = "messenger_thread_photo",
|
||||||
find_size: bool = False,
|
find_size: bool = False,
|
||||||
convert_audio: bool = False,
|
convert_audio: bool = False,
|
||||||
) -> tuple[ContentURI, FileInfo | VideoInfo | AudioInfo | ImageInfo, EncryptedFile | None]:
|
) -> tuple[ContentURI, FileInfo | VideoInfo | AudioInfo | ImageInfo, EncryptedFile | None]:
|
||||||
if not url:
|
if not url:
|
||||||
raise ValueError("URL not provided")
|
raise ValueError("URL not provided")
|
||||||
|
headers = {"referer": f"fbapp://{source.state.application.client_id}/{referer}"}
|
||||||
sandbox = cls.config["bridge.sandbox_media_download"]
|
sandbox = cls.config["bridge.sandbox_media_download"]
|
||||||
# TODO Referer header?
|
async with source.client.get(url, headers=headers, sandbox=sandbox) as resp:
|
||||||
async with source.client.get(url, sandbox=sandbox) as resp:
|
|
||||||
length = int(resp.headers["Content-Length"])
|
length = int(resp.headers["Content-Length"])
|
||||||
if length > cls.matrix.media_config.upload_size:
|
if length > cls.matrix.media_config.upload_size:
|
||||||
raise ValueError("File not available: too large")
|
raise ValueError("File not available: too large")
|
||||||
data = await resp.read()
|
data = await resp.read()
|
||||||
mime = magic.mimetype(data)
|
mime = magic.from_buffer(data, mime=True)
|
||||||
if convert_audio and mime != "audio/ogg":
|
if convert_audio and mime != "audio/ogg":
|
||||||
data = await ffmpeg.convert_bytes(
|
data = await ffmpeg.convert_bytes(
|
||||||
data, ".ogg", output_args=("-c:a", "libopus"), input_mime=mime
|
data, ".ogg", output_args=("-c:a", "libopus"), input_mime=mime
|
||||||
@ -340,6 +312,7 @@ class Portal(DBPortal, BasePortal):
|
|||||||
if decryption_info:
|
if decryption_info:
|
||||||
decryption_info.url = url
|
decryption_info.url = url
|
||||||
return url, info, decryption_info
|
return url, info, decryption_info
|
||||||
|
"""
|
||||||
|
|
||||||
async def _update_name(self, name: str) -> bool:
|
async def _update_name(self, name: str) -> bool:
|
||||||
if not name:
|
if not name:
|
||||||
@ -886,9 +859,10 @@ class Portal(DBPortal, BasePortal):
|
|||||||
source: u.User,
|
source: u.User,
|
||||||
sender: p.Puppet,
|
sender: p.Puppet,
|
||||||
message: Chatlog,
|
message: Chatlog,
|
||||||
|
reply_to: Chatlog | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
await self._handle_remote_message(source, sender, message)
|
await self._handle_remote_message(source, sender, message, reply_to)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.exception(
|
self.log.exception(
|
||||||
"Error handling KakaoTalk message %s",
|
"Error handling KakaoTalk message %s",
|
||||||
@ -900,6 +874,7 @@ class Portal(DBPortal, BasePortal):
|
|||||||
source: u.User,
|
source: u.User,
|
||||||
sender: p.Puppet,
|
sender: p.Puppet,
|
||||||
message: Chatlog,
|
message: Chatlog,
|
||||||
|
reply_to: Chatlog | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.log.debug(f"Handling KakaoTalk event {message.logId}")
|
self.log.debug(f"Handling KakaoTalk event {message.logId}")
|
||||||
if not self.mxid:
|
if not self.mxid:
|
||||||
@ -921,59 +896,25 @@ class Portal(DBPortal, BasePortal):
|
|||||||
await intent.ensure_joined(self.mxid)
|
await intent.ensure_joined(self.mxid)
|
||||||
self._backfill_leave.add(intent)
|
self._backfill_leave.add(intent)
|
||||||
|
|
||||||
event_ids = []
|
if message.attachment:
|
||||||
handler = self._message_handler_type_map.get(message.type)
|
self.log.info("TODO: _handle_remote_message attachments")
|
||||||
if not handler:
|
if message.supplement:
|
||||||
self.log.warning(f"No handler for message type {message.type}, falling back to text")
|
self.log.info("TODO: _handle_remote_message supplements")
|
||||||
handler = Portal._handle_remote_text
|
if message.text:
|
||||||
event_ids += await handler(self, source, intent, message)
|
content = await kakaotalk_to_matrix(message.text)
|
||||||
if not event_ids:
|
event_id = await self._send_message(intent, content, timestamp=message.sendAt)
|
||||||
|
await DBMessage(
|
||||||
|
mxid=event_id,
|
||||||
|
mx_room=self.mxid,
|
||||||
|
ktid=message.logId,
|
||||||
|
index=0,
|
||||||
|
kt_chat=self.ktid,
|
||||||
|
kt_receiver=self.kt_receiver,
|
||||||
|
timestamp=message.sendAt,
|
||||||
|
).insert()
|
||||||
|
await self._send_delivery_receipt(event_id)
|
||||||
|
else:
|
||||||
self.log.warning(f"Unhandled KakaoTalk message {message.logId}")
|
self.log.warning(f"Unhandled KakaoTalk message {message.logId}")
|
||||||
return
|
|
||||||
event_ids = [event_id for event_id in event_ids if event_id]
|
|
||||||
self.log.debug(f"Handled KakaoTalk message {message.logId} -> {event_ids}")
|
|
||||||
await DBMessage.bulk_create(
|
|
||||||
ktid=message.logId,
|
|
||||||
kt_chat=self.ktid,
|
|
||||||
kt_receiver=self.kt_receiver,
|
|
||||||
mx_room=self.mxid,
|
|
||||||
timestamp=message.sendAt,
|
|
||||||
event_ids=event_ids,
|
|
||||||
)
|
|
||||||
await self._send_delivery_receipt(event_ids[-1])
|
|
||||||
|
|
||||||
async def _handle_remote_text(self, source: u.User, intent: IntentAPI, message: Chatlog) -> list[EventID]:
|
|
||||||
# TODO Handle mentions properly
|
|
||||||
content = await kakaotalk_to_matrix(message.text)
|
|
||||||
# TODO Replies
|
|
||||||
return [await self._send_message(intent, content, timestamp=message.sendAt)]
|
|
||||||
|
|
||||||
async def _handle_remote_photo(self, source: u.User, intent: IntentAPI, message: Chatlog) -> list[EventID]:
|
|
||||||
assert message.attachment
|
|
||||||
assert message.attachment.url or message.attachment.urls
|
|
||||||
url = message.attachment.url or message.attachment.urls[0]
|
|
||||||
assert isinstance(message.attachment, PhotoAttachment)
|
|
||||||
info = ImageInfo(
|
|
||||||
width=message.attachment.w,
|
|
||||||
height=message.attachment.h,
|
|
||||||
)
|
|
||||||
# TODO Animated images?
|
|
||||||
mxc, additional_info, decryption_info = await self._reupload_remote_file(
|
|
||||||
url,
|
|
||||||
source,
|
|
||||||
intent,
|
|
||||||
encrypt=self.encrypted,
|
|
||||||
find_size=False,
|
|
||||||
)
|
|
||||||
info.size = additional_info.size
|
|
||||||
info.mimetype = additional_info.mimetype
|
|
||||||
content = MediaMessageEventContent(
|
|
||||||
url=mxc, file=decryption_info, msgtype=MessageType.IMAGE, body=message.text, info=info
|
|
||||||
)
|
|
||||||
if not content:
|
|
||||||
return []
|
|
||||||
# TODO Replies
|
|
||||||
return [await self._send_message(intent, content, timestamp=message.sendAt)]
|
|
||||||
|
|
||||||
# TODO Many more remote handlers
|
# TODO Many more remote handlers
|
||||||
|
|
||||||
|
@ -606,6 +606,7 @@ class User(DBUser, BaseUser):
|
|||||||
await portal.backfill_lock.wait(evt.logId)
|
await portal.backfill_lock.wait(evt.logId)
|
||||||
if not puppet.name:
|
if not puppet.name:
|
||||||
portal.schedule_resync(self, puppet)
|
portal.schedule_resync(self, puppet)
|
||||||
|
# TODO reply_to
|
||||||
await portal.handle_remote_message(self, puppet, evt)
|
await portal.handle_remote_message(self, puppet, evt)
|
||||||
|
|
||||||
# TODO Many more handlers
|
# TODO Many more handlers
|
||||||
|
Loading…
Reference in New Issue
Block a user