diff --git a/maubot/__meta__.py b/maubot/__meta__.py
index b15e5b6..0f62cb1 100644
--- a/maubot/__meta__.py
+++ b/maubot/__meta__.py
@@ -1 +1 @@
-__version__ = "0.1.0.dev22"
+__version__ = "0.1.0.dev23"
diff --git a/maubot/client.py b/maubot/client.py
index cf0ec2e..2a20db2 100644
--- a/maubot/client.py
+++ b/maubot/client.py
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from typing import Dict, List, Optional, Set, TYPE_CHECKING
+from typing import Dict, List, Optional, Set, Callable, Any, Awaitable, TYPE_CHECKING
import asyncio
import logging
@@ -23,6 +23,7 @@ from aiohttp import ClientSession
from mautrix.errors import MatrixInvalidToken, MatrixRequestError
from mautrix.types import (UserID, SyncToken, FilterID, ContentURI, StrippedStateEvent, Membership,
EventType, Filter, RoomFilter, RoomEventFilter)
+from mautrix.client import InternalEventType
from .db import DBClient
from .matrix import MaubotMatrixClient
@@ -51,11 +52,22 @@ class Client:
self.log = log.getChild(self.id)
self.references = set()
self.started = False
+ self.sync_ok = True
self.client = MaubotMatrixClient(mxid=self.id, base_url=self.homeserver,
token=self.access_token, client_session=self.http_client,
log=self.log, loop=self.loop, store=self.db_instance)
+ self.client.ignore_initial_sync = True
+ self.client.ignore_first_sync = True
if self.autojoin:
self.client.add_event_handler(EventType.ROOM_MEMBER, self._handle_invite)
+ self.client.add_event_handler(InternalEventType.SYNC_ERRORED, self._set_sync_ok(False))
+ self.client.add_event_handler(InternalEventType.SYNC_SUCCESSFUL, self._set_sync_ok(True))
+
+ def _set_sync_ok(self, ok: bool) -> Callable[[Dict[str, Any]], Awaitable[None]]:
+ async def handler(data: Dict[str, Any]) -> None:
+ self.sync_ok = ok
+
+ return handler
async def start(self, try_n: Optional[int] = 0) -> None:
try:
@@ -128,6 +140,13 @@ class Client:
await self.stop_plugins()
self.stop_sync()
+ def clear_cache(self) -> None:
+ self.stop_sync()
+ self.db_instance.filter_id = ""
+ self.db_instance.next_batch = ""
+ self.db.commit()
+ self.start_sync()
+
def delete(self) -> None:
try:
del self.cache[self.id]
@@ -144,6 +163,7 @@ class Client:
"enabled": self.enabled,
"started": self.started,
"sync": self.sync,
+ "sync_ok": self.sync_ok,
"autojoin": self.autojoin,
"displayname": self.displayname,
"avatar_url": self.avatar_url,
diff --git a/maubot/management/api/client.py b/maubot/management/api/client.py
index eb1748a..a5e38d1 100644
--- a/maubot/management/api/client.py
+++ b/maubot/management/api/client.py
@@ -130,3 +130,13 @@ async def delete_client(request: web.Request) -> web.Response:
await client.stop()
client.delete()
return resp.deleted
+
+
+@routes.post("/client/{id}/clearcache")
+async def clear_client_cache(request: web.Request) -> web.Response:
+ user_id = request.match_info.get("id", None)
+ client = Client.get(user_id, None)
+ if not client:
+ return resp.client_not_found
+ client.clear_cache()
+ return resp.ok
diff --git a/maubot/management/api/client_proxy.py b/maubot/management/api/client_proxy.py
index f95de23..8c293cd 100644
--- a/maubot/management/api/client_proxy.py
+++ b/maubot/management/api/client_proxy.py
@@ -36,6 +36,7 @@ async def proxy(request: web.Request) -> web.StreamResponse:
except KeyError:
pass
headers = request.headers.copy()
+ del headers["Host"]
headers["Authorization"] = f"Bearer {client.access_token}"
if "X-Forwarded-For" not in headers:
peer = request.transport.get_extra_info("peername")
diff --git a/maubot/management/api/spec.yaml b/maubot/management/api/spec.yaml
index c25967e..c6f5181 100644
--- a/maubot/management/api/spec.yaml
+++ b/maubot/management/api/spec.yaml
@@ -399,6 +399,32 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ '/client/{id}/clearcache':
+ parameters:
+ - name: id
+ in: path
+ description: The Matrix user ID of the client to change
+ required: true
+ schema:
+ type: string
+ put:
+ operationId: clear_client_cache
+ summary: Clear the sync/state cache of a Matrix client
+ tags: [Clients]
+ responses:
+ 200:
+ description: Cache cleared
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ 401:
+ $ref: '#/components/responses/Unauthorized'
+ 404:
+ $ref: '#/components/responses/ClientNotFound'
/client/auth/servers:
get:
operationId: get_client_auth_servers
@@ -607,29 +633,42 @@ components:
type: string
example: '@putkiteippi:maunium.net'
readOnly: true
+ description: The Matrix user ID of this client.
homeserver:
type: string
example: 'https://maunium.net'
+ description: The homeserver URL for this client.
access_token:
type: string
+ description: The Matrix access token for this client.
enabled:
type: boolean
example: true
+ description: Whether or not this client is enabled.
started:
type: boolean
example: true
+ description: Whether or not this client and its instances have been started.
sync:
type: boolean
example: true
+ description: Whether or not syncing is enabled on this client.
+ sync_ok:
+ type: boolean
+ example: true
+ description: Whether or not the previous sync was successful on this client.
autojoin:
type: boolean
example: true
+ description: Whether or not this client should automatically join rooms when invited.
displayname:
type: string
example: J. E. Saarinen
+ description: The display name for this client.
avatar_url:
type: string
example: 'mxc://maunium.net/FsPQQTntCCqhJMFtwArmJdaU'
+ description: The content URI of the avatar for this client.
instances:
type: array
readOnly: true
diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js
index 566bf95..dc4bbc0 100644
--- a/maubot/management/frontend/src/api.js
+++ b/maubot/management/frontend/src/api.js
@@ -37,7 +37,7 @@ async function defaultDelete(type, id) {
}
async function defaultPut(type, entry, id = undefined, suffix = undefined) {
- const resp = await fetch(`${BASE_PATH}/${type}/${id || entry.id}${suffix || ''}`, {
+ const resp = await fetch(`${BASE_PATH}/${type}/${id || entry.id}${suffix || ""}`, {
headers: getHeaders(),
body: JSON.stringify(entry),
method: "PUT",
@@ -221,6 +221,14 @@ export function getAvatarURL({ id, avatar_url }) {
export const putClient = client => defaultPut("client", client)
export const deleteClient = id => defaultDelete("client", id)
+export async function clearClientCache(id) {
+ const resp = await fetch(`${BASE_PATH}/client/${id}/clearcache`, {
+ headers: getHeaders(),
+ method: "POST",
+ })
+ return await resp.json()
+}
+
export const getClientAuthServers = () => defaultGet("/client/auth/servers")
export async function doClientAuth(server, type, username, password) {
@@ -240,6 +248,6 @@ export default {
getInstances, getInstance, putInstance, deleteInstance,
getInstanceDatabase, queryInstanceDatabase,
getPlugins, getPlugin, uploadPlugin, deletePlugin,
- getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient,
- getClientAuthServers, doClientAuth
+ getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient, clearClientCache,
+ getClientAuthServers, doClientAuth,
}
diff --git a/maubot/management/frontend/src/pages/dashboard/Client.js b/maubot/management/frontend/src/pages/dashboard/Client.js
index f9617b0..b41cd57 100644
--- a/maubot/management/frontend/src/pages/dashboard/Client.js
+++ b/maubot/management/frontend/src/pages/dashboard/Client.js
@@ -44,6 +44,7 @@ class Client extends BaseMainView {
constructor(props) {
super(props)
this.deleteFunc = api.deleteClient
+ this.homeserverOptions = {}
}
get entryKeys() {
@@ -69,6 +70,7 @@ class Client extends BaseMainView {
saving: false,
deleting: false,
startingOrStopping: false,
+ clearingCache: false,
error: "",
}
}
@@ -79,6 +81,7 @@ class Client extends BaseMainView {
delete client.saving
delete client.deleting
delete client.startingOrStopping
+ delete client.clearingCache
delete client.error
delete client.instances
return client
@@ -88,7 +91,7 @@ class Client extends BaseMainView {
return this.state.homeserver
? this.homeserverEntry([this.props.ctx.homeserversByURL[this.state.homeserver],
this.state.homeserver])
- : {}
+ : this.homeserverOptions[0] || {}
}
homeserverEntry = ([serverName, serverURL]) => serverURL && {
@@ -156,8 +159,39 @@ class Client extends BaseMainView {
}
}
+ clearCache = async () => {
+ this.setState({ clearingCache: true })
+ const resp = await api.clearClientCache(this.props.entry.id)
+ if (resp.success) {
+ this.setState({ clearingCache: false, error: "" })
+ } else {
+ this.setState({ clearingCache: false, error: resp.error })
+ }
+ }
+
get loading() {
- return this.state.saving || this.state.startingOrStopping || this.state.deleting
+ return this.state.saving || this.state.startingOrStopping || this.clearingCache || this.state.deleting
+ }
+
+ renderStartedContainer = () => {
+ let text
+ if (this.props.entry.started) {
+ if (this.props.entry.sync_ok) {
+ text = "Started"
+ } else {
+ text = "Erroring"
+ }
+ } else if (this.props.entry.enabled) {
+ text = "Stopped"
+ } else {
+ text = "Disabled"
+ }
+ return
+
+ {text}
+
}
renderSidebar = () => !this.isNew && (
@@ -172,20 +206,16 @@ class Client extends BaseMainView {
onDragLeave={evt => evt.target.parentElement.classList.remove("drag")}/>
{this.state.uploadingAvatar && }
-
-
-
- {this.props.entry.started ? "Started" :
- (this.props.entry.enabled ? "Stopped" : "Disabled")}
-
-
- {(this.props.entry.started || this.props.entry.enabled) && (
+ {this.renderStartedContainer()}
+ {(this.props.entry.started || this.props.entry.enabled) && <>
- )}
+
+ >}
)
@@ -195,10 +225,17 @@ class Client extends BaseMainView {
name={this.isNew ? "id" : ""} className="id"
value={this.state.id} origValue={this.props.entry.id}
placeholder="@fancybot:example.com" onChange={this.inputChange}/>
- this.setState({ homeserver: id })}
- creatable={true} isValidNewOption={this.isValidHomeserver}/>
+ {api.getFeatures().client_auth ? (
+ this.setState({ homeserver: value })}
+ creatable={true} isValidNewOption={this.isValidHomeserver}/>
+ ) : (
+
+ )}
div
+ > *
margin-bottom: 1rem
@import avatar
diff --git a/maubot/management/frontend/src/style/pages/client/started.sass b/maubot/management/frontend/src/style/pages/client/started.sass
index ec53968..eed355f 100644
--- a/maubot/management/frontend/src/style/pages/client/started.sass
+++ b/maubot/management/frontend/src/style/pages/client/started.sass
@@ -25,8 +25,13 @@
margin: .5rem
&.true
- background-color: $primary
- box-shadow: 0 0 .75rem .75rem $primary
+ &.sync_ok
+ background-color: $primary
+ box-shadow: 0 0 .75rem .75rem $primary
+
+ &.sync_error
+ background-color: $warning
+ box-shadow: 0 0 .75rem .75rem $warning
&.false
background-color: $error-light
diff --git a/maubot/management/frontend/src/style/pages/dashboard.sass b/maubot/management/frontend/src/style/pages/dashboard.sass
index 78bcdd0..0b30dff 100644
--- a/maubot/management/frontend/src/style/pages/dashboard.sass
+++ b/maubot/management/frontend/src/style/pages/dashboard.sass
@@ -116,7 +116,7 @@
&:hover
background-color: $error !important
- button.save, button.delete
+ button.save, button.clearcache, button.delete
+button
+main-color-button
width: 100%
diff --git a/maubot/matrix.py b/maubot/matrix.py
index 9dccffb..0cca977 100644
--- a/maubot/matrix.py
+++ b/maubot/matrix.py
@@ -18,7 +18,7 @@ from markdown.extensions import Extension
import markdown as md
import attr
-from mautrix.client import Client as MatrixClient
+from mautrix.client import Client as MatrixClient, SyncStream
from mautrix.util.formatter import parse_html
from mautrix.types import (EventType, MessageEvent, Event, EventID, RoomID, MessageEventContent,
MessageType, TextMessageEventContent, Format, RelatesTo)
@@ -83,12 +83,12 @@ class MaubotMatrixClient(MatrixClient):
content.relates_to = relates_to
return self.send_message(room_id, content, **kwargs)
- async def call_handlers(self, event: Event, source) -> None:
+ async def dispatch_event(self, event: Event, source: SyncStream = SyncStream.INTERNAL) -> None:
if isinstance(event, MessageEvent):
event = MaubotMessageEvent(event, self)
- else:
+ elif source != SyncStream.INTERNAL:
event.client = self
- return await super().call_handlers(event, source)
+ return await super().dispatch_event(event, source)
async def get_event(self, room_id: RoomID, event_id: EventID) -> Event:
event = await super().get_event(room_id, event_id)
diff --git a/setup.py b/setup.py
index e246e0a..2928105 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ setuptools.setup(
packages=setuptools.find_packages(),
install_requires=[
- "mautrix>=0.4.dev46,<0.5",
+ "mautrix>=0.4.dev47,<0.5",
"aiohttp>=3.0.1,<4",
"SQLAlchemy>=1.2.3,<2",
"alembic>=1.0.0,<2",