Remove more Python 3.5 compatibility

This commit is contained in:
Tulir Asokan
2019-07-09 22:15:24 +03:00
parent be6d395ed6
commit e0d3c940f8
9 changed files with 172 additions and 108 deletions
+6 -6
View File
@@ -73,7 +73,7 @@ if args.generate_registration:
sys.exit(0)
logging.config.dictConfig(copy.deepcopy(config["logging"]))
log = logging.getLogger("mau.init") # type: logging.Logger
log: logging.Logger = logging.getLogger("mau.init")
log.debug(f"Initializing mautrix-telegram {__version__}")
db_engine = sql.create_engine(config["appservice.database"] or "sqlite:///mautrix-telegram.db")
@@ -91,7 +91,7 @@ try:
except ImportError:
pass
loop = asyncio.get_event_loop() # type: asyncio.AbstractEventLoop
loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
state_store = SQLStateStore()
mebibyte = 1024 ** 2
@@ -123,7 +123,7 @@ if config["metrics.enabled"]:
if prometheus:
prometheus.start_http_server(config["metrics.listen_port"])
else:
log.warn("Metrics are enabled in the config, but prometheus-async is not installed.")
log.warn("Metrics are enabled in the config, but prometheus_client is not installed.")
with appserv.run(config["appservice.hostname"], config["appservice.port"]) as start:
start_ts = time()
@@ -131,9 +131,9 @@ with appserv.run(config["appservice.hostname"], config["appservice.port"]) as st
init_abstract_user(context)
init_formatter(context)
init_portal(context)
startup_actions = (init_puppet(context) +
init_user(context) +
[start, context.mx.init_as_bot()]) # type: List[Awaitable[Any]]
startup_actions: List[Awaitable[Any]] = (init_puppet(context) +
init_user(context) +
[start, context.mx.init_as_bot()])
if context.bot:
startup_actions.append(context.bot.start())
+35 -21
View File
@@ -42,9 +42,9 @@ if TYPE_CHECKING:
from .config import Config
from .bot import Bot
config = None # type: Config
config: Optional['Config'] = None
# Value updated from config in init()
MAX_DELETIONS = 10 # type: int
MAX_DELETIONS: int = 10
UpdateMessage = Union[UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage,
UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage]
@@ -59,26 +59,41 @@ except ImportError:
Histogram = None
UPDATE_TIME = None
class AbstractUser(ABC):
session_container = None # type: AlchemySessionContainer
loop = None # type: asyncio.AbstractEventLoop
log = None # type: logging.Logger
az = None # type: AppService
bot = None # type: Bot
ignore_incoming_bot_events = True # type: bool
session_container: AlchemySessionContainer = None
loop: asyncio.AbstractEventLoop = None
log: logging.Logger
az: AppService
bot: 'Bot'
ignore_incoming_bot_events: bool = True
client: Optional[MautrixTelegramClient]
mxid: Optional[MatrixUserID]
tgid: Optional[TelegramID]
username: Optional['str']
is_bot: bool
is_relaybot: bool
relaybot: Optional['Bot']
puppet_whitelisted: bool
whitelisted: bool
relaybot_whitelisted: bool
matrix_puppet_whitelisted: bool
is_admin: bool
def __init__(self) -> None:
self.is_admin = False # type: bool
self.matrix_puppet_whitelisted = False # type: bool
self.puppet_whitelisted = False # type: bool
self.whitelisted = False # type: bool
self.relaybot_whitelisted = False # type: bool
self.client = None # type: MautrixTelegramClient
self.tgid = None # type: TelegramID
self.mxid = None # type: MatrixUserID
self.is_relaybot = False # type: bool
self.is_bot = False # type: bool
self.relaybot = None # type: Optional[Bot]
self.is_admin = False
self.matrix_puppet_whitelisted = False
self.puppet_whitelisted = False
self.whitelisted = False
self.relaybot_whitelisted = False
self.client = None
self.is_relaybot = False
self.is_bot = False
self.relaybot = None
@property
def connected(self) -> bool:
@@ -367,7 +382,6 @@ class AbstractUser(ABC):
message.delete()
number_left = DBMessage.count_spaces_by_mxid(message.mxid, message.mx_room)
if number_left == 0:
portal = po.Portal.get_by_mxid(message.mx_room)
await self._try_redact(message)
async def delete_channel_message(self, update: UpdateDeleteChannelMessages) -> None:
@@ -414,7 +428,7 @@ class AbstractUser(ABC):
# endregion
def init(context: "Context") -> None:
def init(context: 'Context') -> None:
global config, MAX_DELETIONS
AbstractUser.az, config, AbstractUser.loop, AbstractUser.relaybot = context.core
AbstractUser.ignore_incoming_bot_events = config["bridge.relaybot.ignore_own_incoming_events"]
+25 -17
View File
@@ -35,32 +35,40 @@ from . import puppet as pu, portal as po, user as u
if TYPE_CHECKING:
from .config import Config
from .context import Context
config = None # type: Config
config: Optional['Config'] = None
ReplyFunc = Callable[[str], Awaitable[Message]]
class Bot(AbstractUser):
log = logging.getLogger("mau.bot") # type: logging.Logger
mxid_regex = re.compile("@.+:.+") # type: Pattern
log: logging.Logger = logging.getLogger("mau.bot")
mxid_regex: Pattern = re.compile("@.+:.+")
token: str
chats: Dict[int, str]
tg_whitelist: List[int]
whitelist_group_admins: bool
_me_info: Optional[User]
_me_mxid: Optional[MatrixUserID]
def __init__(self, token: str) -> None:
super().__init__()
self.token = token # type: str
self.puppet_whitelisted = True # type: bool
self.whitelisted = True # type: bool
self.relaybot_whitelisted = True # type: bool
self.username = None # type: str
self.is_relaybot = True # type: bool
self.is_bot = True # type: bool
self.chats = {} # type: Dict[int, str]
self.tg_whitelist = [] # type: List[int]
self.token = token
self.tgid = None
self.mxid = None
self.puppet_whitelisted = True
self.whitelisted = True
self.relaybot_whitelisted = True
self.username = None
self.is_relaybot = True
self.is_bot = True
self.chats = {}
self.tg_whitelist = []
self.whitelist_group_admins = (config["bridge.relaybot.whitelist_group_admins"]
or False) # type: bool
self._me_info = None # type: Optional[User]
self._me_mxid = None # type: Optional[MatrixUserID]
or False)
self._me_info = None
self._me_mxid = None
async def get_me(self, use_cache: bool = True) -> Tuple[User, MatrixUserID]:
if not use_cache or not self._me_mxid:
@@ -91,7 +99,7 @@ class Bot(AbstractUser):
async def post_login(self) -> None:
await self.init_permissions()
info = await self.client.get_me()
self.tgid = info.id
self.tgid = TelegramID(info.id)
self.username = info.username
self.mxid = pu.Puppet.get_mxid_from_id(self.tgid)
+21 -13
View File
@@ -19,13 +19,15 @@ from ruamel.yaml.comments import CommentedMap
import random
import string
yaml = YAML() # type: YAML
yaml: YAML = YAML()
yaml.indent(4)
class DictWithRecursion:
_data: CommentedMap
def __init__(self, data: Optional[CommentedMap] = None) -> None:
self._data = data or CommentedMap() # type: CommentedMap
self._data = data or CommentedMap()
@staticmethod
def _parse_key(key: str) -> Tuple[str, Optional[str]]:
@@ -102,14 +104,20 @@ class DictWithRecursion:
class Config(DictWithRecursion):
path: str
registration_path: str
base_path: str
_registration: Optional[Dict[str, Any]]
_overrides: Dict[str, Any]
def __init__(self, path: str, registration_path: str, base_path: str,
overrides: Dict[str, Any] = None) -> None:
super().__init__()
self.path = path # type: str
self.registration_path = registration_path # type: str
self.base_path = base_path # type: str
self._registration = None # type: Optional[Dict]
self._overrides = overrides or {} # type: Dict[str, Any]
self.path = path
self.registration_path = registration_path
self.base_path = base_path
self._registration = None
self._overrides = overrides or {}
def __getitem__(self, key: str) -> Any:
try:
@@ -327,10 +335,10 @@ class Config(DictWithRecursion):
def generate_registration(self) -> None:
homeserver = self["homeserver.domain"]
username_format = self.get("bridge.username_template", "telegram_{userid}") \
.format(userid=".+")
alias_format = self.get("bridge.alias_template", "telegram_{groupname}") \
.format(groupname=".+")
username_format = self.get("bridge.username_template",
"telegram_{userid}").format(userid=".+")
alias_format = self.get("bridge.alias_template",
"telegram_{groupname}").format(groupname=".+")
self.set("appservice.as_token", self._new_token())
self.set("appservice.hs_token", self._new_token())
@@ -354,5 +362,5 @@ class Config(DictWithRecursion):
"rate_limited": False
}
if self["appservice.community_id"]:
self._registration["namespaces"]["users"][0]["group_id"] \
= self["appservice.community_id"]
self._registration["namespaces"]["users"][0]["group_id"] = self[
"appservice.community_id"]
+17 -8
View File
@@ -28,16 +28,25 @@ if TYPE_CHECKING:
class Context:
az: 'AppService'
config: 'Config'
loop: 'asyncio.AbstractEventLoop'
bot: Optional['Bot']
mx: Optional['MatrixHandler']
session_container: 'AlchemySessionContainer'
public_website: Optional['PublicBridgeWebsite']
provisioning_api: Optional['ProvisioningAPI']
def __init__(self, az: 'AppService', config: 'Config', loop: 'asyncio.AbstractEventLoop',
session_container: 'AlchemySessionContainer', bot: Optional['Bot']) -> None:
self.az = az # type: AppService
self.config = config # type: Config
self.loop = loop # type: asyncio.AbstractEventLoop
self.bot = bot # type: Optional[Bot]
self.mx = None # type: Optional[MatrixHandler]
self.session_container = session_container # type: AlchemySessionContainer
self.public_website = None # type: Optional[PublicBridgeWebsite]
self.provisioning_api = None # type: Optional[ProvisioningAPI]
self.az = az
self.config = config
self.loop = loop
self.bot = bot
self.mx = None
self.session_container = session_container
self.public_website = None
self.provisioning_api = None
@property
def core(self) -> Tuple['AppService', 'Config', 'asyncio.AbstractEventLoop', Optional['Bot']]:
+34 -24
View File
@@ -26,6 +26,9 @@ from . import user as u, portal as po, puppet as pu, commands as com
if TYPE_CHECKING:
from .context import Context
from .config import Config
from .bot import Bot
from mautrix_appservice import AppService
try:
from prometheus_client import Histogram
@@ -38,12 +41,17 @@ except ImportError:
class MatrixHandler:
log = logging.getLogger("mau.mx") # type: logging.Logger
log: logging.Logger = logging.getLogger("mau.mx")
az: 'AppService'
config: 'Config'
bot: 'Bot'
commands: 'com.CommandProcessor'
previously_typing: Dict[MatrixRoomID, Set[MatrixUserID]]
def __init__(self, context: 'Context') -> None:
self.az, self.config, _, self.tgbot = context.core
self.commands = com.CommandProcessor(context) # type: com.CommandProcessor
self.previously_typing = [] # type: List[MatrixUserID]
self.commands = com.CommandProcessor(context)
self.previously_typing = {}
self.az.matrix_event_handler(self.handle_event)
@@ -372,14 +380,16 @@ class MatrixHandler:
return
await user.set_presence(presence == "online")
async def handle_typing(self, room_id: MatrixRoomID, now_typing: List[MatrixUserID]) -> None:
async def handle_typing(self, room_id: MatrixRoomID, now_typing: Set[MatrixUserID]) -> None:
portal = po.Portal.get_by_mxid(room_id)
if not portal:
return
for user_id in set(self.previously_typing + now_typing):
previously_typing = self.previously_typing.get(room_id, set())
for user_id in set(previously_typing | now_typing):
is_typing = user_id in now_typing
was_typing = user_id in self.previously_typing
was_typing = user_id in previously_typing
if is_typing and was_typing:
continue
@@ -389,7 +399,7 @@ class MatrixHandler:
await portal.set_typing(user, is_typing)
self.previously_typing = now_typing
self.previously_typing[room_id] = now_typing
def filter_matrix_event(self, event: MatrixEvent) -> bool:
sender = event.get("sender", None)
@@ -405,38 +415,38 @@ class MatrixHandler:
self.log.exception("Error handling manually received Matrix event")
async def handle_ephemeral_event(self, evt: MatrixEvent) -> None:
evt_type = evt.get("type", "m.unknown") # type: str
room_id = evt.get("room_id", None) # type: Optional[MatrixRoomID]
sender = evt.get("sender", None) # type: Optional[MatrixUserID]
content = evt.get("content", {}) # type: Dict
evt_type: str = evt.get("type", "m.unknown")
room_id: Optional[MatrixRoomID] = evt.get("room_id", None)
sender: Optional[MatrixUserID] = evt.get("sender", None)
content: Dict = evt.get("content", {})
if evt_type == "m.receipt":
await self.handle_read_receipts(room_id, self.parse_read_receipts(content))
elif evt_type == "m.presence":
await self.handle_presence(sender, content.get("presence", "offline"))
elif evt_type == "m.typing":
await self.handle_typing(room_id, content.get("user_ids", []))
await self.handle_typing(room_id, set(content.get("user_ids", [])))
async def handle_event(self, evt: MatrixEvent) -> None:
if self.filter_matrix_event(evt):
return
start_time = time.time()
self.log.debug("Received event: %s", evt)
evt_type = evt.get("type", "m.unknown") # type: str
room_id = evt.get("room_id", None) # type: Optional[MatrixRoomID]
event_id = evt.get("event_id", None) # type: Optional[MatrixEventID]
sender = evt.get("sender", None) # type: Optional[MatrixUserID]
evt_type: str = evt.get("type", "m.unknown")
room_id: Optional[MatrixRoomID] = evt.get("room_id", None)
event_id: Optional[MatrixEventID] = evt.get("event_id", None)
sender: Optional[MatrixUserID] = evt.get("sender", None)
state_key = evt.get("state_key", None)
content = evt.get("content", {}) # type: Dict
content: Dict = evt.get("content", {})
if state_key is not None:
if evt_type == "m.room.member":
prev_content = evt.get("unsigned", {}).get("prev_content", {}) # type: Dict
membership = content.get("membership", "") # type: str
prev_membership = prev_content.get("membership", "leave") # type: str
prev_content: Dict = evt.get("unsigned", {}).get("prev_content", {})
membership: str = content.get("membership", "")
prev_membership: str = prev_content.get("membership", "leave")
if membership == prev_membership:
match = re.compile("@(.+):(.+)").match(state_key) # type: Match
mxid = match.group(0) # type: str
displayname = content.get("displayname", None) or mxid # type: str
prev_displayname = prev_content.get("displayname", None) or mxid # type: str
match: Match = re.compile("@(.+):(.+)").match(state_key)
mxid: str = match.group(0)
displayname: str = content.get("displayname", None) or mxid
prev_displayname: str = prev_content.get("displayname", None) or mxid
if displayname != prev_displayname:
await self.handle_name_change(room_id, state_key, displayname,
prev_displayname, event_id)
+5 -2
View File
@@ -23,10 +23,13 @@ from .db import RoomState, UserProfile
class SQLStateStore(StateStore):
profile_cache: Dict[Tuple[str, str], UserProfile]
room_state_cache: Dict[str, RoomState]
def __init__(self) -> None:
super().__init__()
self.profile_cache = {} # type: Dict[Tuple[str, str], UserProfile]
self.room_state_cache = {} # type: Dict[str, RoomState]
self.profile_cache = {}
self.room_state_cache = {}
@staticmethod
def is_registered(user: MatrixUserID) -> bool:
+3
View File
@@ -21,9 +21,12 @@ from telethon.tl.types import (
InputMediaUploadedDocument, InputMediaUploadedPhoto, TypeDocumentAttribute, TypeInputMedia,
TypeInputPeer, TypeMessageEntity, TypeMessageMedia, TypePeer)
from telethon.tl.patched import Message
from telethon.sessions.abstract import Session
class MautrixTelegramClient(TelegramClient):
session: Session
async def upload_file_direct(self, file: bytes, mime_type: str = None,
attributes: List[TypeDocumentAttribute] = None,
file_name: str = None, max_image_size: float = 10 * 1000 ** 2,
+26 -17
View File
@@ -13,7 +13,8 @@
#
# 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 Awaitable, Dict, List, Iterable, Match, NewType, Optional, Tuple, TYPE_CHECKING
from typing import (Awaitable, Dict, List, Iterable, Match, NewType, Optional, Tuple, Any,
TYPE_CHECKING)
import logging
import asyncio
import re
@@ -35,15 +36,23 @@ if TYPE_CHECKING:
from .config import Config
from .context import Context
config = None # type: Config
config: Optional['Config'] = None
SearchResult = NewType('SearchResult', Tuple['pu.Puppet', int])
class User(AbstractUser):
log = logging.getLogger("mau.user") # type: logging.Logger
by_mxid = {} # type: Dict[str, User]
by_tgid = {} # type: Dict[int, User]
log: logging.Logger = logging.getLogger("mau.user")
by_mxid: Dict[str, 'User'] = {}
by_tgid: Dict[int, 'User'] = {}
phone: Optional[str]
contacts: List['pu.Puppet']
saved_contacts: int
portals: Dict[Tuple[TelegramID, TelegramID], 'po.Portal']
command_status: Optional[Dict[str, Any]]
_db_instance: Optional[DBUser]
def __init__(self, mxid: MatrixUserID, tgid: Optional[TelegramID] = None,
username: Optional[str] = None, phone: Optional[str] = None,
@@ -52,19 +61,19 @@ class User(AbstractUser):
db_portals: Optional[Iterable[Tuple[TelegramID, TelegramID]]] = None,
db_instance: Optional[DBUser] = None) -> None:
super().__init__()
self.mxid = mxid # type: MatrixUserID
self.tgid = tgid # type: TelegramID
self.is_bot = is_bot # type: bool
self.username = username # type: str
self.phone = phone # type: str
self.contacts = [] # type: List[pu.Puppet]
self.saved_contacts = saved_contacts # type: int
self.mxid = mxid
self.tgid = tgid
self.is_bot = is_bot
self.username = username
self.phone = phone
self.contacts = []
self.saved_contacts = saved_contacts
self.db_contacts = db_contacts
self.portals = {} # type: Dict[Tuple[TelegramID, TelegramID], po.Portal]
self.portals = {}
self.db_portals = db_portals or []
self._db_instance = db_instance # type: Optional[DBUser]
self._db_instance = db_instance
self.command_status = None # type: Optional[Dict]
self.command_status = None
(self.relaybot_whitelisted,
self.whitelisted,
@@ -83,7 +92,7 @@ class User(AbstractUser):
@property
def mxid_localpart(self) -> str:
match = re.compile("@(.+):(.+)").match(self.mxid) # type: Match
match: Match = re.compile("@(.+):(.+)").match(self.mxid)
return match.group(1)
@property
@@ -228,7 +237,7 @@ class User(AbstractUser):
self.phone = info.phone
changed = True
if self.tgid != info.id:
self.tgid = info.id
self.tgid = TelegramID(info.id)
self.by_tgid[self.tgid] = self
if changed:
self.save()