Skip to content

Commit 149c100

Browse files
committed
Initial commit. Transfer from mautrix-telegram
0 parents  commit 149c100

File tree

12 files changed

+1713
-0
lines changed

12 files changed

+1713
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
indent_style = tab
5+
indent_size = 4
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.{yaml,yml,py}]
12+
indent_style = space

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.idea/
2+
3+
build/
4+
dist/
5+
mautrix_appservice.egg-info
6+
7+
.venv
8+
pip-selfcheck.json
9+
*.pyc
10+
__pycache__

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# mautrix-appservice
2+
A Python 3 asyncio-based Matrix application service framework.

mautrix_appservice/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .appservice import AppService
2+
from .errors import MatrixError, MatrixRequestError, IntentError
3+
4+
__version__ = "0.1.0"
5+
__author__ = "Tulir Asokan <[email protected]>"

mautrix_appservice/appservice.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# -*- coding: future_fstrings -*-
2+
# matrix-appservice-python - A Matrix Application Service framework written in Python.
3+
# Copyright (C) 2018 Tulir Asokan
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
#
18+
# Partly based on github.com/Cadair/python-appservice-framework (MIT license)
19+
from contextlib import contextmanager
20+
from aiohttp import web
21+
import aiohttp
22+
import asyncio
23+
import logging
24+
25+
from .intent_api import HTTPAPI
26+
from .state_store import StateStore
27+
28+
29+
class AppService:
30+
def __init__(self, server, domain, as_token, hs_token, bot_localpart, loop=None, log=None,
31+
verify_ssl=True, query_user=None, query_alias=None):
32+
self.server = server
33+
self.domain = domain
34+
self.verify_ssl = verify_ssl
35+
self.as_token = as_token
36+
self.hs_token = hs_token
37+
self.bot_mxid = f"@{bot_localpart}:{domain}"
38+
self.state_store = StateStore(autosave_file="mx-state.json")
39+
self.state_store.load("mx-state.json")
40+
41+
self.transactions = []
42+
43+
self._http_session = None
44+
self._intent = None
45+
46+
self.loop = loop or asyncio.get_event_loop()
47+
self.log = (logging.getLogger(log) if isinstance(log, str)
48+
else log or logging.getLogger("mautrix_appservice"))
49+
50+
async def default_query_handler(_):
51+
return None
52+
53+
self.query_user = query_user or default_query_handler
54+
self.query_alias = query_alias or default_query_handler
55+
56+
self.event_handlers = []
57+
58+
self.app = web.Application(loop=self.loop)
59+
self.app.router.add_route("PUT", "/transactions/{transaction_id}",
60+
self._http_handle_transaction)
61+
self.app.router.add_route("GET", "/rooms/{alias}", self._http_query_alias)
62+
self.app.router.add_route("GET", "/users/{user_id}", self._http_query_user)
63+
64+
self.matrix_event_handler(self.update_state_store)
65+
66+
@property
67+
def http_session(self):
68+
if self._http_session is None:
69+
raise AttributeError("the http_session attribute can only be used "
70+
"from within the `AppService.run` context manager")
71+
else:
72+
return self._http_session
73+
74+
@property
75+
def intent(self):
76+
if self._intent is None:
77+
raise AttributeError("the intent attribute can only be used from "
78+
"within the `AppService.run` context manager")
79+
else:
80+
return self._intent
81+
82+
@contextmanager
83+
def run(self, host="127.0.0.1", port=8080):
84+
connector = None
85+
if self.server.startswith("https://") and not self.verify_ssl:
86+
connector = aiohttp.TCPConnector(verify_ssl=False)
87+
self._http_session = aiohttp.ClientSession(loop=self.loop, connector=connector)
88+
self._intent = HTTPAPI(base_url=self.server, domain=self.domain, bot_mxid=self.bot_mxid,
89+
token=self.as_token, log=self.log, state_store=self.state_store,
90+
client_session=self._http_session).bot_intent()
91+
92+
yield self.loop.create_server(self.app.make_handler(), host, port)
93+
94+
self._intent = None
95+
self._http_session.close()
96+
self._http_session = None
97+
98+
def _check_token(self, request):
99+
try:
100+
token = request.rel_url.query["access_token"]
101+
except KeyError:
102+
return False
103+
104+
if token != self.hs_token:
105+
return False
106+
107+
return True
108+
109+
async def _http_query_user(self, request):
110+
if not self._check_token(request):
111+
return web.Response(status=401)
112+
113+
user_id = request.match_info["userId"]
114+
115+
try:
116+
response = await self.query_user(user_id)
117+
except Exception:
118+
self.log.exception("Exception in user query handler")
119+
return web.Response(status=500)
120+
121+
if not response:
122+
return web.Response(status=404)
123+
return web.json_response(response)
124+
125+
async def _http_query_alias(self, request):
126+
if not self._check_token(request):
127+
return web.Response(status=401)
128+
129+
alias = request.match_info["alias"]
130+
131+
try:
132+
response = await self.query_alias(alias)
133+
except Exception:
134+
self.log.exception("Exception in alias query handler")
135+
return web.Response(status=500)
136+
137+
if not response:
138+
return web.Response(status=404)
139+
return web.json_response(response)
140+
141+
async def _http_handle_transaction(self, request):
142+
if not self._check_token(request):
143+
return web.Response(status=401)
144+
145+
transaction_id = request.match_info["transaction_id"]
146+
if transaction_id in self.transactions:
147+
return web.Response(status=200)
148+
149+
json = await request.json()
150+
151+
try:
152+
events = json["events"]
153+
except KeyError:
154+
return web.Response(status=400)
155+
156+
for event in events:
157+
self.handle_matrix_event(event)
158+
159+
self.transactions.append(transaction_id)
160+
161+
return web.json_response({})
162+
163+
async def update_state_store(self, event):
164+
event_type = event["type"]
165+
if event_type == "m.room.power_levels":
166+
self.state_store.set_power_levels(event["room_id"], event["content"])
167+
elif event_type == "m.room.member":
168+
self.state_store.set_membership(event["room_id"], event["state_key"],
169+
event["content"]["membership"])
170+
171+
def handle_matrix_event(self, event):
172+
async def try_handle(handler):
173+
try:
174+
await handler(event)
175+
except Exception:
176+
self.log.exception("Exception in Matrix event handler")
177+
178+
for handler in self.event_handlers:
179+
asyncio.ensure_future(try_handle(handler), loop=self.loop)
180+
181+
def matrix_event_handler(self, func):
182+
self.event_handlers.append(func)
183+
return func

mautrix_appservice/errors.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: future_fstrings -*-
2+
# mautrix-telegram - A Matrix-Telegram puppeting bridge
3+
# Copyright (C) 2018 Tulir Asokan
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
19+
class MatrixError(Exception):
20+
"""A generic Matrix error. Specific errors will subclass this."""
21+
pass
22+
23+
24+
class IntentError(MatrixError):
25+
def __init__(self, message, source):
26+
super().__init__(message)
27+
self.source = source
28+
29+
30+
class MatrixRequestError(MatrixError):
31+
""" The home server returned an error response. """
32+
33+
def __init__(self, code=0, text="", errcode=None, message=None):
34+
super().__init__(f"{code}: {text}")
35+
self.code = code
36+
self.text = text
37+
self.errcode = errcode
38+
self.message = message

0 commit comments

Comments
 (0)