From 54c40c6b8586fbb9b2b639cd0c7aa1c72a6e53f1 Mon Sep 17 00:00:00 2001 From: retoor Date: Mon, 10 Feb 2025 16:18:08 +0100 Subject: [PATCH] Added online status --- src/snek/model/user.py | 2 ++ src/snek/service/channel.py | 21 +++++++++++++++++++++ src/snek/service/user.py | 1 + src/snek/static/app.js | 24 +++++++++++++++++++++++- src/snek/view/rpc.py | 19 ++++++++++++++++++- 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/snek/model/user.py b/src/snek/model/user.py index cac9aaf..54b6f30 100644 --- a/src/snek/model/user.py +++ b/src/snek/model/user.py @@ -29,3 +29,5 @@ class UserModel(BaseModel): regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", ) password = ModelField(name="password", required=True, min_length=1) + + last_ping = ModelField(name="last_ping", required=False, kind=str) diff --git a/src/snek/service/channel.py b/src/snek/service/channel.py index ee23c2d..357c5dd 100644 --- a/src/snek/service/channel.py +++ b/src/snek/service/channel.py @@ -1,5 +1,7 @@ from snek.system.service import BaseService +from datetime import datetime +from snek.system.model import now class ChannelService(BaseService): mapper_name = "channel" @@ -29,6 +31,25 @@ class ChannelService(BaseService): return model raise Exception(f"Failed to create channel: {model.errors}.") + async def get_users(self, channel_uid): + users = [] + async for channel_member in self.services.channel_member.find( + channel_uid=channel_uid, + is_banned=False, + is_muted=False, + deleted_at=None, + ): + yield await self.services.user.get(uid=channel_member["user_uid"]) + + async def get_online_users(self, channel_uid): + users = [] + async for user in self.get_users(channel_uid): + if not user["last_ping"]: + continue + + if (datetime.fromisoformat(now()) - datetime.fromisoformat(user["last_ping"])).total_seconds() < 30: + yield user + async def ensure_public_channel(self, created_by_uid): model = await self.get(is_listed=True, tag="public") is_moderator = False diff --git a/src/snek/service/user.py b/src/snek/service/user.py index 1cad3ee..02707b0 100644 --- a/src/snek/service/user.py +++ b/src/snek/service/user.py @@ -27,6 +27,7 @@ class UserService(BaseService): user['color'] = await self.services.util.random_light_hex_color() return await super().save(user) + async def register(self, email, username, password): if await self.exists(username=username): raise Exception("User already exists.") diff --git a/src/snek/static/app.js b/src/snek/static/app.js index a0f83c2..b6b468b 100644 --- a/src/snek/static/app.js +++ b/src/snek/static/app.js @@ -206,11 +206,14 @@ class Socket extends EventHandler { ws.onerror = () => { this.onClose(); }; + this.onConnect() this.connectPromises.forEach(resolver => resolver(this)); }; }); } - + onConnect(){ + this.emit("connected") + } onData(data) { if (data.success !== undefined && !data.success) { console.error(data); @@ -282,12 +285,31 @@ class App extends EventHandler { audio = null; user = {}; + async ping(...args) { + if(this.is_pinging)return false + this.is_pinging = true + await this.rpc.ping(...args); + this.is_pinging = false + } + async forcePing(...arg) { + await this.rpc.ping(...args); + } + constructor() { super(); this.ws = new Socket(); this.rpc = this.ws.client; this.audio = new NotificationAudio(500); + this.is_pinging = false + this.ping_interval = setInterval(()=>{ + this.ping("active") + }, 15000) + + const me = this + this.ws.addEventListener("connected", (data)=> { + this.ping("online") + }) this.ws.addEventListener("channel-message", (data) => { me.emit("channel-message", data); }); diff --git a/src/snek/view/rpc.py b/src/snek/view/rpc.py index 9aa89f5..02e7bff 100644 --- a/src/snek/view/rpc.py +++ b/src/snek/view/rpc.py @@ -11,6 +11,7 @@ from aiohttp import web from snek.system.view import BaseView import traceback import json +from snek.system.model import now class RPCView(BaseView): @@ -133,8 +134,24 @@ class RPCView(BaseView): async def _send_json(self, obj): await self.ws.send_str(json.dumps(obj, default=str)) + - async def call_ping(self, callId, *args): + async def get_online_users(self, channel_uid): + self._require_login() + + return [dict(uid=record['uid'],username=record['username'], nick=record['nick']) async for record in self.services.channel.get_online_users(channel_uid)] + + + async def get_users(self, channel_uid): + self._require_login() + + return [dict(uid=record['uid'],username=record['username'], nick=record['nick']) async for record in self.services.channel.get_users(channel_uid)] + + async def ping(self, callId, *args): + if self.user_uid: + user = await self.services.user.get(uid=self.user_uid) + user['last_ping'] = now() + await self.services.user.save(user) return {"pong": args} async def get(self):