Merge branch 'master' into independent-login
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user