Skip to content

Commit 3adc002

Browse files
committed
Add support for real users
1 parent 4408bf9 commit 3adc002

File tree

2 files changed

+88
-13
lines changed

2 files changed

+88
-13
lines changed

mautrix_appservice/appservice.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ def __init__(self, server: str, domain: str, as_token: str, hs_token: str, bot_l
1919
loop: Optional[asyncio.AbstractEventLoop] = None,
2020
log: Optional[Union[logging.Logger, str]] = None, verify_ssl: bool = True,
2121
query_user: QueryFunc = None, query_alias: QueryFunc = None,
22+
real_user_content_key: Optional[str] = "net.maunium.appservice.puppet",
2223
state_store: StateStore = None):
2324
self.server = server
2425
self.domain = domain
2526
self.verify_ssl = verify_ssl
2627
self.as_token = as_token
2728
self.hs_token = hs_token
2829
self.bot_mxid = f"@{bot_localpart}:{domain}"
30+
self.real_user_content_key = real_user_content_key
2931
if isinstance(state_store, StateStore):
3032
self.state_store = state_store
3133
else:
@@ -85,6 +87,7 @@ def run(self, host: str = "127.0.0.1", port: int = 8080):
8587
self._http_session = aiohttp.ClientSession(loop=self.loop, connector=connector)
8688
self._intent = HTTPAPI(base_url=self.server, domain=self.domain, bot_mxid=self.bot_mxid,
8789
token=self.as_token, log=self.log, state_store=self.state_store,
90+
real_user_content_key=self.real_user_content_key,
8891
client_session=self._http_session).bot_intent()
8992

9093
yield self.loop.create_server(self.app.make_handler(), host, port)

mautrix_appservice/intent_api.py

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ def quote(*args, **kwargs):
2828
class HTTPAPI:
2929
def __init__(self, base_url: str, domain: str = None, bot_mxid: str = None, token: str = None,
3030
identity: str = None, log: Logger = None, state_store: StateStore = None,
31-
client_session: ClientSession = None, child: bool = False):
31+
client_session: ClientSession = None, child: bool = False, real_user: bool = False,
32+
real_user_content_key: Optional[str] = None):
3233
self.base_url = base_url
3334
self.token = token
3435
self.identity = identity
@@ -39,14 +40,17 @@ def __init__(self, base_url: str, domain: str = None, bot_mxid: str = None, toke
3940
self.bot_mxid = bot_mxid
4041
self._bot_intent = None
4142
self.state_store = state_store
43+
self.is_real_user = real_user
44+
self.real_user_content_key = real_user_content_key
4245

43-
if child:
46+
if child or real_user:
4447
self.log = log
4548
else:
4649
self.intent_log = log.getChild("intent")
4750
self.log = log.getChild("api")
4851
self.txn_id = 0
4952
self.children = {}
53+
self.real_users = {}
5054

5155
def user(self, user: str) -> "ChildHTTPAPI":
5256
"""
@@ -58,25 +62,42 @@ def user(self, user: str) -> "ChildHTTPAPI":
5862
Returns:
5963
A HTTPAPI instance that always uses the given Matrix ID.
6064
"""
65+
if self.is_real_user:
66+
raise ValueError("Can't get child of real user")
67+
6168
try:
6269
return self.children[user]
6370
except KeyError:
6471
child = ChildHTTPAPI(user, self)
6572
self.children[user] = child
6673
return child
6774

75+
def real_user(self, mxid: str, token: str) -> "HTTPAPI":
76+
if self.is_real_user:
77+
raise ValueError("Can't get child of real user")
78+
79+
try:
80+
return self.real_users[mxid]
81+
except KeyError:
82+
child = self.__class__(self.base_url, self.domain, None, token, mxid, self.log,
83+
self.state_store, self.session, real_user=True,
84+
real_user_content_key=self.real_user_content_key)
85+
self.real_users[mxid] = child
86+
return child
87+
6888
def bot_intent(self) -> "IntentAPI":
6989
"""
7090
Get the intent API for the appservice bot.
7191
7292
Returns:
7393
The IntentAPI for the appservice bot.
7494
"""
75-
if self._bot_intent:
76-
return self._bot_intent
77-
return IntentAPI(self.bot_mxid, self, state_store=self.state_store, log=self.intent_log)
95+
if not self._bot_intent:
96+
self._bot_intent = IntentAPI(self.bot_mxid, self, state_store=self.state_store,
97+
log=self.intent_log)
98+
return self._bot_intent
7899

79-
def intent(self, user: str) -> "IntentAPI":
100+
def intent(self, user: str = None, token: Optional[str] = None) -> "IntentAPI":
80101
"""
81102
Get the intent API for a specific user.
82103
@@ -86,6 +107,11 @@ def intent(self, user: str) -> "IntentAPI":
86107
Returns:
87108
The IntentAPI for the given user.
88109
"""
110+
if self.is_real_user:
111+
raise ValueError("Can't get child intent of real user")
112+
if token:
113+
return IntentAPI(user, self.real_user(user, token), self.bot_intent(), self.state_store,
114+
self.intent_log)
89115
return IntentAPI(user, self.user(user), self.bot_intent(), self.state_store,
90116
self.intent_log)
91117

@@ -154,19 +180,50 @@ def request(self, method: str, path: str, content: Optional[Union[dict, bytes, s
154180
if method not in ["GET", "PUT", "DELETE", "POST"]:
155181
raise MatrixError("Unsupported HTTP method: %s" % method)
156182

157-
if "Content-Type" not in headers:
183+
if content and "Content-Type" not in headers:
158184
headers["Content-Type"] = "application/json"
159-
if headers["Content-Type"] == "application/json":
185+
if headers.get("Content-Type", None) == "application/json":
160186
content = json.dumps(content)
161187

162-
if self.identity:
188+
if self.identity and not self.is_real_user:
163189
query_params["user_id"] = self.identity
164190

165191
self._log_request(method, path, content, query_params)
166192

167193
endpoint = self.base_url + api_path + path
168194
return self._send(method, endpoint, content, query_params, headers or {})
169195

196+
def sync(self, since=None, timeout_ms=30000, filter=None, full_state=None, set_presence=None):
197+
""" Perform a sync request.
198+
Args:
199+
since (str): Optional. A token which specifies where to continue a sync from.
200+
timeout_ms (int): Optional. The time in m1illiseconds to wait.
201+
filter (int|str): Either a Filter ID or a JSON string.
202+
full_state (bool): Return the full state for every room the user has joined
203+
Defaults to false.
204+
set_presence (str): Should the client be marked as "online" or" offline"
205+
"""
206+
request = {
207+
"timeout": int(timeout_ms)
208+
}
209+
210+
if since:
211+
request["since"] = since
212+
if filter:
213+
request["filter"] = filter
214+
if full_state:
215+
request["full_state"] = json.dumps(full_state)
216+
if set_presence:
217+
request["set_presence"] = set_presence
218+
return self.request("GET", "/sync", query_params=request)
219+
220+
def get_filter(self, user_id: str, filter_id: str) -> Awaitable[dict]:
221+
return self.request("GET", f"/user/{user_id}/filter/{filter_id}")
222+
223+
async def create_filter(self, user_id: str, filter_params: dict) -> str:
224+
resp = await self.request("POST", f"/user/{user_id}/filter", filter_params)
225+
return resp.get("filter_id", None)
226+
170227
def get_download_url(self, mxc_uri: str) -> str:
171228
"""
172229
Get the full URL to download a mxc:// URI.
@@ -191,7 +248,8 @@ class ChildHTTPAPI(HTTPAPI):
191248

192249
def __init__(self, user: str, parent: HTTPAPI):
193250
super().__init__(parent.base_url, parent.domain, parent.bot_mxid, parent.token, user,
194-
parent.log, parent.state_store, parent.session, child=True)
251+
parent.log, parent.state_store, parent.session, child=True,
252+
real_user_content_key=parent.real_user_content_key)
195253
self.parent = parent
196254

197255
@property
@@ -225,7 +283,7 @@ def __init__(self, mxid: str, client: HTTPAPI, bot: "IntentAPI" = None,
225283

226284
self.state_store = state_store
227285

228-
def user(self, user: str) -> "IntentAPI":
286+
def user(self, user: str, token: Optional[str] = None) -> "IntentAPI":
229287
"""
230288
Get the intent API for a specific user. This is just a proxy to :func:`~HTTPAPI.intent`.
231289
@@ -234,15 +292,16 @@ def user(self, user: str) -> "IntentAPI":
234292
235293
Args:
236294
user: The Matrix ID of the user whose intent API to get.
295+
token: The access token to use for the Matrix ID.
237296
238297
Returns:
239298
The IntentAPI for the given user.
240299
"""
241300
if not self.bot:
242-
return self.client.intent(user)
301+
return self.client.intent(user, token)
243302
else:
244303
self.log.warning("Called IntentAPI#user() of child intent object.")
245-
return self.bot.client.intent(user)
304+
return self.bot.client.intent(user, token)
246305

247306
# region User actions
248307

@@ -541,6 +600,16 @@ async def set_join_rule(self, room_id: str, join_rule: str, **kwargs):
541600
"join_rule": join_rule,
542601
}, **kwargs)
543602

603+
async def get_profile(self, user_id: str) -> dict:
604+
return await self.client.request("GET", f"/profile/{quote(user_id)}")
605+
606+
async def whoami(self) -> Optional[str]:
607+
try:
608+
resp = await self.client.request("GET", f"/account/whoami")
609+
return resp.get("user_id", None)
610+
except MatrixError:
611+
return None
612+
544613
async def get_displayname(self, room_id: str, user_id: str, ignore_cache=False) -> str:
545614
return (await self.get_member_info(room_id, user_id, ignore_cache)).get("displayname", None)
546615

@@ -688,6 +757,9 @@ async def send_event(self, room_id: str, event_type: str, content: dict,
688757
await self.ensure_joined(room_id)
689758
await self._ensure_has_power_level_for(room_id, event_type)
690759

760+
if self.client.is_real_user and self.client.real_user_content_key:
761+
content[self.client.real_user_content_key] = True
762+
691763
txn_id = txn_id or str(self.client.txn_id) + str(int(time() * 1000))
692764
self.client.txn_id += 1
693765

0 commit comments

Comments
 (0)