Merge branch 'master' into independent-login

This commit is contained in:
Tulir Asokan
2018-02-20 21:48:05 +02:00
33 changed files with 1576 additions and 813 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ class AppService:
self.log = (logging.getLogger(log) if isinstance(log, str)
else log or logging.getLogger("mautrix_appservice"))
def default_query_handler(_):
async def default_query_handler(_):
return None
self.query_user = query_user or default_query_handler
+1 -1
View File
@@ -31,7 +31,7 @@ class MatrixRequestError(MatrixError):
""" The home server returned an error response. """
def __init__(self, code=0, text="", errcode=None, message=None):
super().__init__("%d: %s" % (code, text))
super().__init__(f"{code}: {text}")
self.code = code
self.text = text
self.errcode = errcode
+76 -10
View File
@@ -194,12 +194,16 @@ class IntentAPI:
content = {"displayname": name}
return await self.client.request("PUT", f"/profile/{self.mxid}/displayname", content)
async def set_presence(self, status="online"):
async def set_presence(self, status="online", ignore_cache=False):
await self.ensure_registered()
if not ignore_cache and self.state_store.has_presence(self.mxid, status):
return
content = {
"presence": status
}
return await self.client.request("PUT", f"/presence/{self.mxid}/status", content)
resp = await self.client.request("PUT", f"/presence/{self.mxid}/status", content)
self.state_store.set_presence(self.mxid, status)
return resp
async def set_avatar(self, url):
await self.ensure_registered()
@@ -223,11 +227,14 @@ class IntentAPI:
# region Room actions
async def create_room(self, alias=None, is_public=False, name=None, topic=None,
is_direct=False, invitees=None, initial_state=None):
is_direct=False, invitees=None, initial_state=None,
guests_can_join=False):
await self.ensure_registered()
content = {
"visibility": "public" if is_public else "private",
"visibility": "private",
"is_direct": is_direct,
"preset": "public_chat" if is_public else "private_chat",
"guests_can_join": guests_can_join,
}
if alias:
content["room_alias_name"] = alias
@@ -326,18 +333,29 @@ class IntentAPI:
events.remove(event_id)
await self.set_pinned_messages(room_id, events)
async def set_join_rule(self, room_id, join_rule):
if join_rule not in ("public", "knock", "invite", "private"):
raise ValueError(f"Invalid join rule \"{join_rule}\"")
await self.send_state_event(room_id, "m.room.join_rules", {
"join_rule": join_rule,
})
async def get_event(self, room_id, event_id):
await self.ensure_joined(room_id)
return await self.client.request("GET", f"/rooms/{room_id}/event/{event_id}")
async def set_typing(self, room_id, is_typing=True, timeout=5000):
async def set_typing(self, room_id, is_typing=True, timeout=5000, ignore_cache=False):
await self.ensure_joined(room_id)
if not ignore_cache and is_typing == self.state_store.is_typing(room_id, self.mxid):
return
content = {
"typing": is_typing
}
if is_typing:
content["timeout"] = timeout
return await self.client.request("PUT", f"/rooms/{room_id}/typing/{self.mxid}", content)
resp = await self.client.request("PUT", f"/rooms/{room_id}/typing/{self.mxid}", content)
self.state_store.set_typing(room_id, self.mxid, is_typing, timeout)
return resp
async def mark_read(self, room_id, event_id):
await self.ensure_joined(room_id)
@@ -407,11 +425,31 @@ class IntentAPI:
return self.send_state_event(room_id, "m.room.member", body, state_key=user_id)
def redact(self, room_id, event_id, reason=None, txn_id=None):
txn_id = txn_id or str(self.client.txn_id) + str(int(time() * 1000))
self.client.txn_id += 1
content = {}
if reason:
content["reason"] = reason
return self.client.request("PUT",
f"/rooms/{quote(room_id)}/redact/{quote(event_id)}/{txn_id}",
content)
@staticmethod
def _get_event_url(room_id, event_type, txn_id):
if not room_id:
raise ValueError("Room ID not given")
elif not event_type:
raise ValueError("Event type not given")
elif not txn_id:
raise ValueError("Transaction ID not given")
return f"/rooms/{quote(room_id)}/send/{quote(event_type)}/{quote(txn_id)}"
async def send_event(self, room_id, event_type, content, txn_id=None):
if not room_id:
raise ValueError("Room ID not given")
elif not event_type:
raise ValueError("Event type not given")
await self.ensure_joined(room_id)
await self._ensure_has_power_level_for(room_id, event_type)
@@ -424,29 +462,47 @@ class IntentAPI:
@staticmethod
def _get_state_url(room_id, event_type, state_key=""):
if not room_id:
raise ValueError("Room ID not given")
elif not event_type:
raise ValueError("Event type not given")
url = f"/rooms/{quote(room_id)}/state/{quote(event_type)}"
if state_key:
url += f"/{quote(state_key)}"
return url
async def send_state_event(self, room_id, event_type, content, state_key=""):
if not room_id:
raise ValueError("Room ID not given")
elif not event_type:
raise ValueError("Event type not given")
await self.ensure_joined(room_id)
await self._ensure_has_power_level_for(room_id, event_type)
await self._ensure_has_power_level_for(room_id, event_type, is_state_event=True)
url = self._get_state_url(room_id, event_type, state_key)
return await self.client.request("PUT", url, content)
async def get_state_event(self, room_id, event_type, state_key=""):
if not room_id:
raise ValueError("Room ID not given")
elif not event_type:
raise ValueError("Event type not given")
await self.ensure_joined(room_id)
url = self._get_state_url(room_id, event_type, state_key)
return await self.client.request("GET", url)
def join_room(self, room_id):
if not room_id:
raise ValueError("Room ID not given")
return self.ensure_joined(room_id, ignore_cache=True)
def _join_room_direct(self, room):
if not room:
raise ValueError("Room ID not given")
return self.client.request("POST", f"/join/{quote(room)}")
def leave_room(self, room_id):
if not room_id:
raise ValueError("Room ID not given")
try:
self.state_store.left(room_id, self.mxid)
return self.client.request("POST", f"/rooms/{quote(room_id)}/leave")
@@ -455,6 +511,8 @@ class IntentAPI:
raise
def get_room_memberships(self, room_id):
if not room_id:
raise ValueError("Room ID not given")
return self.client.request("GET", f"/rooms/{quote(room_id)}/members")
async def get_room_members(self, room_id, allowed_memberships=("join",)):
@@ -472,6 +530,8 @@ class IntentAPI:
# region Ensure functions
async def ensure_joined(self, room_id, ignore_cache=False):
if not room_id:
raise ValueError("Room ID not given")
if not ignore_cache and self.state_store.is_joined(room_id, self.mxid):
return
await self.ensure_registered()
@@ -505,16 +565,22 @@ class IntentAPI:
return
self.state_store.registered(self.mxid)
async def _ensure_has_power_level_for(self, room_id, event_type):
async def _ensure_has_power_level_for(self, room_id, event_type, is_state_event=False):
if not room_id:
raise ValueError("Room ID not given")
elif not event_type:
raise ValueError("Event type not given")
if not self.state_store.has_power_levels(room_id):
await self.get_power_levels(room_id)
if self.state_store.has_power_level(room_id, self.mxid, event_type):
if self.state_store.has_power_level(room_id, self.mxid, event_type,
is_state_event=is_state_event):
return
elif not self.bot:
self.log.warning(
f"Power level of {self.mxid} is not enough for {event_type} in {room_id}")
# raise IntentError(f"Power level of {self.mxid} is not enough"
# + f"for {event_type} in {room_id}")
# f"for {event_type} in {room_id}")
return
# TODO implement
+36 -4
View File
@@ -15,14 +15,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import time
class StateStore:
def __init__(self, autosave_file=None):
self.autosave_file = autosave_file
# Persistent storage
self.registrations = set()
self.memberships = {}
self.power_levels = {}
self.autosave_file = autosave_file
# Non-persistent storage
self.presence = {}
self.typing = {}
def save(self, file):
if isinstance(file, str):
@@ -63,6 +70,29 @@ class StateStore:
if self.autosave_file:
self.save(self.autosave_file)
def set_presence(self, user, presence):
self.presence[user] = presence
def has_presence(self, user, presence):
try:
return self.presence[user] == presence
except KeyError:
return False
def set_typing(self, room_id, user, is_typing, timeout=0):
if is_typing:
ts = int(round(time.time() * 1000))
self.typing[(room_id, user)] = ts + timeout
else:
del self.typing[(room_id, user)]
def is_typing(self, room_id, user):
ts = int(round(time.time() * 1000))
try:
return self.typing[(room_id, user)] > ts
except KeyError:
return False
def is_registered(self, user):
return user in self.registrations
@@ -97,10 +127,12 @@ class StateStore:
def get_power_levels(self, room):
return self.power_levels[room]
def has_power_level(self, room, user, event):
def has_power_level(self, room, user, event, is_state_event=False):
room_levels = self.power_levels.get(room, {})
required = room_levels.get("events", {}).get(event, 95)
has = room_levels.get("users", {}).get(user, 0)
default_required = (room_levels.get("state_default", 50) if is_state_event
else room_levels.get("events_default", 0))
required = room_levels.get("events", {}).get(event, default_required)
has = room_levels.get("users", {}).get(user, room_levels.get("users_default", 0))
return has >= required
def set_power_level(self, room, user, level):