Added webdav.
This commit is contained in:
parent
fe1b3d6d19
commit
29139d5d0c
pyproject.toml
src
snek
app.pydump.py
form
mapper
model
service
__init__.pychannel.pychannel_member.pychannel_message.pychat.pydrive.pydrive_item.pynotification.pysocket.pyuser.pyutil.py
system
view
snekssh
@ -15,6 +15,8 @@ keywords = ["chat", "snek", "molodetz"]
|
|||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mkdocs>=1.4.0",
|
"mkdocs>=1.4.0",
|
||||||
|
"lxml",
|
||||||
|
|
||||||
"shed",
|
"shed",
|
||||||
"app @ git+https://retoor.molodetz.nl/retoor/app",
|
"app @ git+https://retoor.molodetz.nl/retoor/app",
|
||||||
"beautifulsoup4",
|
"beautifulsoup4",
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import pathlib
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import pathlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from snek.view.threads import ThreadsView
|
from snek.view.threads import ThreadsView
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp_session import (
|
from aiohttp_session import (
|
||||||
get_session as session_get,
|
get_session as session_get,
|
||||||
@ -24,23 +25,24 @@ from snek.system import http
|
|||||||
from snek.system.cache import Cache
|
from snek.system.cache import Cache
|
||||||
from snek.system.markdown import MarkdownExtension
|
from snek.system.markdown import MarkdownExtension
|
||||||
from snek.system.middleware import cors_middleware
|
from snek.system.middleware import cors_middleware
|
||||||
from snek.system.template import LinkifyExtension, PythonExtension,EmojiExtension
|
from snek.system.profiler import profiler_handler
|
||||||
|
from snek.system.template import EmojiExtension, LinkifyExtension, PythonExtension
|
||||||
from snek.view.about import AboutHTMLView, AboutMDView
|
from snek.view.about import AboutHTMLView, AboutMDView
|
||||||
|
from snek.view.avatar import AvatarView
|
||||||
from snek.view.docs import DocsHTMLView, DocsMDView
|
from snek.view.docs import DocsHTMLView, DocsMDView
|
||||||
|
from snek.view.drive import DriveView
|
||||||
from snek.view.index import IndexView
|
from snek.view.index import IndexView
|
||||||
from snek.view.login import LoginView
|
from snek.view.login import LoginView
|
||||||
from snek.view.logout import LogoutView
|
from snek.view.logout import LogoutView
|
||||||
from snek.view.register import RegisterView
|
from snek.view.register import RegisterView
|
||||||
from snek.view.rpc import RPCView
|
from snek.view.rpc import RPCView
|
||||||
|
from snek.view.search_user import SearchUserView
|
||||||
from snek.view.status import StatusView
|
from snek.view.status import StatusView
|
||||||
from snek.view.web import WebView
|
from snek.view.terminal import TerminalSocketView, TerminalView
|
||||||
from snek.view.upload import UploadView
|
from snek.view.upload import UploadView
|
||||||
from snek.view.search_user import SearchUserView
|
from snek.view.web import WebView
|
||||||
from snek.view.avatar import AvatarView
|
from snek.webdav import WebdavApplication
|
||||||
from snek.system.profiler import profiler_handler
|
|
||||||
from snek.view.terminal import TerminalView, TerminalSocketView
|
|
||||||
from snek.view.drive import DriveView
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
|
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +52,15 @@ async def session_middleware(request, handler):
|
|||||||
response = await handler(request)
|
response = await handler(request)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@web.middleware
|
||||||
|
async def trailing_slash_middleware(request, handler):
|
||||||
|
if request.path and not request.path.endswith("/"):
|
||||||
|
# Redirect to the same path with a trailing slash
|
||||||
|
raise web.HTTPFound(request.path + "/")
|
||||||
|
return await handler(request)
|
||||||
|
|
||||||
|
|
||||||
class Application(BaseApplication):
|
class Application(BaseApplication):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -68,9 +79,9 @@ class Application(BaseApplication):
|
|||||||
self.jinja2_env.add_extension(LinkifyExtension)
|
self.jinja2_env.add_extension(LinkifyExtension)
|
||||||
self.jinja2_env.add_extension(PythonExtension)
|
self.jinja2_env.add_extension(PythonExtension)
|
||||||
self.jinja2_env.add_extension(EmojiExtension)
|
self.jinja2_env.add_extension(EmojiExtension)
|
||||||
|
|
||||||
self.setup_router()
|
self.setup_router()
|
||||||
|
|
||||||
self.cache = Cache(self)
|
self.cache = Cache(self)
|
||||||
self.services = get_services(app=self)
|
self.services = get_services(app=self)
|
||||||
self.mappers = get_mappers(app=self)
|
self.mappers = get_mappers(app=self)
|
||||||
@ -81,7 +92,7 @@ class Application(BaseApplication):
|
|||||||
|
|
||||||
async def task_runner(self):
|
async def task_runner(self):
|
||||||
while True:
|
while True:
|
||||||
task = await self.tasks.get()
|
task = await self.tasks.get()
|
||||||
self.db.begin()
|
self.db.begin()
|
||||||
try:
|
try:
|
||||||
task_start = time.time()
|
task_start = time.time()
|
||||||
@ -93,20 +104,20 @@ class Application(BaseApplication):
|
|||||||
print(ex)
|
print(ex)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
async def prepare_database(self,app):
|
async def prepare_database(self, app):
|
||||||
self.db.query("PRAGMA journal_mode=WAL")
|
self.db.query("PRAGMA journal_mode=WAL")
|
||||||
self.db.query("PRAGMA syncnorm=off")
|
self.db.query("PRAGMA syncnorm=off")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.db["user"].has_index("username"):
|
if not self.db["user"].has_index("username"):
|
||||||
self.db["user"].create_index("username", unique=True)
|
self.db["user"].create_index("username", unique=True)
|
||||||
if not self.db["channel_member"].has_index(["channel_uid","user_uid"]):
|
if not self.db["channel_member"].has_index(["channel_uid", "user_uid"]):
|
||||||
self.db["channel_member"].create_index(["channel_uid","user_uid"])
|
self.db["channel_member"].create_index(["channel_uid", "user_uid"])
|
||||||
if not self.db["channel_message"].has_index(["channel_uid","user_uid"]):
|
if not self.db["channel_message"].has_index(["channel_uid", "user_uid"]):
|
||||||
self.db["channel_message"].create_index(["channel_uid","user_uid"])
|
self.db["channel_message"].create_index(["channel_uid", "user_uid"])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await app.services.drive.prepare_all()
|
await app.services.drive.prepare_all()
|
||||||
self.loop.create_task(self.task_runner())
|
self.loop.create_task(self.task_runner())
|
||||||
|
|
||||||
@ -145,6 +156,8 @@ class Application(BaseApplication):
|
|||||||
self.router.add_view("/terminal.html", TerminalView)
|
self.router.add_view("/terminal.html", TerminalView)
|
||||||
self.router.add_view("/drive.json", DriveView)
|
self.router.add_view("/drive.json", DriveView)
|
||||||
self.router.add_view("/drive/{drive}.json", DriveView)
|
self.router.add_view("/drive/{drive}.json", DriveView)
|
||||||
|
self.webdav = WebdavApplication(self)
|
||||||
|
self.add_subapp("/webdav", self.webdav)
|
||||||
|
|
||||||
self.add_subapp(
|
self.add_subapp(
|
||||||
"/docs",
|
"/docs",
|
||||||
@ -174,17 +187,21 @@ class Application(BaseApplication):
|
|||||||
channels = []
|
channels = []
|
||||||
if not context:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
if request.session.get("uid"):
|
if request.session.get("uid"):
|
||||||
async for subscribed_channel in self.services.channel_member.find(user_uid=request.session.get("uid"), deleted_at=None, is_banned=False):
|
async for subscribed_channel in self.services.channel_member.find(
|
||||||
|
user_uid=request.session.get("uid"), deleted_at=None, is_banned=False
|
||||||
|
):
|
||||||
item = {}
|
item = {}
|
||||||
other_user = await self.services.channel_member.get_other_dm_user(subscribed_channel["channel_uid"], request.session.get("uid"))
|
other_user = await self.services.channel_member.get_other_dm_user(
|
||||||
|
subscribed_channel["channel_uid"], request.session.get("uid")
|
||||||
|
)
|
||||||
parent_object = await subscribed_channel.get_channel()
|
parent_object = await subscribed_channel.get_channel()
|
||||||
last_message =await parent_object.get_last_message()
|
last_message = await parent_object.get_last_message()
|
||||||
color = None
|
color = None
|
||||||
if last_message:
|
if last_message:
|
||||||
last_message_user = await last_message.get_user()
|
last_message_user = await last_message.get_user()
|
||||||
color = last_message_user["color"]
|
color = last_message_user["color"]
|
||||||
item['color'] = color
|
item["color"] = color
|
||||||
item["last_message_on"] = parent_object["last_message_on"]
|
item["last_message_on"] = parent_object["last_message_on"]
|
||||||
item["is_private"] = parent_object["tag"] == "dm"
|
item["is_private"] = parent_object["tag"] == "dm"
|
||||||
if other_user:
|
if other_user:
|
||||||
@ -193,19 +210,22 @@ class Application(BaseApplication):
|
|||||||
else:
|
else:
|
||||||
item["name"] = subscribed_channel["label"]
|
item["name"] = subscribed_channel["label"]
|
||||||
item["uid"] = subscribed_channel["channel_uid"]
|
item["uid"] = subscribed_channel["channel_uid"]
|
||||||
item['new_count'] = subscribed_channel['new_count']
|
item["new_count"] = subscribed_channel["new_count"]
|
||||||
|
|
||||||
print(item)
|
print(item)
|
||||||
channels.append(item)
|
channels.append(item)
|
||||||
|
|
||||||
channels.sort(key=lambda x: x['last_message_on'] or '', reverse=True)
|
channels.sort(key=lambda x: x["last_message_on"] or "", reverse=True)
|
||||||
if not 'channels' in context:
|
if "channels" not in context:
|
||||||
context['channels'] = channels
|
context["channels"] = channels
|
||||||
if not 'user' in context:
|
if "user" not in context:
|
||||||
context['user'] = await self.services.user.get(uid=request.session.get("uid"))
|
context["user"] = await self.services.user.get(
|
||||||
|
uid=request.session.get("uid")
|
||||||
|
)
|
||||||
|
|
||||||
return await super().render_template(template, request, context)
|
return await super().render_template(template, request, context)
|
||||||
|
|
||||||
|
|
||||||
executor = ThreadPoolExecutor(max_workers=200)
|
executor = ThreadPoolExecutor(max_workers=200)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
@ -213,8 +233,10 @@ loop.set_default_executor(executor)
|
|||||||
|
|
||||||
app = Application(db_path="sqlite:///snek.db")
|
app = Application(db_path="sqlite:///snek.db")
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
await web._run_app(app, port=8081, host="0.0.0.0")
|
await web._run_app(app, port=8081, host="0.0.0.0")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
@ -1,31 +1,39 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
|
||||||
|
|
||||||
from snek.app import app
|
from snek.app import app
|
||||||
|
|
||||||
|
|
||||||
async def fix_message(message):
|
async def fix_message(message):
|
||||||
message = dict(
|
message = {
|
||||||
uid=message['uid'],
|
"uid": message["uid"],
|
||||||
user_uid=message['user_uid'],
|
"user_uid": message["user_uid"],
|
||||||
text=message['message'],
|
"text": message["message"],
|
||||||
sent=message['created_at']
|
"sent": message["created_at"],
|
||||||
)
|
}
|
||||||
user = await app.services.user.get(uid=message['user_uid'])
|
user = await app.services.user.get(uid=message["user_uid"])
|
||||||
message['user'] = user and user['username'] or None
|
message["user"] = user and user["username"] or None
|
||||||
return (message['user'] or '') + ': ' + (message['text'] or '')
|
return (message["user"] or "") + ": " + (message["text"] or "")
|
||||||
|
|
||||||
|
|
||||||
async def dump_public_channels():
|
async def dump_public_channels():
|
||||||
result = []
|
result = []
|
||||||
for channel in app.db['channel'].find(is_private=False,is_listed=True,tag='public'):
|
for channel in app.db["channel"].find(
|
||||||
|
is_private=False, is_listed=True, tag="public"
|
||||||
|
):
|
||||||
print(f"Dumping channel: {channel['label']}.")
|
print(f"Dumping channel: {channel['label']}.")
|
||||||
result += [await fix_message(record) for record in app.db['channel_message'].find(channel_uid=channel['uid'],order_by='created_at')]
|
result += [
|
||||||
|
await fix_message(record)
|
||||||
|
for record in app.db["channel_message"].find(
|
||||||
|
channel_uid=channel["uid"], order_by="created_at"
|
||||||
|
)
|
||||||
|
]
|
||||||
print("Dump succesfull!")
|
print("Dump succesfull!")
|
||||||
print("Converting to json.")
|
print("Converting to json.")
|
||||||
print("Converting succesful, now writing to dump.json")
|
print("Converting succesful, now writing to dump.json")
|
||||||
with open("dump.txt","w") as f:
|
with open("dump.txt", "w") as f:
|
||||||
f.write('\n\n'.join(result))
|
f.write("\n\n".join(result))
|
||||||
print("Dump written to dump.json")
|
print("Dump written to dump.json")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
asyncio.run(dump_public_channels())
|
asyncio.run(dump_public_channels())
|
||||||
|
@ -16,4 +16,3 @@ class SearchUserForm(Form):
|
|||||||
action = FormButtonElement(
|
action = FormButtonElement(
|
||||||
name="action", value="submit", text="Search", type="button"
|
name="action", value="submit", text="Search", type="button"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ import functools
|
|||||||
from snek.mapper.channel import ChannelMapper
|
from snek.mapper.channel import ChannelMapper
|
||||||
from snek.mapper.channel_member import ChannelMemberMapper
|
from snek.mapper.channel_member import ChannelMemberMapper
|
||||||
from snek.mapper.channel_message import ChannelMessageMapper
|
from snek.mapper.channel_message import ChannelMessageMapper
|
||||||
|
from snek.mapper.drive import DriveMapper
|
||||||
|
from snek.mapper.drive_item import DriveItemMapper
|
||||||
from snek.mapper.notification import NotificationMapper
|
from snek.mapper.notification import NotificationMapper
|
||||||
from snek.mapper.user import UserMapper
|
from snek.mapper.user import UserMapper
|
||||||
from snek.mapper.drive import DriveMapper
|
|
||||||
from snek.mapper.drive_item import DriveItemMapper
|
|
||||||
from snek.system.object import Object
|
from snek.system.object import Object
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,5 +3,5 @@ from snek.system.mapper import BaseMapper
|
|||||||
|
|
||||||
|
|
||||||
class DriveMapper(BaseMapper):
|
class DriveMapper(BaseMapper):
|
||||||
table_name = 'drive'
|
table_name = "drive"
|
||||||
model_class = DriveModel
|
model_class = DriveModel
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
from snek.model.drive_item import DriveItemModel
|
||||||
from snek.system.mapper import BaseMapper
|
from snek.system.mapper import BaseMapper
|
||||||
from snek.model.drive_item import DriveItemModel
|
|
||||||
|
|
||||||
class DriveItemMapper(BaseMapper):
|
class DriveItemMapper(BaseMapper):
|
||||||
|
|
||||||
model_class = DriveItemModel
|
model_class = DriveItemModel
|
||||||
table_name = 'drive_item'
|
table_name = "drive_item"
|
||||||
|
@ -12,11 +12,16 @@ class ChannelModel(BaseModel):
|
|||||||
index = ModelField(name="index", required=True, kind=int, value=1000)
|
index = ModelField(name="index", required=True, kind=int, value=1000)
|
||||||
last_message_on = ModelField(name="last_message_on", required=False, kind=str)
|
last_message_on = ModelField(name="last_message_on", required=False, kind=str)
|
||||||
|
|
||||||
async def get_last_message(self)->ChannelMessageModel:
|
async def get_last_message(self) -> ChannelMessageModel:
|
||||||
async for model in self.app.services.channel_message.query("SELECT uid FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT 1",dict(channel_uid=self['uid'])):
|
async for model in self.app.services.channel_message.query(
|
||||||
|
"SELECT uid FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT 1",
|
||||||
return await self.app.services.channel_message.get(uid=model['uid'])
|
{"channel_uid": self["uid"]},
|
||||||
|
):
|
||||||
|
|
||||||
|
return await self.app.services.channel_message.get(uid=model["uid"])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_members(self):
|
async def get_members(self):
|
||||||
return await self.app.services.channel_member.find(channel_uid=self['uid'],deleted_at=None,is_banned=False)
|
return await self.app.services.channel_member.find(
|
||||||
|
channel_uid=self["uid"], deleted_at=None, is_banned=False
|
||||||
|
)
|
||||||
|
@ -16,25 +16,26 @@ class ChannelMemberModel(BaseModel):
|
|||||||
new_count = ModelField(name="new_count", required=False, kind=int, value=0)
|
new_count = ModelField(name="new_count", required=False, kind=int, value=0)
|
||||||
|
|
||||||
async def get_user(self):
|
async def get_user(self):
|
||||||
return await self.app.services.user.get(uid=self['user_uid'])
|
return await self.app.services.user.get(uid=self["user_uid"])
|
||||||
|
|
||||||
async def get_channel(self):
|
async def get_channel(self):
|
||||||
return await self.app.services.channel.get(uid=self['channel_uid'])
|
return await self.app.services.channel.get(uid=self["channel_uid"])
|
||||||
|
|
||||||
async def get_name(self):
|
async def get_name(self):
|
||||||
channel = await self.get_channel()
|
channel = await self.get_channel()
|
||||||
if channel["tag"] == "dm":
|
if channel["tag"] == "dm":
|
||||||
user = await self.get_other_dm_user()
|
user = await self.get_other_dm_user()
|
||||||
return user['nick']
|
return user["nick"]
|
||||||
return channel['name'] or self['label']
|
return channel["name"] or self["label"]
|
||||||
|
|
||||||
async def get_other_dm_user(self):
|
async def get_other_dm_user(self):
|
||||||
channel = await self.get_channel()
|
channel = await self.get_channel()
|
||||||
if channel["tag"] != "dm":
|
if channel["tag"] != "dm":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async for model in self.app.services.channel_member.find(channel_uid=channel['uid']):
|
async for model in self.app.services.channel_member.find(
|
||||||
if model["uid"] != self['uid']:
|
channel_uid=channel["uid"]
|
||||||
|
):
|
||||||
|
if model["uid"] != self["uid"]:
|
||||||
return await self.app.services.user.get(uid=model["user_uid"])
|
return await self.app.services.user.get(uid=model["user_uid"])
|
||||||
return await self.get_user()
|
return await self.get_user()
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ class ChannelMessageModel(BaseModel):
|
|||||||
message = ModelField(name="message", required=True, kind=str)
|
message = ModelField(name="message", required=True, kind=str)
|
||||||
html = ModelField(name="html", required=False, kind=str)
|
html = ModelField(name="html", required=False, kind=str)
|
||||||
|
|
||||||
async def get_user(self)->UserModel:
|
async def get_user(self) -> UserModel:
|
||||||
return await self.app.services.user.get(uid=self["user_uid"])
|
return await self.app.services.user.get(uid=self["user_uid"])
|
||||||
|
|
||||||
async def get_channel(self):
|
async def get_channel(self):
|
||||||
return await self.app.services.channel.get(uid=self["channel_uid"])
|
return await self.app.services.channel.get(uid=self["channel_uid"])
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
from snek.system.model import BaseModel,ModelField
|
from snek.system.model import BaseModel, ModelField
|
||||||
|
|
||||||
|
|
||||||
class DriveModel(BaseModel):
|
class DriveModel(BaseModel):
|
||||||
|
|
||||||
user_uid = ModelField(name="user_uid", required=True)
|
user_uid = ModelField(name="user_uid", required=True)
|
||||||
name = ModelField(name='name', required=False, type=str)
|
name = ModelField(name="name", required=False, type=str)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def items(self):
|
async def items(self):
|
||||||
async for drive_item in self.app.services.drive_item.find(drive_uid=self['uid']):
|
async for drive_item in self.app.services.drive_item.find(
|
||||||
|
drive_uid=self["uid"]
|
||||||
|
):
|
||||||
yield drive_item
|
yield drive_item
|
||||||
|
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
from snek.system.model import BaseModel,ModelField
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
|
from snek.system.model import BaseModel, ModelField
|
||||||
|
|
||||||
|
|
||||||
class DriveItemModel(BaseModel):
|
class DriveItemModel(BaseModel):
|
||||||
drive_uid = ModelField(name="drive_uid", required=True,kind=str)
|
drive_uid = ModelField(name="drive_uid", required=True, kind=str)
|
||||||
name = ModelField(name="name", required=True,kind=str)
|
name = ModelField(name="name", required=True, kind=str)
|
||||||
path = ModelField(name="path", required=True,kind=str)
|
path = ModelField(name="path", required=True, kind=str)
|
||||||
file_type = ModelField(name="file_type", required=True,kind=str)
|
file_type = ModelField(name="file_type", required=True, kind=str)
|
||||||
file_size = ModelField(name="file_size", required=True,kind=int)
|
file_size = ModelField(name="file_size", required=True, kind=int)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extension(self):
|
def extension(self):
|
||||||
return self['name'].split('.')[-1]
|
return self["name"].split(".")[-1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mime_type(self):
|
def mime_type(self):
|
||||||
mimetype,_ = mimetypes.guess_type(self['name'])
|
mimetype, _ = mimetypes.guess_type(self["name"])
|
||||||
return mimetype
|
return mimetype
|
||||||
|
@ -18,10 +18,7 @@ class UserModel(BaseModel):
|
|||||||
regex=r"^[a-zA-Z0-9_-+/]+$",
|
regex=r"^[a-zA-Z0-9_-+/]+$",
|
||||||
)
|
)
|
||||||
color = ModelField(
|
color = ModelField(
|
||||||
name ="color",
|
name="color", required=True, regex=r"^#[0-9a-fA-F]{6}$", kind=str
|
||||||
required=True,
|
|
||||||
regex=r"^#[0-9a-fA-F]{6}$",
|
|
||||||
kind=str
|
|
||||||
)
|
)
|
||||||
email = ModelField(
|
email = ModelField(
|
||||||
name="email",
|
name="email",
|
||||||
@ -33,5 +30,7 @@ class UserModel(BaseModel):
|
|||||||
last_ping = ModelField(name="last_ping", required=False, kind=str)
|
last_ping = ModelField(name="last_ping", required=False, kind=str)
|
||||||
|
|
||||||
async def get_channel_members(self):
|
async def get_channel_members(self):
|
||||||
async for channel_member in self.app.services.channel_member.find(user_uid=self['uid'],is_banned=False,deleted_at=None):
|
async for channel_member in self.app.services.channel_member.find(
|
||||||
|
user_uid=self["uid"], is_banned=False, deleted_at=None
|
||||||
|
):
|
||||||
yield channel_member
|
yield channel_member
|
||||||
|
@ -4,12 +4,12 @@ from snek.service.channel import ChannelService
|
|||||||
from snek.service.channel_member import ChannelMemberService
|
from snek.service.channel_member import ChannelMemberService
|
||||||
from snek.service.channel_message import ChannelMessageService
|
from snek.service.channel_message import ChannelMessageService
|
||||||
from snek.service.chat import ChatService
|
from snek.service.chat import ChatService
|
||||||
|
from snek.service.drive import DriveService
|
||||||
|
from snek.service.drive_item import DriveItemService
|
||||||
from snek.service.notification import NotificationService
|
from snek.service.notification import NotificationService
|
||||||
from snek.service.socket import SocketService
|
from snek.service.socket import SocketService
|
||||||
from snek.service.user import UserService
|
from snek.service.user import UserService
|
||||||
from snek.service.util import UtilService
|
from snek.service.util import UtilService
|
||||||
from snek.service.drive import DriveService
|
|
||||||
from snek.service.drive_item import DriveItemService
|
|
||||||
from snek.system.object import Object
|
from snek.system.object import Object
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ def get_services(app):
|
|||||||
"notification": NotificationService(app=app),
|
"notification": NotificationService(app=app),
|
||||||
"util": UtilService(app=app),
|
"util": UtilService(app=app),
|
||||||
"drive": DriveService(app=app),
|
"drive": DriveService(app=app),
|
||||||
"drive_item": DriveItemService(app=app)
|
"drive_item": DriveItemService(app=app),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
from snek.system.service import BaseService
|
from datetime import datetime
|
||||||
from datetime import datetime
|
|
||||||
|
from snek.system.model import now
|
||||||
|
from snek.system.service import BaseService
|
||||||
|
|
||||||
from snek.system.model import now
|
|
||||||
|
|
||||||
class ChannelService(BaseService):
|
class ChannelService(BaseService):
|
||||||
mapper_name = "channel"
|
mapper_name = "channel"
|
||||||
|
|
||||||
async def get(
|
async def get(self, uid=None, **kwargs):
|
||||||
self,
|
|
||||||
uid=None,
|
|
||||||
**kwargs):
|
|
||||||
if uid:
|
if uid:
|
||||||
kwargs['uid'] = uid
|
kwargs["uid"] = uid
|
||||||
result = await super().get(**kwargs)
|
|
||||||
if result:
|
|
||||||
return result
|
|
||||||
del kwargs['uid']
|
|
||||||
kwargs['name'] = uid
|
|
||||||
result = await super().get(**kwargs)
|
result = await super().get(**kwargs)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
kwargs['name'] = '#' + uid
|
del kwargs["uid"]
|
||||||
|
kwargs["name"] = uid
|
||||||
result = await super().get(**kwargs)
|
result = await super().get(**kwargs)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
return None
|
kwargs["name"] = "#" + uid
|
||||||
|
result = await super().get(**kwargs)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
return None
|
||||||
return await super().get(**kwargs)
|
return await super().get(**kwargs)
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
@ -53,38 +51,34 @@ class ChannelService(BaseService):
|
|||||||
raise Exception(f"Failed to create channel: {model.errors}.")
|
raise Exception(f"Failed to create channel: {model.errors}.")
|
||||||
|
|
||||||
async def get_dm(self, user1, user2):
|
async def get_dm(self, user1, user2):
|
||||||
channel_member = await self.services.channel_member.get_dm(
|
channel_member = await self.services.channel_member.get_dm(user1, user2)
|
||||||
user1, user2
|
|
||||||
)
|
|
||||||
if channel_member:
|
if channel_member:
|
||||||
return await self.get(uid=channel_member["channel_uid"])
|
return await self.get(uid=channel_member["channel_uid"])
|
||||||
channel = await self.create(
|
channel = await self.create("DM", user1, tag="dm")
|
||||||
"DM", user1, tag="dm"
|
await self.services.channel_member.create_dm(channel["uid"], user1, user2)
|
||||||
)
|
return channel
|
||||||
await self.services.channel_member.create_dm(
|
|
||||||
channel["uid"], user1, user2
|
|
||||||
)
|
|
||||||
return channel
|
|
||||||
|
|
||||||
async def get_users(self, channel_uid):
|
async def get_users(self, channel_uid):
|
||||||
users = []
|
|
||||||
async for channel_member in self.services.channel_member.find(
|
async for channel_member in self.services.channel_member.find(
|
||||||
channel_uid=channel_uid,
|
channel_uid=channel_uid,
|
||||||
is_banned=False,
|
is_banned=False,
|
||||||
is_muted=False,
|
is_muted=False,
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
):
|
):
|
||||||
user = await self.services.user.get(uid=channel_member["user_uid"])
|
user = await self.services.user.get(uid=channel_member["user_uid"])
|
||||||
if user:
|
if user:
|
||||||
yield user
|
yield user
|
||||||
|
|
||||||
async def get_online_users(self, channel_uid):
|
async def get_online_users(self, channel_uid):
|
||||||
users = []
|
|
||||||
async for user in self.get_users(channel_uid):
|
async for user in self.get_users(channel_uid):
|
||||||
if not user["last_ping"]:
|
if not user["last_ping"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (datetime.fromisoformat(now()) - datetime.fromisoformat(user["last_ping"])).total_seconds() < 20:
|
if (
|
||||||
yield user
|
datetime.fromisoformat(now())
|
||||||
|
- datetime.fromisoformat(user["last_ping"])
|
||||||
|
).total_seconds() < 20:
|
||||||
|
yield user
|
||||||
|
|
||||||
async def get_for_user(self, user_uid):
|
async def get_for_user(self, user_uid):
|
||||||
async for channel_member in self.services.channel_member.find(
|
async for channel_member in self.services.channel_member.find(
|
||||||
@ -93,7 +87,7 @@ class ChannelService(BaseService):
|
|||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
):
|
):
|
||||||
channel = await self.get(uid=channel_member["channel_uid"])
|
channel = await self.get(uid=channel_member["channel_uid"])
|
||||||
yield channel
|
yield channel
|
||||||
|
|
||||||
async def ensure_public_channel(self, created_by_uid):
|
async def ensure_public_channel(self, created_by_uid):
|
||||||
model = await self.get(is_listed=True, tag="public")
|
model = await self.get(is_listed=True, tag="public")
|
||||||
|
@ -6,10 +6,7 @@ class ChannelMemberService(BaseService):
|
|||||||
mapper_name = "channel_member"
|
mapper_name = "channel_member"
|
||||||
|
|
||||||
async def mark_as_read(self, channel_uid, user_uid):
|
async def mark_as_read(self, channel_uid, user_uid):
|
||||||
channel_member = await self.get(
|
channel_member = await self.get(channel_uid=channel_uid, user_uid=user_uid)
|
||||||
channel_uid=channel_uid,
|
|
||||||
user_uid=user_uid
|
|
||||||
)
|
|
||||||
channel_member["new_count"] = 0
|
channel_member["new_count"] = 0
|
||||||
return await self.save(channel_member)
|
return await self.save(channel_member)
|
||||||
|
|
||||||
@ -24,7 +21,7 @@ class ChannelMemberService(BaseService):
|
|||||||
):
|
):
|
||||||
model = await self.get(channel_uid=channel_uid, user_uid=user_uid)
|
model = await self.get(channel_uid=channel_uid, user_uid=user_uid)
|
||||||
if model:
|
if model:
|
||||||
if model['is_banned']:
|
if model["is_banned"]:
|
||||||
return False
|
return False
|
||||||
return model
|
return model
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
@ -39,30 +36,32 @@ class ChannelMemberService(BaseService):
|
|||||||
if await self.save(model):
|
if await self.save(model):
|
||||||
return model
|
return model
|
||||||
raise Exception(f"Failed to create channel member: {model.errors}.")
|
raise Exception(f"Failed to create channel member: {model.errors}.")
|
||||||
|
|
||||||
async def get_dm(self,from_user, to_user):
|
async def get_dm(self, from_user, to_user):
|
||||||
async for model in self.query("SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') INNER JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = channel.uid AND channel_member2.user_uid = :to_user) WHERE channel_member.user_uid=:from_user " ,dict(from_user=from_user, to_user=to_user)):
|
async for model in self.query(
|
||||||
|
"SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') INNER JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = channel.uid AND channel_member2.user_uid = :to_user) WHERE channel_member.user_uid=:from_user ",
|
||||||
|
{"from_user": from_user, "to_user": to_user},
|
||||||
|
):
|
||||||
return model
|
return model
|
||||||
if not from_user == to_user:
|
if not from_user == to_user:
|
||||||
return None
|
return None
|
||||||
async for model in self.query("SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') LEFT JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = NULL AND channel_member2.user_uid = NULL) WHERE channel_member.user_uid=:from_user " ,dict(from_user=from_user, to_user=to_user)):
|
async for model in self.query(
|
||||||
|
"SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') LEFT JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = NULL AND channel_member2.user_uid = NULL) WHERE channel_member.user_uid=:from_user ",
|
||||||
return model
|
{"from_user": from_user, "to_user": to_user},
|
||||||
|
):
|
||||||
|
|
||||||
|
return model
|
||||||
|
|
||||||
async def get_other_dm_user(self, channel_uid, user_uid):
|
async def get_other_dm_user(self, channel_uid, user_uid):
|
||||||
channel_member = await self.get(channel_uid=channel_uid, user_uid=user_uid)
|
channel_member = await self.get(channel_uid=channel_uid, user_uid=user_uid)
|
||||||
channel = await self.services.channel.get(uid=channel_member['channel_uid'])
|
channel = await self.services.channel.get(uid=channel_member["channel_uid"])
|
||||||
if channel["tag"] != "dm":
|
if channel["tag"] != "dm":
|
||||||
return None
|
return None
|
||||||
async for model in self.services.channel_member.find(channel_uid=channel_uid):
|
async for model in self.services.channel_member.find(channel_uid=channel_uid):
|
||||||
if model["uid"] != channel_member['uid']:
|
if model["uid"] != channel_member["uid"]:
|
||||||
return await self.services.user.get(uid=model["user_uid"])
|
return await self.services.user.get(uid=model["user_uid"])
|
||||||
|
|
||||||
async def create_dm(self,channel_uid, from_user_uid, to_user_uid):
|
async def create_dm(self, channel_uid, from_user_uid, to_user_uid):
|
||||||
result = await self.create(channel_uid, from_user_uid)
|
result = await self.create(channel_uid, from_user_uid)
|
||||||
await self.create(channel_uid, to_user_uid)
|
await self.create(channel_uid, to_user_uid)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,71 +1,93 @@
|
|||||||
from snek.system.service import BaseService
|
from snek.system.service import BaseService
|
||||||
import jinja2
|
|
||||||
|
|
||||||
class ChannelMessageService(BaseService):
|
class ChannelMessageService(BaseService):
|
||||||
mapper_name = "channel_message"
|
mapper_name = "channel_message"
|
||||||
|
|
||||||
async def create(self, channel_uid, user_uid, message):
|
async def create(self, channel_uid, user_uid, message):
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
|
|
||||||
model["channel_uid"] = channel_uid
|
model["channel_uid"] = channel_uid
|
||||||
model["user_uid"] = user_uid
|
model["user_uid"] = user_uid
|
||||||
model["message"] = message
|
model["message"] = message
|
||||||
|
|
||||||
context = {
|
context = {}
|
||||||
|
|
||||||
}
|
record = model.record
|
||||||
|
|
||||||
|
|
||||||
record = model.record
|
|
||||||
context.update(record)
|
context.update(record)
|
||||||
user = await self.app.services.user.get(uid=user_uid)
|
user = await self.app.services.user.get(uid=user_uid)
|
||||||
context.update(dict(
|
context.update(
|
||||||
user_uid=user['uid'],
|
{
|
||||||
username=user['username'],
|
"user_uid": user["uid"],
|
||||||
user_nick=user['nick'],
|
"username": user["username"],
|
||||||
color=user['color']
|
"user_nick": user["nick"],
|
||||||
))
|
"color": user["color"],
|
||||||
|
}
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
template = self.app.jinja2_env.get_template("message.html")
|
template = self.app.jinja2_env.get_template("message.html")
|
||||||
model["html"] = template.render(**context)
|
model["html"] = template.render(**context)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(ex,flush=True)
|
print(ex, flush=True)
|
||||||
|
|
||||||
if await self.save(model):
|
if await self.save(model):
|
||||||
return model
|
return model
|
||||||
raise Exception(f"Failed to create channel message: {model.errors}.")
|
raise Exception(f"Failed to create channel message: {model.errors}.")
|
||||||
|
|
||||||
async def to_extended_dict(self, message):
|
async def to_extended_dict(self, message):
|
||||||
user = await self.services.user.get(uid=message["user_uid"])
|
user = await self.services.user.get(uid=message["user_uid"])
|
||||||
if not user:
|
if not user:
|
||||||
return {}
|
return {}
|
||||||
return {
|
return {
|
||||||
"uid": message["uid"],
|
"uid": message["uid"],
|
||||||
"color": user['color'],
|
"color": user["color"],
|
||||||
"user_uid": message["user_uid"],
|
"user_uid": message["user_uid"],
|
||||||
"channel_uid": message["channel_uid"],
|
"channel_uid": message["channel_uid"],
|
||||||
"user_nick": user['nick'],
|
"user_nick": user["nick"],
|
||||||
"message": message["message"],
|
"message": message["message"],
|
||||||
"created_at": message["created_at"],
|
"created_at": message["created_at"],
|
||||||
"html": message['html'],
|
"html": message["html"],
|
||||||
"username": user['username']
|
"username": user["username"],
|
||||||
}
|
}
|
||||||
|
|
||||||
async def offset(self, channel_uid, page=0, timestamp = None, page_size=30):
|
async def offset(self, channel_uid, page=0, timestamp=None, page_size=30):
|
||||||
results = []
|
results = []
|
||||||
offset = page * page_size
|
offset = page * page_size
|
||||||
try:
|
try:
|
||||||
if timestamp:
|
if timestamp:
|
||||||
async for model in self.query("SELECT * FROM channel_message WHERE channel_uid=:channel_uid AND created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",dict(channel_uid=channel_uid, page_size=page_size, offset=offset, timestamp=timestamp)):
|
async for model in self.query(
|
||||||
|
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid AND created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",
|
||||||
|
{
|
||||||
|
"channel_uid": channel_uid,
|
||||||
|
"page_size": page_size,
|
||||||
|
"offset": offset,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
},
|
||||||
|
):
|
||||||
results.append(model)
|
results.append(model)
|
||||||
elif page > 0:
|
elif page > 0:
|
||||||
async for model in self.query("SELECT * FROM channel_message WHERE channel_uid=:channel_uid WHERE created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size",dict(channel_uid=channel_uid, page_size=page_size, offset=offset,timestamp=timestamp )):
|
async for model in self.query(
|
||||||
|
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid WHERE created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size",
|
||||||
|
{
|
||||||
|
"channel_uid": channel_uid,
|
||||||
|
"page_size": page_size,
|
||||||
|
"offset": offset,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
},
|
||||||
|
):
|
||||||
results.append(model)
|
results.append(model)
|
||||||
else:
|
else:
|
||||||
async for model in self.query("SELECT * FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",dict(channel_uid=channel_uid, page_size=page_size, offset=offset)):
|
async for model in self.query(
|
||||||
|
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",
|
||||||
|
{
|
||||||
|
"channel_uid": channel_uid,
|
||||||
|
"page_size": page_size,
|
||||||
|
"offset": offset,
|
||||||
|
},
|
||||||
|
):
|
||||||
results.append(model)
|
results.append(model)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
results.sort(key=lambda x: x['created_at'])
|
results.sort(key=lambda x: x["created_at"])
|
||||||
return results
|
return results
|
||||||
|
@ -1,40 +1,39 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from snek.system.model import now
|
from snek.system.model import now
|
||||||
from snek.system.service import BaseService
|
from snek.system.service import BaseService
|
||||||
|
|
||||||
|
|
||||||
class ChatService(BaseService):
|
class ChatService(BaseService):
|
||||||
|
|
||||||
async def send(self,user_uid, channel_uid, message):
|
async def send(self, user_uid, channel_uid, message):
|
||||||
channel = await self.services.channel.get(uid=channel_uid)
|
channel = await self.services.channel.get(uid=channel_uid)
|
||||||
if not channel:
|
if not channel:
|
||||||
raise Exception("Channel not found.")
|
raise Exception("Channel not found.")
|
||||||
channel_message = await self.services.channel_message.create(
|
channel_message = await self.services.channel_message.create(
|
||||||
channel_uid,
|
channel_uid, user_uid, message
|
||||||
user_uid,
|
|
||||||
message
|
|
||||||
)
|
)
|
||||||
channel_message_uid = channel_message["uid"]
|
channel_message_uid = channel_message["uid"]
|
||||||
|
|
||||||
|
|
||||||
user = await self.services.user.get(uid=user_uid)
|
user = await self.services.user.get(uid=user_uid)
|
||||||
channel['last_message_on'] = now()
|
channel["last_message_on"] = now()
|
||||||
await self.services.channel.save(channel)
|
await self.services.channel.save(channel)
|
||||||
|
|
||||||
await self.services.socket.broadcast(channel_uid, dict(
|
await self.services.socket.broadcast(
|
||||||
message=channel_message["message"],
|
channel_uid,
|
||||||
html=channel_message["html"],
|
{
|
||||||
user_uid=user_uid,
|
"message": channel_message["message"],
|
||||||
color=user['color'],
|
"html": channel_message["html"],
|
||||||
channel_uid=channel_uid,
|
"user_uid": user_uid,
|
||||||
created_at=channel_message["created_at"],
|
"color": user["color"],
|
||||||
updated_at=None,
|
"channel_uid": channel_uid,
|
||||||
username=user['username'],
|
"created_at": channel_message["created_at"],
|
||||||
uid=channel_message['uid'],
|
"updated_at": None,
|
||||||
user_nick=user['nick']
|
"username": user["username"],
|
||||||
))
|
"uid": channel_message["uid"],
|
||||||
await self.app.create_task(self.services.notification.create_channel_message(channel_message_uid))
|
"user_nick": user["nick"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await self.app.create_task(
|
||||||
|
self.services.notification.create_channel_message(channel_message_uid)
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -2,14 +2,90 @@ from snek.system.service import BaseService
|
|||||||
|
|
||||||
|
|
||||||
class DriveService(BaseService):
|
class DriveService(BaseService):
|
||||||
|
|
||||||
mapper_name = "drive"
|
mapper_name = "drive"
|
||||||
|
|
||||||
EXTENSIONS_PICTURES = ["jpg","jpeg","png","gif","svg","webp","tiff"]
|
EXTENSIONS_PICTURES = ["jpg", "jpeg", "png", "gif", "svg", "webp", "tiff"]
|
||||||
EXTENSIONS_VIDEOS = ["mp4","m4v","mov","wmv","webm","mkv","mpg","mpeg","avi","ogv","ogg","flv","3gp","3g2"]
|
EXTENSIONS_VIDEOS = [
|
||||||
EXTENSIONS_ARCHIVES = ["zip","rar","7z","tar","tar.gz","tar.xz","tar.bz2","tar.lzma","tar.lz"]
|
"mp4",
|
||||||
EXTENSIONS_AUDIO = ["mp3","wav","ogg","flac","m4a","wma","aac","opus","aiff","au","mid","midi"]
|
"m4v",
|
||||||
EXTENSIONS_DOCS = ["pdf","doc","docx","xls","xlsx","ppt","pptx","txt","md","json","csv","xml","html","css","js","py","sql","rs","toml","yml","yaml","ini","conf","config","log","csv","tsv","java","cs","csproj","scss","less","sass","json","lock","lock.json","jsonl"]
|
"mov",
|
||||||
|
"wmv",
|
||||||
|
"webm",
|
||||||
|
"mkv",
|
||||||
|
"mpg",
|
||||||
|
"mpeg",
|
||||||
|
"avi",
|
||||||
|
"ogv",
|
||||||
|
"ogg",
|
||||||
|
"flv",
|
||||||
|
"3gp",
|
||||||
|
"3g2",
|
||||||
|
]
|
||||||
|
EXTENSIONS_ARCHIVES = [
|
||||||
|
"zip",
|
||||||
|
"rar",
|
||||||
|
"7z",
|
||||||
|
"tar",
|
||||||
|
"tar.gz",
|
||||||
|
"tar.xz",
|
||||||
|
"tar.bz2",
|
||||||
|
"tar.lzma",
|
||||||
|
"tar.lz",
|
||||||
|
]
|
||||||
|
EXTENSIONS_AUDIO = [
|
||||||
|
"mp3",
|
||||||
|
"wav",
|
||||||
|
"ogg",
|
||||||
|
"flac",
|
||||||
|
"m4a",
|
||||||
|
"wma",
|
||||||
|
"aac",
|
||||||
|
"opus",
|
||||||
|
"aiff",
|
||||||
|
"au",
|
||||||
|
"mid",
|
||||||
|
"midi",
|
||||||
|
]
|
||||||
|
EXTENSIONS_DOCS = [
|
||||||
|
"pdf",
|
||||||
|
"doc",
|
||||||
|
"docx",
|
||||||
|
"xls",
|
||||||
|
"xlsx",
|
||||||
|
"ppt",
|
||||||
|
"pptx",
|
||||||
|
"txt",
|
||||||
|
"md",
|
||||||
|
"json",
|
||||||
|
"csv",
|
||||||
|
"xml",
|
||||||
|
"html",
|
||||||
|
"css",
|
||||||
|
"js",
|
||||||
|
"py",
|
||||||
|
"sql",
|
||||||
|
"rs",
|
||||||
|
"toml",
|
||||||
|
"yml",
|
||||||
|
"yaml",
|
||||||
|
"ini",
|
||||||
|
"conf",
|
||||||
|
"config",
|
||||||
|
"log",
|
||||||
|
"csv",
|
||||||
|
"tsv",
|
||||||
|
"java",
|
||||||
|
"cs",
|
||||||
|
"csproj",
|
||||||
|
"scss",
|
||||||
|
"less",
|
||||||
|
"sass",
|
||||||
|
"json",
|
||||||
|
"lock",
|
||||||
|
"lock.json",
|
||||||
|
"jsonl",
|
||||||
|
]
|
||||||
|
|
||||||
async def get_drive_name_by_extension(self, extension):
|
async def get_drive_name_by_extension(self, extension):
|
||||||
if extension.startswith("."):
|
if extension.startswith("."):
|
||||||
@ -26,54 +102,52 @@ class DriveService(BaseService):
|
|||||||
return "Documents"
|
return "Documents"
|
||||||
return "My Drive"
|
return "My Drive"
|
||||||
|
|
||||||
async def get_drive_by_extension(self,user_uid, extension):
|
async def get_drive_by_extension(self, user_uid, extension):
|
||||||
name = await self.get_drive_name_by_extension(extension)
|
name = await self.get_drive_name_by_extension(extension)
|
||||||
return await self.get_or_create(user_uid=user_uid,name=name)
|
return await self.get_or_create(user_uid=user_uid, name=name)
|
||||||
|
|
||||||
async def get_by_user(self, user_uid,name=None):
|
async def get_by_user(self, user_uid, name=None):
|
||||||
kwargs = dict(
|
kwargs = {"user_uid": user_uid}
|
||||||
user_uid = user_uid
|
|
||||||
)
|
|
||||||
async for model in self.find(**kwargs):
|
async for model in self.find(**kwargs):
|
||||||
if not name:
|
if not name:
|
||||||
yield model
|
|
||||||
elif model['name'] == name:
|
|
||||||
yield model
|
yield model
|
||||||
elif not model['name'] and name == 'My Drive':
|
elif model["name"] == name:
|
||||||
model['name'] = 'My Drive'
|
yield model
|
||||||
|
elif not model["name"] and name == "My Drive":
|
||||||
|
model["name"] = "My Drive"
|
||||||
await self.save(model)
|
await self.save(model)
|
||||||
yield model
|
yield model
|
||||||
|
|
||||||
async def get_or_create(self, user_uid,name=None,extensions=None):
|
async def get_or_create(self, user_uid, name=None, extensions=None):
|
||||||
kwargs = dict(user_uid=user_uid)
|
kwargs = {"user_uid": user_uid}
|
||||||
if name:
|
if name:
|
||||||
kwargs['name'] = name
|
kwargs["name"] = name
|
||||||
async for model in self.get_by_user(**kwargs):
|
async for model in self.get_by_user(**kwargs):
|
||||||
return model
|
return model
|
||||||
|
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
model['user_uid'] = user_uid
|
model["user_uid"] = user_uid
|
||||||
model['name'] = name
|
model["name"] = name
|
||||||
await self.save(model)
|
await self.save(model)
|
||||||
return model
|
return model
|
||||||
|
|
||||||
async def prepare_default_drives(self):
|
async def prepare_default_drives(self):
|
||||||
async for drive_item in self.services.drive_item.find():
|
async for drive_item in self.services.drive_item.find():
|
||||||
extension = drive_item.extension
|
extension = drive_item.extension
|
||||||
drive = await self.get_drive_by_extension(drive_item['user_uid'],extension)
|
drive = await self.get_drive_by_extension(drive_item["user_uid"], extension)
|
||||||
if not drive_item['drive_uid'] == drive['uid']:
|
if not drive_item["drive_uid"] == drive["uid"]:
|
||||||
drive_item['drive_uid'] = drive['uid']
|
drive_item["drive_uid"] = drive["uid"]
|
||||||
await self.services.drive_item.save(drive_item)
|
await self.services.drive_item.save(drive_item)
|
||||||
|
|
||||||
async def prepare_default_drives_for_user(self, user_uid):
|
async def prepare_default_drives_for_user(self, user_uid):
|
||||||
await self.get_or_create(user_uid=user_uid,name="My Drive")
|
await self.get_or_create(user_uid=user_uid, name="My Drive")
|
||||||
await self.get_or_create(user_uid=user_uid,name="Shared Drive")
|
await self.get_or_create(user_uid=user_uid, name="Shared Drive")
|
||||||
await self.get_or_create(user_uid=user_uid,name="Pictures")
|
await self.get_or_create(user_uid=user_uid, name="Pictures")
|
||||||
await self.get_or_create(user_uid=user_uid,name="Videos")
|
await self.get_or_create(user_uid=user_uid, name="Videos")
|
||||||
await self.get_or_create(user_uid=user_uid,name="Archives")
|
await self.get_or_create(user_uid=user_uid, name="Archives")
|
||||||
await self.get_or_create(user_uid=user_uid,name="Documents")
|
await self.get_or_create(user_uid=user_uid, name="Documents")
|
||||||
|
|
||||||
async def prepare_all(self):
|
async def prepare_all(self):
|
||||||
await self.prepare_default_drives()
|
await self.prepare_default_drives()
|
||||||
async for user in self.services.user.find():
|
async for user in self.services.user.find():
|
||||||
await self.prepare_default_drives_for_user(user['uid'])
|
await self.prepare_default_drives_for_user(user["uid"])
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
from snek.system.service import BaseService
|
from snek.system.service import BaseService
|
||||||
|
|
||||||
|
|
||||||
class DriveItemService(BaseService):
|
class DriveItemService(BaseService):
|
||||||
|
|
||||||
mapper_name = "drive_item"
|
mapper_name = "drive_item"
|
||||||
|
|
||||||
async def create(self, drive_uid, name, path, type_,size):
|
async def create(self, drive_uid, name, path, type_, size):
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
model['drive_uid'] = drive_uid
|
model["drive_uid"] = drive_uid
|
||||||
model['name'] = name
|
model["name"] = name
|
||||||
model['path'] = str(path)
|
model["path"] = str(path)
|
||||||
model['extension'] = str(name).split(".")[-1]
|
model["extension"] = str(name).split(".")[-1]
|
||||||
model['file_type'] = type_
|
model["file_type"] = type_
|
||||||
model['file_size'] = size
|
model["file_size"] = size
|
||||||
if await self.save(model):
|
if await self.save(model):
|
||||||
return model
|
return model
|
||||||
errors = await model.errors
|
errors = await model.errors
|
||||||
raise Exception(f"Failed to create drive item: {errors}.")
|
raise Exception(f"Failed to create drive item: {errors}.")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from snek.system.service import BaseService
|
|
||||||
from snek.system.model import now
|
from snek.system.model import now
|
||||||
|
from snek.system.service import BaseService
|
||||||
|
|
||||||
|
|
||||||
class NotificationService(BaseService):
|
class NotificationService(BaseService):
|
||||||
mapper_name = "notification"
|
mapper_name = "notification"
|
||||||
@ -7,13 +8,16 @@ class NotificationService(BaseService):
|
|||||||
async def mark_as_read(self, user_uid, channel_message_uid):
|
async def mark_as_read(self, user_uid, channel_message_uid):
|
||||||
model = await self.get(user_uid, object_uid=channel_message_uid)
|
model = await self.get(user_uid, object_uid=channel_message_uid)
|
||||||
if not model:
|
if not model:
|
||||||
return False
|
return False
|
||||||
model['read_at'] = now()
|
model["read_at"] = now()
|
||||||
await self.save(model)
|
await self.save(model)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_unread_stats(self,user_uid):
|
async def get_unread_stats(self, user_uid):
|
||||||
records = await self.query("SELECT object_type, COUNT(*) as count FROM notification WHERE user_uid=:user_uid AND read_at IS NULL GROUP BY object_type",dict(user_uid=user_uid))
|
await self.query(
|
||||||
|
"SELECT object_type, COUNT(*) as count FROM notification WHERE user_uid=:user_uid AND read_at IS NULL GROUP BY object_type",
|
||||||
|
{"user_uid": user_uid},
|
||||||
|
)
|
||||||
|
|
||||||
async def create(self, object_uid, object_type, user_uid, message):
|
async def create(self, object_uid, object_type, user_uid, message):
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
@ -37,10 +41,10 @@ class NotificationService(BaseService):
|
|||||||
is_muted=False,
|
is_muted=False,
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
):
|
):
|
||||||
if not channel_member['new_count']:
|
if not channel_member["new_count"]:
|
||||||
channel_member['new_count'] = 0
|
channel_member["new_count"] = 0
|
||||||
channel_member['new_count'] += 1
|
channel_member["new_count"] += 1
|
||||||
|
|
||||||
usr = await self.services.user.get(uid=channel_member["user_uid"])
|
usr = await self.services.user.get(uid=channel_member["user_uid"])
|
||||||
if not usr:
|
if not usr:
|
||||||
continue
|
continue
|
||||||
@ -55,7 +59,7 @@ class NotificationService(BaseService):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await self.save(model)
|
await self.save(model)
|
||||||
except Exception as ex:
|
except Exception:
|
||||||
raise Exception(f"Failed to create notification: {model.errors}.")
|
raise Exception(f"Failed to create notification: {model.errors}.")
|
||||||
|
|
||||||
self.app.db.commit()
|
self.app.db.commit()
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
from snek.model.user import UserModel
|
from snek.model.user import UserModel
|
||||||
|
|
||||||
|
|
||||||
from snek.system.service import BaseService
|
from snek.system.service import BaseService
|
||||||
|
|
||||||
|
|
||||||
@ -9,28 +7,27 @@ class SocketService(BaseService):
|
|||||||
class Socket:
|
class Socket:
|
||||||
def __init__(self, ws, user: UserModel):
|
def __init__(self, ws, user: UserModel):
|
||||||
self.ws = ws
|
self.ws = ws
|
||||||
self.is_connected = True
|
self.is_connected = True
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
async def send_json(self, data):
|
async def send_json(self, data):
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
await self.ws.send_json(data)
|
await self.ws.send_json(data)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(ex,flush=True)
|
print(ex, flush=True)
|
||||||
self.is_connected = False
|
self.is_connected = False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
await self.ws.close()
|
await self.ws.close()
|
||||||
self.is_connected = False
|
self.is_connected = False
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
@ -42,32 +39,31 @@ class SocketService(BaseService):
|
|||||||
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
|
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
|
||||||
self.sockets.add(s)
|
self.sockets.add(s)
|
||||||
if not self.users.get(user_uid):
|
if not self.users.get(user_uid):
|
||||||
self.users[user_uid] = set()
|
self.users[user_uid] = set()
|
||||||
self.users[user_uid].add(s)
|
self.users[user_uid].add(s)
|
||||||
|
|
||||||
async def subscribe(self, ws,channel_uid, user_uid):
|
async def subscribe(self, ws, channel_uid, user_uid):
|
||||||
return
|
return
|
||||||
if not channel_uid in self.subscriptions:
|
if channel_uid not in self.subscriptions:
|
||||||
self.subscriptions[channel_uid] = set()
|
self.subscriptions[channel_uid] = set()
|
||||||
s = self.Socket(ws,await self.app.services.user.get(uid=user_uid))
|
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
|
||||||
self.subscriptions[channel_uid].add(s)
|
self.subscriptions[channel_uid].add(s)
|
||||||
|
|
||||||
async def send_to_user(self, user_uid, message):
|
async def send_to_user(self, user_uid, message):
|
||||||
count = 0
|
count = 0
|
||||||
for s in self.users.get(user_uid,[]):
|
for s in self.users.get(user_uid, []):
|
||||||
if await s.send_json(message):
|
if await s.send_json(message):
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
async def broadcast(self, channel_uid, message):
|
async def broadcast(self, channel_uid, message):
|
||||||
count = 0
|
async for channel_member in self.app.services.channel_member.find(
|
||||||
async for channel_member in self.app.services.channel_member.find(channel_uid=channel_uid):
|
channel_uid=channel_uid
|
||||||
await self.send_to_user(channel_member["user_uid"],message)
|
):
|
||||||
return True
|
await self.send_to_user(channel_member["user_uid"], message)
|
||||||
|
return True
|
||||||
|
|
||||||
async def delete(self, ws):
|
async def delete(self, ws):
|
||||||
for s in [sock for sock in self.sockets if sock.ws == ws]:
|
for s in [sock for sock in self.sockets if sock.ws == ws]:
|
||||||
await s.close()
|
await s.close()
|
||||||
self.sockets.remove(s)
|
self.sockets.remove(s)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
|
import pathlib
|
||||||
|
|
||||||
from snek.system import security
|
from snek.system import security
|
||||||
from snek.system.service import BaseService
|
from snek.system.service import BaseService
|
||||||
|
|
||||||
|
|
||||||
class UserService(BaseService):
|
class UserService(BaseService):
|
||||||
mapper_name = "user"
|
mapper_name = "user"
|
||||||
|
|
||||||
async def search(self, query, **kwargs):
|
async def search(self, query, **kwargs):
|
||||||
query = query.strip().lower()
|
query = query.strip().lower()
|
||||||
if not query:
|
if not query:
|
||||||
raise []
|
raise []
|
||||||
results = []
|
results = []
|
||||||
async for result in self.find(username=dict(ilike='%' + query + '%'), **kwargs):
|
async for result in self.find(username={"ilike": "%" + query + "%"}, **kwargs):
|
||||||
results.append(result)
|
results.append(result)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@ -23,17 +25,32 @@ class UserService(BaseService):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def save(self, user):
|
async def save(self, user):
|
||||||
if not user['color']:
|
if not user["color"]:
|
||||||
user['color'] = await self.services.util.random_light_hex_color()
|
user["color"] = await self.services.util.random_light_hex_color()
|
||||||
return await super().save(user)
|
return await super().save(user)
|
||||||
|
|
||||||
|
async def authenticate(self, username, password):
|
||||||
|
print(username, password, flush=True)
|
||||||
|
success = await self.validate_login(username, password)
|
||||||
|
print(success, flush=True)
|
||||||
|
if not success:
|
||||||
|
return None
|
||||||
|
|
||||||
|
model = await self.get(username=username, deleted_at=None)
|
||||||
|
return model
|
||||||
|
|
||||||
|
async def get_home_folder(self, user_uid):
|
||||||
|
folder = pathlib.Path(f"./drive/{user_uid}")
|
||||||
|
if not folder.exists():
|
||||||
|
folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
return folder
|
||||||
|
|
||||||
async def register(self, email, username, password):
|
async def register(self, email, username, password):
|
||||||
if await self.exists(username=username):
|
if await self.exists(username=username):
|
||||||
raise Exception("User already exists.")
|
raise Exception("User already exists.")
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
model["nick"] = username
|
model["nick"] = username
|
||||||
model['color'] = await self.services.util.random_light_hex_color()
|
model["color"] = await self.services.util.random_light_hex_color()
|
||||||
model.email.value = email
|
model.email.value = email
|
||||||
model.username.value = username
|
model.username.value = username
|
||||||
model.password.value = await security.hash(password)
|
model.password.value = await security.hash(password)
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
from snek.system.service import BaseService
|
from snek.system.service import BaseService
|
||||||
|
|
||||||
|
|
||||||
class UtilService(BaseService):
|
class UtilService(BaseService):
|
||||||
|
|
||||||
async def random_light_hex_color(self):
|
async def random_light_hex_color(self):
|
||||||
|
|
||||||
r = random.randint(128, 255)
|
r = random.randint(128, 255)
|
||||||
g = random.randint(128, 255)
|
g = random.randint(128, 255)
|
||||||
b = random.randint(128, 255)
|
b = random.randint(128, 255)
|
||||||
|
|
||||||
return "#{:02x}{:02x}{:02x}".format(r, g, b)
|
return f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
@ -20,13 +20,13 @@ class Cache:
|
|||||||
try:
|
try:
|
||||||
self.lru.pop(self.lru.index(args))
|
self.lru.pop(self.lru.index(args))
|
||||||
except:
|
except:
|
||||||
#print("Cache miss!", args, flush=True)
|
# print("Cache miss!", args, flush=True)
|
||||||
return None
|
return None
|
||||||
self.lru.insert(0, args)
|
self.lru.insert(0, args)
|
||||||
while len(self.lru) > self.max_items:
|
while len(self.lru) > self.max_items:
|
||||||
self.cache.pop(self.lru[-1])
|
self.cache.pop(self.lru[-1])
|
||||||
self.lru.pop()
|
self.lru.pop()
|
||||||
#print("Cache hit!", args, flush=True)
|
# print("Cache hit!", args, flush=True)
|
||||||
return self.cache[args]
|
return self.cache[args]
|
||||||
|
|
||||||
def json_default(self, value):
|
def json_default(self, value):
|
||||||
@ -61,7 +61,7 @@ class Cache:
|
|||||||
|
|
||||||
if is_new:
|
if is_new:
|
||||||
self.version += 1
|
self.version += 1
|
||||||
#print(f"Cache store! {len(self.lru)} items. New version:", self.version, flush=True)
|
# print(f"Cache store! {len(self.lru)} items. New version:", self.version, flush=True)
|
||||||
|
|
||||||
async def delete(self, args):
|
async def delete(self, args):
|
||||||
if args in self.cache:
|
if args in self.cache:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2
|
# Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2
|
||||||
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from html import escape
|
|
||||||
from app.cache import time_cache_async
|
from app.cache import time_cache_async
|
||||||
from mistune import HTMLRenderer, Markdown
|
from mistune import HTMLRenderer, Markdown
|
||||||
from pygments import highlight
|
from pygments import highlight
|
||||||
@ -24,21 +24,21 @@ class MarkdownRenderer(HTMLRenderer):
|
|||||||
def _escape(self, str):
|
def _escape(self, str):
|
||||||
return str ##escape(str)
|
return str ##escape(str)
|
||||||
|
|
||||||
def get_lexer(self, lang, default='bash'):
|
def get_lexer(self, lang, default="bash"):
|
||||||
try:
|
try:
|
||||||
return get_lexer_by_name(lang, stripall=True)
|
return get_lexer_by_name(lang, stripall=True)
|
||||||
except:
|
except:
|
||||||
return get_lexer_by_name(default, stripall=True)
|
return get_lexer_by_name(default, stripall=True)
|
||||||
|
|
||||||
def block_code(self, code, lang=None, info=None):
|
def block_code(self, code, lang=None, info=None):
|
||||||
if not lang:
|
if not lang:
|
||||||
lang = info
|
lang = info
|
||||||
if not lang:
|
if not lang:
|
||||||
lang = 'bash'
|
lang = "bash"
|
||||||
lexer = self.get_lexer(lang)
|
lexer = self.get_lexer(lang)
|
||||||
formatter = html.HtmlFormatter(lineseparator="<br>")
|
formatter = html.HtmlFormatter(lineseparator="<br>")
|
||||||
result = highlight(code, lexer, formatter)
|
result = highlight(code, lexer, formatter)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
markdown_string = self.app.template_path.joinpath(self.template).read_text()
|
markdown_string = self.app.template_path.joinpath(self.template).read_text()
|
||||||
|
@ -20,7 +20,9 @@ async def no_cors_middleware(request, handler):
|
|||||||
async def cors_allow_middleware(request, handler):
|
async def cors_allow_middleware(request, handler):
|
||||||
response = await handler(request)
|
response = await handler(request)
|
||||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, PUT, DELETE"
|
response.headers["Access-Control-Allow-Methods"] = (
|
||||||
|
"GET, POST, OPTIONS, PUT, DELETE, MOVE, COPY, HEAD, LOCK, UNLOCK, PATCH, PROPFIND"
|
||||||
|
)
|
||||||
response.headers["Access-Control-Allow-Headers"] = "*"
|
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||||||
return response
|
return response
|
||||||
@ -28,17 +30,12 @@ async def cors_allow_middleware(request, handler):
|
|||||||
|
|
||||||
@web.middleware
|
@web.middleware
|
||||||
async def cors_middleware(request, handler):
|
async def cors_middleware(request, handler):
|
||||||
if request.method == "OPTIONS":
|
if request.headers.get("Allow"):
|
||||||
response = web.Response()
|
return await handler(request)
|
||||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
|
||||||
response.headers["Access-Control-Allow-Methods"] = (
|
|
||||||
"GET, POST, PUT, DELETE, OPTIONS"
|
|
||||||
)
|
|
||||||
response.headers["Access-Control-Allow-Headers"] = "*"
|
|
||||||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
|
||||||
return response
|
|
||||||
|
|
||||||
response = await handler(request)
|
response = await handler(request)
|
||||||
|
if request.headers.get("Allow"):
|
||||||
|
return response
|
||||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
|
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
|
||||||
response.headers["Access-Control-Allow-Headers"] = "*"
|
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
import cProfile
|
import cProfile
|
||||||
import pstats
|
import pstats
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
profiler = None
|
profiler = None
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
|
||||||
@web.middleware
|
@web.middleware
|
||||||
async def profile_middleware(request, handler):
|
async def profile_middleware(request, handler):
|
||||||
global profiler
|
global profiler
|
||||||
if not profiler:
|
if not profiler:
|
||||||
profiler = cProfile.Profile()
|
profiler = cProfile.Profile()
|
||||||
profiler.enable()
|
profiler.enable()
|
||||||
response = await handler(request)
|
response = await handler(request)
|
||||||
profiler.disable()
|
profiler.disable()
|
||||||
stats = pstats.Stats(profiler, stream=sys.stdout)
|
stats = pstats.Stats(profiler, stream=sys.stdout)
|
||||||
stats.sort_stats('cumulative')
|
stats.sort_stats("cumulative")
|
||||||
stats.print_stats()
|
stats.print_stats()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
async def profiler_handler(request):
|
async def profiler_handler(request):
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
stats = pstats.Stats(profiler, stream=output)
|
stats = pstats.Stats(profiler, stream=output)
|
||||||
@ -27,17 +30,17 @@ async def profiler_handler(request):
|
|||||||
stats.print_stats()
|
stats.print_stats()
|
||||||
return web.Response(text=output.getvalue())
|
return web.Response(text=output.getvalue())
|
||||||
|
|
||||||
|
|
||||||
class Profiler:
|
class Profiler:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
global profiler
|
global profiler
|
||||||
if profiler is None:
|
if profiler is None:
|
||||||
profiler = cProfile.Profile()
|
profiler = cProfile.Profile()
|
||||||
self.profiler = profiler
|
self.profiler = profiler
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
self.profiler.enable()
|
self.profiler.enable()
|
||||||
|
|
||||||
async def __aexit__(self, *args, **kwargs):
|
async def __aexit__(self, *args, **kwargs):
|
||||||
self.profiler.disable()
|
self.profiler.disable()
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class BaseService:
|
|||||||
raise Exception(f"Couldn't save model. Errors: f{errors}")
|
raise Exception(f"Couldn't save model. Errors: f{errors}")
|
||||||
|
|
||||||
async def find(self, **kwargs):
|
async def find(self, **kwargs):
|
||||||
if not "_limit" in kwargs or int(kwargs.get("_limit")) > 30:
|
if "_limit" not in kwargs or int(kwargs.get("_limit")) > 30:
|
||||||
kwargs["_limit"] = 60
|
kwargs["_limit"] = 60
|
||||||
async for model in self.mapper.find(**kwargs):
|
async for model in self.mapper.find(**kwargs):
|
||||||
yield model
|
yield model
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
|
import re
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
import re
|
|
||||||
import emoji
|
|
||||||
|
|
||||||
|
import emoji
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
from jinja2 import TemplateSyntaxError, nodes
|
from jinja2 import TemplateSyntaxError, nodes
|
||||||
from jinja2.ext import Extension
|
from jinja2.ext import Extension
|
||||||
from jinja2.nodes import Const
|
from jinja2.nodes import Const
|
||||||
|
|
||||||
emoji.EMOJI_DATA['<img src="/emoji/snek1.gif" />'] = {"en": ":snek1:","status":2,"E":0.6, "alias":[":snek1:"]}
|
emoji.EMOJI_DATA['<img src="/emoji/snek1.gif" />'] = {
|
||||||
|
"en": ":snek1:",
|
||||||
|
"status": 2,
|
||||||
|
"E": 0.6,
|
||||||
|
"alias": [":snek1:"],
|
||||||
|
}
|
||||||
|
|
||||||
emoji.EMOJI_DATA["""⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
emoji.EMOJI_DATA[
|
||||||
|
"""⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⡴⠶⢶⣞⣛⣛⡳⣳⠶⣶⡶⢶⢶⣦⣤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⡴⠶⢶⣞⣛⣛⡳⣳⠶⣶⡶⢶⢶⣦⣤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⠶⠚⣫⣴⣬⠐⣶⣿⣿⣏⣽⣿⣿⣇⢿⣯⣿⣿⣻⣿⣿⣾⣮⣹⣿⢶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⠶⠚⣫⣴⣬⠐⣶⣿⣿⣏⣽⣿⣿⣇⢿⣯⣿⣿⣻⣿⣿⣾⣮⣹⣿⢶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⢾⣻⣽⣾⡇⢡⣿⣿⣇⡟⣿⣿⣿⣼⣿⣿⣿⣿⢸⣿⣟⣿⣷⢻⣿⣿⣿⣷⡽⣧⣹⡻⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⢾⣻⣽⣾⡇⢡⣿⣿⣇⡟⣿⣿⣿⣼⣿⣿⣿⣿⢸⣿⣟⣿⣷⢻⣿⣿⣿⣷⡽⣧⣹⡻⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
@ -64,62 +70,91 @@ emoji.EMOJI_DATA["""⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣟⣽⣿⣯⣿⣣⣿⣿⣿⣿⣿⡄⢿⣿⣿⣿⣿⣿⣿⡿⠟⣡⣾⣿⣿⣿⣿⣿⣿⡏⣰⣿⣿⡿⢹⡇⣾⠀⣿⣿⣾⣿⡇⠀⠈⢷⢰⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣽⣻⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣦⣀⡀⠈⠛⠻⢿⣎⢿⣿⣿⣿⣿⣿⣿⣷⣄⠀⣿⡇⠘⣧⠀⠀⠀⠀⠀⠀⠀⠀
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣟⣽⣿⣯⣿⣣⣿⣿⣿⣿⣿⡄⢿⣿⣿⣿⣿⣿⣿⡿⠟⣡⣾⣿⣿⣿⣿⣿⣿⡏⣰⣿⣿⡿⢹⡇⣾⠀⣿⣿⣾⣿⡇⠀⠈⢷⢰⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣽⣻⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣦⣀⡀⠈⠛⠻⢿⣎⢿⣿⣿⣿⣿⣿⣿⣷⣄⠀⣿⡇⠘⣧⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⢟⣾⣿⣻⡿⣵⣿⣿⣿⣿⣟⣾⣯⠌⠛⠛⠛⠛⠉⠁⢠⣾⣿⣿⣿⣿⣿⣿⡿⠏⣰⣿⣿⡿⠁⢸⡇⢿⠀⣿⣿⣿⣿⡇⠀⠠⠈⠂⢃⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣻⡿⣿⣿⣿⣿⣿⣿⣿⡇⡄⠾⣲⣾⣿⣌⢿⣿⣿⣿⣿⣿⣿⣿⣧⡙⠇⠀⢻⡄⠀⠀⠀⠀⠀⠀⠀
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⢟⣾⣿⣻⡿⣵⣿⣿⣿⣿⣟⣾⣯⠌⠛⠛⠛⠛⠉⠁⢠⣾⣿⣿⣿⣿⣿⣿⡿⠏⣰⣿⣿⡿⠁⢸⡇⢿⠀⣿⣿⣿⣿⡇⠀⠠⠈⠂⢃⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣻⡿⣿⣿⣿⣿⣿⣿⣿⡇⡄⠾⣲⣾⣿⣌⢿⣿⣿⣿⣿⣿⣿⣿⣧⡙⠇⠀⢻⡄⠀⠀⠀⠀⠀⠀⠀
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠚⠛⠛⠛⠚⠛⠛⠛⠛⠛⠚⠛⠛⠓⠚⠒⠛⠒⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠚⠛⠛⠛⠛⠓⠛⠛⠛⠓⠛⠛⠛⠛⠓⠚⠛⠛⠓⠚⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠚⠛⠛⠛⠛⠛⠛⠓⠛⠚⠛⠛⠛⠛⠓⠛⠛⠛⠛⠛⠛⠛⠛⠛⠒⠚⠚⠁⠀⠀⠀⠀⠀⠀⠀
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠚⠛⠛⠛⠚⠛⠛⠛⠛⠛⠚⠛⠛⠓⠚⠒⠛⠒⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠚⠛⠛⠛⠛⠓⠛⠛⠛⠓⠛⠛⠛⠛⠓⠚⠛⠛⠓⠚⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠚⠛⠛⠛⠛⠛⠛⠓⠛⠚⠛⠛⠛⠛⠓⠛⠛⠛⠛⠛⠛⠛⠛⠛⠒⠚⠚⠁⠀⠀⠀⠀⠀⠀⠀
|
||||||
"""] = {"en": ":a1:","status":2,"E":0.6, "alias":[":a1:"]}
|
"""
|
||||||
|
] = {"en": ":a1:", "status": 2, "E": 0.6, "alias": [":a1:"]}
|
||||||
|
|
||||||
|
|
||||||
def set_link_target_blank(text):
|
def set_link_target_blank(text):
|
||||||
soup = BeautifulSoup(text, 'html.parser')
|
soup = BeautifulSoup(text, "html.parser")
|
||||||
|
|
||||||
for element in soup.find_all("a"):
|
for element in soup.find_all("a"):
|
||||||
element.attrs['target'] = '_blank'
|
element.attrs["target"] = "_blank"
|
||||||
element.attrs['rel'] = 'noopener noreferrer'
|
element.attrs["rel"] = "noopener noreferrer"
|
||||||
element.attrs['referrerpolicy'] = 'no-referrer'
|
element.attrs["referrerpolicy"] = "no-referrer"
|
||||||
element.attrs['href'] = element.attrs['href'].strip(".").strip(",")
|
element.attrs["href"] = element.attrs["href"].strip(".").strip(",")
|
||||||
|
|
||||||
return str(soup)
|
return str(soup)
|
||||||
|
|
||||||
|
|
||||||
def embed_youtube(text):
|
def embed_youtube(text):
|
||||||
soup = BeautifulSoup(text, 'html.parser')
|
soup = BeautifulSoup(text, "html.parser")
|
||||||
for element in soup.find_all("a"):
|
for element in soup.find_all("a"):
|
||||||
if element.attrs['href'].startswith("https://www.you") and "?v=" in element.attrs["href"]:
|
if (
|
||||||
|
element.attrs["href"].startswith("https://www.you")
|
||||||
|
and "?v=" in element.attrs["href"]
|
||||||
|
):
|
||||||
video_name = element.attrs["href"].split("?v=")[1].split("&")[0]
|
video_name = element.attrs["href"].split("?v=")[1].split("&")[0]
|
||||||
embed_template = f'<iframe width="560" height="315" src="https://www.youtube.com/embed/{video_name}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
|
embed_template = f'<iframe width="560" height="315" src="https://www.youtube.com/embed/{video_name}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
|
||||||
element.replace_with(BeautifulSoup(embed_template, 'html.parser'))
|
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
|
||||||
return str(soup)
|
return str(soup)
|
||||||
|
|
||||||
|
|
||||||
def embed_image(text):
|
def embed_image(text):
|
||||||
soup = BeautifulSoup(text, 'html.parser')
|
soup = BeautifulSoup(text, "html.parser")
|
||||||
for element in soup.find_all("a"):
|
for element in soup.find_all("a"):
|
||||||
for extension in [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".tiff", ".ico", ".heif"]:
|
for extension in [
|
||||||
if extension in element.attrs['href'].lower():
|
".png",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".gif",
|
||||||
|
".webp",
|
||||||
|
".svg",
|
||||||
|
".bmp",
|
||||||
|
".tiff",
|
||||||
|
".ico",
|
||||||
|
".heif",
|
||||||
|
]:
|
||||||
|
if extension in element.attrs["href"].lower():
|
||||||
embed_template = f'<img src="{element.attrs["href"]}" title="{element.attrs["href"]}" alt="{element.attrs["href"]}" />'
|
embed_template = f'<img src="{element.attrs["href"]}" title="{element.attrs["href"]}" alt="{element.attrs["href"]}" />'
|
||||||
element.replace_with(BeautifulSoup(embed_template, 'html.parser'))
|
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
|
||||||
return str(soup)
|
return str(soup)
|
||||||
|
|
||||||
|
|
||||||
def embed_media(text):
|
def embed_media(text):
|
||||||
soup = BeautifulSoup(text, 'html.parser')
|
soup = BeautifulSoup(text, "html.parser")
|
||||||
for element in soup.find_all("a"):
|
for element in soup.find_all("a"):
|
||||||
for extension in [".mp4", ".mp3", ".wav", ".ogg", ".webm", ".flac", ".aac",".mpg",".avi",".wmv"]:
|
for extension in [
|
||||||
if extension in element.attrs['href'].lower():
|
".mp4",
|
||||||
|
".mp3",
|
||||||
|
".wav",
|
||||||
|
".ogg",
|
||||||
|
".webm",
|
||||||
|
".flac",
|
||||||
|
".aac",
|
||||||
|
".mpg",
|
||||||
|
".avi",
|
||||||
|
".wmv",
|
||||||
|
]:
|
||||||
|
if extension in element.attrs["href"].lower():
|
||||||
embed_template = f'<video controls> <source src="{element.attrs["href"]}">Your browser does not support the video tag.</video>'
|
embed_template = f'<video controls> <source src="{element.attrs["href"]}">Your browser does not support the video tag.</video>'
|
||||||
element.replace_with(BeautifulSoup(embed_template, 'html.parser'))
|
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
|
||||||
return str(soup)
|
return str(soup)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def linkify_https(text):
|
def linkify_https(text):
|
||||||
if not "https://" in text:
|
if "https://" not in text:
|
||||||
return text
|
return text
|
||||||
url_pattern = r'(?<!["\'])\bhttps://[^\s<>()]+(?<!\.)'
|
url_pattern = r'(?<!["\'])\bhttps://[^\s<>()]+(?<!\.)'
|
||||||
|
|
||||||
soup = BeautifulSoup(text, 'html.parser')
|
soup = BeautifulSoup(text, "html.parser")
|
||||||
|
|
||||||
for element in soup.find_all(text=True):
|
for element in soup.find_all(text=True):
|
||||||
parent = element.parent
|
parent = element.parent
|
||||||
if parent.name in ['a', 'script', 'style']:
|
if parent.name in ["a", "script", "style"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_text = re.sub(url_pattern, r'<a href="\g<0>">\g<0></a>', element)
|
new_text = re.sub(url_pattern, r'<a href="\g<0>">\g<0></a>', element)
|
||||||
element.replace_with(BeautifulSoup(new_text, 'html.parser'))
|
element.replace_with(BeautifulSoup(new_text, "html.parser"))
|
||||||
|
|
||||||
return set_link_target_blank(str(soup))
|
return set_link_target_blank(str(soup))
|
||||||
|
|
||||||
@ -140,8 +175,7 @@ class EmojiExtension(Extension):
|
|||||||
).set_lineno(line_number)
|
).set_lineno(line_number)
|
||||||
|
|
||||||
def _to_html(self, md_file, caller):
|
def _to_html(self, md_file, caller):
|
||||||
return emoji.emojize(caller(),language='alias')
|
return emoji.emojize(caller(), language="alias")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LinkifyExtension(Extension):
|
class LinkifyExtension(Extension):
|
||||||
@ -170,6 +204,7 @@ class LinkifyExtension(Extension):
|
|||||||
result = embed_youtube(result)
|
result = embed_youtube(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class PythonExtension(Extension):
|
class PythonExtension(Extension):
|
||||||
tags = {"py3"}
|
tags = {"py3"}
|
||||||
|
|
||||||
@ -186,26 +221,26 @@ class PythonExtension(Extension):
|
|||||||
).set_lineno(line_number)
|
).set_lineno(line_number)
|
||||||
|
|
||||||
def _to_html(self, md_file, caller):
|
def _to_html(self, md_file, caller):
|
||||||
|
|
||||||
def fn(source):
|
def fn(source):
|
||||||
import subprocess
|
import subprocess
|
||||||
import subprocess
|
|
||||||
import pathlib
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import requests
|
|
||||||
def system(command):
|
def system(command):
|
||||||
if isinstance(command):
|
if isinstance(command):
|
||||||
command = command.split(" ")
|
command = command.split(" ")
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
stdout = StringIO()
|
stdout = StringIO()
|
||||||
subprocess.run(command,stderr=stdout,stdout=stdout,text=True)
|
subprocess.run(command, stderr=stdout, stdout=stdout, text=True)
|
||||||
return stdout.getvalue()
|
return stdout.getvalue()
|
||||||
|
|
||||||
to_write = []
|
to_write = []
|
||||||
|
|
||||||
def render(text):
|
def render(text):
|
||||||
global to_write
|
global to_write
|
||||||
to_write.append(text)
|
to_write.append(text)
|
||||||
|
|
||||||
exec(source)
|
exec(source)
|
||||||
return "".join(to_write)
|
return "".join(to_write)
|
||||||
|
|
||||||
return str(fn(caller()))
|
return str(fn(caller()))
|
||||||
|
@ -1,30 +1,27 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
|
||||||
import aiohttp.web
|
|
||||||
import os
|
import os
|
||||||
import pty
|
import pty
|
||||||
import shlex
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import pathlib
|
|
||||||
|
|
||||||
commands = {
|
commands = {
|
||||||
'alpine': 'docker run -it alpine /bin/sh',
|
"alpine": "docker run -it alpine /bin/sh",
|
||||||
'r': 'docker run -v /usr/local/bin:/usr/local/bin -it ubuntu:latest run.sh',
|
"r": "docker run -v /usr/local/bin:/usr/local/bin -it ubuntu:latest run.sh",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TerminalSession:
|
class TerminalSession:
|
||||||
def __init__(self,command):
|
def __init__(self, command):
|
||||||
self.master, self.slave = pty.openpty()
|
self.master, self.slave = pty.openpty()
|
||||||
self.sockets =[]
|
self.sockets = []
|
||||||
self.history = b''
|
self.history = b""
|
||||||
self.history_size = 1024*20
|
self.history_size = 1024 * 20
|
||||||
self.process = subprocess.Popen(
|
self.process = subprocess.Popen(
|
||||||
command.split(" "),
|
command.split(" "),
|
||||||
stdin=self.slave,
|
stdin=self.slave,
|
||||||
stdout=self.slave,
|
stdout=self.slave,
|
||||||
stderr=self.slave,
|
stderr=self.slave,
|
||||||
bufsize=0,
|
bufsize=0,
|
||||||
universal_newlines=True
|
universal_newlines=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def add_websocket(self, ws):
|
async def add_websocket(self, ws):
|
||||||
@ -35,11 +32,11 @@ class TerminalSession:
|
|||||||
if len(self.sockets) > 1 and self.history:
|
if len(self.sockets) > 1 and self.history:
|
||||||
start = 0
|
start = 0
|
||||||
try:
|
try:
|
||||||
start = self.history.index(b'\n')
|
start = self.history.index(b"\n")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
await ws.send_bytes(self.history[start:])
|
await ws.send_bytes(self.history[start:])
|
||||||
return
|
return
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -48,9 +45,10 @@ class TerminalSession:
|
|||||||
break
|
break
|
||||||
self.history += data
|
self.history += data
|
||||||
if len(self.history) > self.history_size:
|
if len(self.history) > self.history_size:
|
||||||
self.history = self.history[:0-self.history_size]
|
self.history = self.history[: 0 - self.history_size]
|
||||||
try:
|
try:
|
||||||
for ws in self.sockets: await ws.send_bytes(data) # Send raw bytes for ANSI support
|
for ws in self.sockets:
|
||||||
|
await ws.send_bytes(data) # Send raw bytes for ANSI support
|
||||||
except:
|
except:
|
||||||
self.sockets.remove(ws)
|
self.sockets.remove(ws)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -12,9 +12,9 @@ class BaseView(web.View):
|
|||||||
return web.HTTPFound("/")
|
return web.HTTPFound("/")
|
||||||
return await super()._iter()
|
return await super()._iter()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_url(self):
|
def base_url(self):
|
||||||
return str(self.request.url.with_path('').with_query(''))
|
return str(self.request.url.with_path("").with_query(""))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def app(self):
|
def app(self):
|
||||||
|
@ -26,12 +26,14 @@
|
|||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class AboutHTMLView(BaseView):
|
class AboutHTMLView(BaseView):
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
return await self.render_template("about.html")
|
return await self.render_template("about.html")
|
||||||
|
|
||||||
|
|
||||||
class AboutMDView(BaseView):
|
class AboutMDView(BaseView):
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
return await self.render_template("about.md")
|
return await self.render_template("about.md")
|
||||||
|
@ -23,11 +23,14 @@
|
|||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
from multiavatar import multiavatar
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
from multiavatar import multiavatar
|
||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class AvatarView(BaseView):
|
class AvatarView(BaseView):
|
||||||
login_required = False
|
login_required = False
|
||||||
|
|
||||||
@ -36,6 +39,6 @@ class AvatarView(BaseView):
|
|||||||
if uid == "unique":
|
if uid == "unique":
|
||||||
uid = str(uuid.uuid4())
|
uid = str(uuid.uuid4())
|
||||||
avatar = multiavatar.multiavatar(uid, True, None)
|
avatar = multiavatar.multiavatar(uid, True, None)
|
||||||
response = web.Response(text=avatar, content_type='image/svg+xml')
|
response = web.Response(text=avatar, content_type="image/svg+xml")
|
||||||
response.headers['Cache-Control'] = f'public, max-age={1337*42}'
|
response.headers["Cache-Control"] = f"public, max-age={1337*42}"
|
||||||
return response
|
return response
|
||||||
|
@ -1,35 +1,37 @@
|
|||||||
# Written by retoor@molodetz.nl
|
# Written by retoor@molodetz.nl
|
||||||
|
|
||||||
# This code defines two classes, DocsHTMLView and DocsMDView, which are intended to asynchronously render HTML and Markdown templates respectively. Both classes inherit from the BaseView class.
|
# This code defines two classes, DocsHTMLView and DocsMDView, which are intended to asynchronously render HTML and Markdown templates respectively. Both classes inherit from the BaseView class.
|
||||||
|
|
||||||
# Dependencies: BaseView is imported from the "snek.system.view" package.
|
# Dependencies: BaseView is imported from the "snek.system.view" package.
|
||||||
|
|
||||||
# MIT License
|
# MIT License
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
# in the Software without restriction, including without limitation the rights
|
# in the Software without restriction, including without limitation the rights
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class DocsHTMLView(BaseView):
|
class DocsHTMLView(BaseView):
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
return await self.render_template("docs.html")
|
return await self.render_template("docs.html")
|
||||||
|
|
||||||
|
|
||||||
class DocsMDView(BaseView):
|
class DocsMDView(BaseView):
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
return await self.render_template("docs.md")
|
return await self.render_template("docs.md")
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from snek.system.view import BaseView
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class DriveView(BaseView):
|
class DriveView(BaseView):
|
||||||
|
|
||||||
login_required = True
|
login_required = True
|
||||||
@ -14,21 +16,22 @@ class DriveView(BaseView):
|
|||||||
drive_items = []
|
drive_items = []
|
||||||
async for item in drive.items:
|
async for item in drive.items:
|
||||||
record = item.record
|
record = item.record
|
||||||
record['url'] = '/drive.bin/' + record['uid'] + '.' + item.extension
|
record["url"] = "/drive.bin/" + record["uid"] + "." + item.extension
|
||||||
drive_items.append(record)
|
drive_items.append(record)
|
||||||
return web.json_response(drive_items)
|
return web.json_response(drive_items)
|
||||||
|
|
||||||
user = await self.services.user.get(uid=self.session.get("uid"))
|
user = await self.services.user.get(uid=self.session.get("uid"))
|
||||||
|
|
||||||
|
|
||||||
drives = []
|
drives = []
|
||||||
async for drive in self.services.drive.get_by_user(user['uid']):
|
async for drive in self.services.drive.get_by_user(user["uid"]):
|
||||||
record = drive.record
|
record = drive.record
|
||||||
record['items'] = []
|
record["items"] = []
|
||||||
async for item in drive.items:
|
async for item in drive.items:
|
||||||
drive_item_record = item.record
|
drive_item_record = item.record
|
||||||
drive_item_record['url'] = '/drive.bin/' + drive_item_record['uid'] + '.' + item.extension
|
drive_item_record["url"] = (
|
||||||
record['items'].append(item.record)
|
"/drive.bin/" + drive_item_record["uid"] + "." + item.extension
|
||||||
|
)
|
||||||
|
record["items"].append(item.record)
|
||||||
drives.append(record)
|
drives.append(record)
|
||||||
|
|
||||||
return web.json_response(drives)
|
return web.json_response(drives)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class IndexView(BaseView):
|
class IndexView(BaseView):
|
||||||
async def get(self):
|
async def get(self):
|
||||||
return await self.render_template("index.html")
|
return await self.render_template("index.html")
|
||||||
|
@ -7,9 +7,11 @@
|
|||||||
# MIT License
|
# MIT License
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from snek.form.login import LoginForm
|
from snek.form.login import LoginForm
|
||||||
from snek.system.view import BaseFormView
|
from snek.system.view import BaseFormView
|
||||||
|
|
||||||
|
|
||||||
class LoginView(BaseFormView):
|
class LoginView(BaseFormView):
|
||||||
form = LoginForm
|
form = LoginForm
|
||||||
|
|
||||||
@ -18,17 +20,23 @@ class LoginView(BaseFormView):
|
|||||||
return web.HTTPFound("/web.html")
|
return web.HTTPFound("/web.html")
|
||||||
if self.request.path.endswith(".json"):
|
if self.request.path.endswith(".json"):
|
||||||
return await super().get()
|
return await super().get()
|
||||||
return await self.render_template("login.html", {"form": await self.form(app=self.app).to_json()})
|
return await self.render_template(
|
||||||
|
"login.html", {"form": await self.form(app=self.app).to_json()}
|
||||||
|
)
|
||||||
|
|
||||||
async def submit(self, form):
|
async def submit(self, form):
|
||||||
if await form.is_valid:
|
if await form.is_valid:
|
||||||
user = await self.services.user.get(username=form['username'], deleted_at=None)
|
user = await self.services.user.get(
|
||||||
|
username=form["username"], deleted_at=None
|
||||||
|
)
|
||||||
await self.services.user.save(user)
|
await self.services.user.save(user)
|
||||||
self.session.update({
|
self.session.update(
|
||||||
"logged_in": True,
|
{
|
||||||
"username": user['username'],
|
"logged_in": True,
|
||||||
"uid": user["uid"],
|
"username": user["username"],
|
||||||
"color": user["color"]
|
"uid": user["uid"],
|
||||||
})
|
"color": user["color"],
|
||||||
|
}
|
||||||
|
)
|
||||||
return {"redirect_url": "/web.html"}
|
return {"redirect_url": "/web.html"}
|
||||||
return {"is_valid": False}
|
return {"is_valid": False}
|
||||||
|
@ -20,4 +20,4 @@ class LoginFormView(BaseFormView):
|
|||||||
self.session["username"] = form.username.value
|
self.session["username"] = form.username.value
|
||||||
self.session["uid"] = form.uid.value
|
self.session["uid"] = form.uid.value
|
||||||
return {"redirect_url": "/web.html"}
|
return {"redirect_url": "/web.html"}
|
||||||
return {"is_valid": False}
|
return {"is_valid": False}
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
@ -52,4 +53,4 @@ class LogoutView(BaseView):
|
|||||||
del self.session["username"]
|
del self.session["username"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return await self.json_response({"redirect_url": self.redirect_url})
|
return await self.json_response({"redirect_url": self.redirect_url})
|
||||||
|
@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
# This module defines a web view for user registration. It handles GET requests and form submissions for the registration process.
|
# This module defines a web view for user registration. It handles GET requests and form submissions for the registration process.
|
||||||
|
|
||||||
# The code makes use of 'RegisterForm' from 'snek.form.register' for handling registration forms and 'BaseFormView' from 'snek.system.view' for basic view functionalities.
|
# The code makes use of 'RegisterForm' from 'snek.form.register' for handling registration forms and 'BaseFormView' from 'snek.system.view' for basic view functionalities.
|
||||||
|
|
||||||
# MIT License
|
# MIT License
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from snek.form.register import RegisterForm
|
from snek.form.register import RegisterForm
|
||||||
from snek.system.view import BaseFormView
|
from snek.system.view import BaseFormView
|
||||||
|
|
||||||
|
|
||||||
class RegisterView(BaseFormView):
|
class RegisterView(BaseFormView):
|
||||||
form = RegisterForm
|
form = RegisterForm
|
||||||
|
|
||||||
@ -18,16 +20,20 @@ class RegisterView(BaseFormView):
|
|||||||
return web.HTTPFound("/web.html")
|
return web.HTTPFound("/web.html")
|
||||||
if self.request.path.endswith(".json"):
|
if self.request.path.endswith(".json"):
|
||||||
return await super().get()
|
return await super().get()
|
||||||
return await self.render_template("register.html", {"form": await self.form(app=self.app).to_json()})
|
return await self.render_template(
|
||||||
|
"register.html", {"form": await self.form(app=self.app).to_json()}
|
||||||
|
)
|
||||||
|
|
||||||
async def submit(self, form):
|
async def submit(self, form):
|
||||||
result = await self.app.services.user.register(
|
result = await self.app.services.user.register(
|
||||||
form.email.value, form.username.value, form.password.value
|
form.email.value, form.username.value, form.password.value
|
||||||
)
|
)
|
||||||
self.request.session.update({
|
self.request.session.update(
|
||||||
"uid": result["uid"],
|
{
|
||||||
"username": result["username"],
|
"uid": result["uid"],
|
||||||
"logged_in": True,
|
"username": result["username"],
|
||||||
"color": result["color"]
|
"logged_in": True,
|
||||||
})
|
"color": result["color"],
|
||||||
return {"redirect_url": "/web.html"}
|
}
|
||||||
|
)
|
||||||
|
return {"redirect_url": "/web.html"}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# This code defines a `RegisterFormView` class that handles the user registration process by using a form object, a view parent class, and asynchronously submitting the form data to register a user. It then stores the user's session details and provides a redirect URL to a specific page.
|
# This code defines a `RegisterFormView` class that handles the user registration process by using a form object, a view parent class, and asynchronously submitting the form data to register a user. It then stores the user's session details and provides a redirect URL to a specific page.
|
||||||
|
|
||||||
# Imports used but not part of the language:
|
# Imports used but not part of the language:
|
||||||
# snek.form.register.RegisterForm, snek.system.view.BaseFormView
|
# snek.form.register.RegisterForm, snek.system.view.BaseFormView
|
||||||
|
|
||||||
# MIT License
|
# MIT License
|
||||||
@ -13,10 +13,10 @@
|
|||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -28,6 +28,7 @@
|
|||||||
from snek.form.register import RegisterForm
|
from snek.form.register import RegisterForm
|
||||||
from snek.system.view import BaseFormView
|
from snek.system.view import BaseFormView
|
||||||
|
|
||||||
|
|
||||||
class RegisterFormView(BaseFormView):
|
class RegisterFormView(BaseFormView):
|
||||||
form = RegisterForm
|
form = RegisterForm
|
||||||
|
|
||||||
@ -35,10 +36,12 @@ class RegisterFormView(BaseFormView):
|
|||||||
result = await self.app.services.user.register(
|
result = await self.app.services.user.register(
|
||||||
form.email.value, form.username.value, form.password.value
|
form.email.value, form.username.value, form.password.value
|
||||||
)
|
)
|
||||||
self.request.session.update({
|
self.request.session.update(
|
||||||
"uid": result["uid"],
|
{
|
||||||
"username": result["username"],
|
"uid": result["uid"],
|
||||||
"logged_in": True,
|
"username": result["username"],
|
||||||
"color": result["color"]
|
"logged_in": True,
|
||||||
})
|
"color": result["color"],
|
||||||
return {"redirect_url": "/web.html"}
|
}
|
||||||
|
)
|
||||||
|
return {"redirect_url": "/web.html"}
|
||||||
|
@ -7,41 +7,44 @@
|
|||||||
# MIT License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
|
# MIT License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
|
||||||
|
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
from snek.system.view import BaseView
|
|
||||||
import traceback
|
|
||||||
import json
|
import json
|
||||||
from snek.system.model import now
|
import traceback
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from snek.system.model import now
|
||||||
from snek.system.profiler import Profiler
|
from snek.system.profiler import Profiler
|
||||||
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class RPCView(BaseView):
|
class RPCView(BaseView):
|
||||||
|
|
||||||
class RPCApi:
|
class RPCApi:
|
||||||
def __init__(self, view, ws):
|
def __init__(self, view, ws):
|
||||||
self.view = view
|
self.view = view
|
||||||
self.app = self.view.app
|
self.app = self.view.app
|
||||||
self.services = self.app.services
|
self.services = self.app.services
|
||||||
self.ws = ws
|
self.ws = ws
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_uid(self):
|
def user_uid(self):
|
||||||
return self.view.session.get("uid")
|
return self.view.session.get("uid")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def request(self):
|
def request(self):
|
||||||
return self.view.request
|
return self.view.request
|
||||||
|
|
||||||
def _require_login(self):
|
def _require_login(self):
|
||||||
if not self.is_logged_in:
|
if not self.is_logged_in:
|
||||||
raise Exception("Not logged in")
|
raise Exception("Not logged in")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_logged_in(self):
|
def is_logged_in(self):
|
||||||
return self.view.session.get("logged_in", False)
|
return self.view.session.get("logged_in", False)
|
||||||
|
|
||||||
async def mark_as_read(self, channel_uid):
|
async def mark_as_read(self, channel_uid):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
await self.services.channel_member.mark_as_read(channel_uid, self.user_uid)
|
await self.services.channel_member.mark_as_read(channel_uid, self.user_uid)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def login(self, username, password):
|
async def login(self, username, password):
|
||||||
@ -54,16 +57,26 @@ class RPCView(BaseView):
|
|||||||
self.view.session["username"] = user["username"]
|
self.view.session["username"] = user["username"]
|
||||||
self.view.session["user_nick"] = user["nick"]
|
self.view.session["user_nick"] = user["nick"]
|
||||||
record = user.record
|
record = user.record
|
||||||
del record['password']
|
del record["password"]
|
||||||
del record['deleted_at']
|
del record["deleted_at"]
|
||||||
await self.services.socket.add(self.ws,self.view.request.session.get('uid'))
|
await self.services.socket.add(
|
||||||
async for subscription in self.services.channel_member.find(user_uid=self.view.request.session.get("uid"), deleted_at=None, is_banned=False):
|
self.ws, self.view.request.session.get("uid")
|
||||||
await self.services.socket.subscribe(self.ws, subscription["channel_uid"], self.view.request.session.get("uid"))
|
)
|
||||||
return record
|
async for subscription in self.services.channel_member.find(
|
||||||
|
user_uid=self.view.request.session.get("uid"),
|
||||||
|
deleted_at=None,
|
||||||
|
is_banned=False,
|
||||||
|
):
|
||||||
|
await self.services.socket.subscribe(
|
||||||
|
self.ws,
|
||||||
|
subscription["channel_uid"],
|
||||||
|
self.view.request.session.get("uid"),
|
||||||
|
)
|
||||||
|
return record
|
||||||
|
|
||||||
async def search_user(self, query):
|
async def search_user(self, query):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
return [user['username'] for user in await self.services.user.search(query)]
|
return [user["username"] for user in await self.services.user.search(query)]
|
||||||
|
|
||||||
async def get_user(self, user_uid):
|
async def get_user(self, user_uid):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
@ -71,46 +84,56 @@ class RPCView(BaseView):
|
|||||||
user_uid = self.user_uid
|
user_uid = self.user_uid
|
||||||
user = await self.services.user.get(uid=user_uid)
|
user = await self.services.user.get(uid=user_uid)
|
||||||
record = user.record
|
record = user.record
|
||||||
del record['password']
|
del record["password"]
|
||||||
del record['deleted_at']
|
del record["deleted_at"]
|
||||||
if user_uid != user["uid"]:
|
if user_uid != user["uid"]:
|
||||||
del record['email']
|
del record["email"]
|
||||||
return record
|
return record
|
||||||
|
|
||||||
async def get_messages(self, channel_uid, offset=0,timestamp = None):
|
async def get_messages(self, channel_uid, offset=0, timestamp=None):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
messages = []
|
messages = []
|
||||||
for message in await self.services.channel_message.offset(channel_uid, offset or 0,timestamp or None):
|
for message in await self.services.channel_message.offset(
|
||||||
extended_dict = await self.services.channel_message.to_extended_dict(message)
|
channel_uid, offset or 0, timestamp or None
|
||||||
|
):
|
||||||
|
extended_dict = await self.services.channel_message.to_extended_dict(
|
||||||
|
message
|
||||||
|
)
|
||||||
messages.append(extended_dict)
|
messages.append(extended_dict)
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
async def get_channels(self):
|
async def get_channels(self):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
channels = []
|
channels = []
|
||||||
async for subscription in self.services.channel_member.find(user_uid=self.user_uid, is_banned=False):
|
async for subscription in self.services.channel_member.find(
|
||||||
channel = await self.services.channel.get(uid=subscription['channel_uid'])
|
user_uid=self.user_uid, is_banned=False
|
||||||
|
):
|
||||||
|
channel = await self.services.channel.get(
|
||||||
|
uid=subscription["channel_uid"]
|
||||||
|
)
|
||||||
last_message = await channel.get_last_message()
|
last_message = await channel.get_last_message()
|
||||||
color = None
|
color = None
|
||||||
if last_message:
|
if last_message:
|
||||||
last_message_user = await last_message.get_user()
|
last_message_user = await last_message.get_user()
|
||||||
color = last_message_user['color']
|
color = last_message_user["color"]
|
||||||
channels.append({
|
channels.append(
|
||||||
"name": subscription["label"],
|
{
|
||||||
"uid": subscription["channel_uid"],
|
"name": subscription["label"],
|
||||||
"tag": channel["tag"],
|
"uid": subscription["channel_uid"],
|
||||||
"new_count": subscription["new_count"],
|
"tag": channel["tag"],
|
||||||
"is_moderator": subscription["is_moderator"],
|
"new_count": subscription["new_count"],
|
||||||
"is_read_only": subscription["is_read_only"],
|
"is_moderator": subscription["is_moderator"],
|
||||||
'new_count': subscription['new_count'],
|
"is_read_only": subscription["is_read_only"],
|
||||||
'color': color
|
"new_count": subscription["new_count"],
|
||||||
})
|
"color": color,
|
||||||
|
}
|
||||||
|
)
|
||||||
return channels
|
return channels
|
||||||
|
|
||||||
async def send_message(self, channel_uid, message):
|
async def send_message(self, channel_uid, message):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
await self.services.chat.send(self.user_uid, channel_uid, message)
|
await self.services.chat.send(self.user_uid, channel_uid, message)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def echo(self, *args):
|
async def echo(self, *args):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
@ -118,29 +141,47 @@ class RPCView(BaseView):
|
|||||||
|
|
||||||
async def query(self, *args):
|
async def query(self, *args):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
query = args[0]
|
query = args[0]
|
||||||
lowercase = query.lower()
|
lowercase = query.lower()
|
||||||
if any(keyword in lowercase for keyword in ["drop", "alter", "update", "delete", "replace", "insert", "truncate"]) and 'select' not in lowercase:
|
if (
|
||||||
|
any(
|
||||||
|
keyword in lowercase
|
||||||
|
for keyword in [
|
||||||
|
"drop",
|
||||||
|
"alter",
|
||||||
|
"update",
|
||||||
|
"delete",
|
||||||
|
"replace",
|
||||||
|
"insert",
|
||||||
|
"truncate",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
and "select" not in lowercase
|
||||||
|
):
|
||||||
raise Exception("Not allowed")
|
raise Exception("Not allowed")
|
||||||
records = [dict(record) async for record in self.services.channel.query(args[0])]
|
records = [
|
||||||
|
dict(record) async for record in self.services.channel.query(args[0])
|
||||||
|
]
|
||||||
for record in records:
|
for record in records:
|
||||||
try:
|
try:
|
||||||
del record['email']
|
del record["email"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
del record["password"]
|
del record["password"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
del record['message']
|
del record["message"]
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
del record['html']
|
del record["html"]
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return [dict(record) async for record in self.services.channel.query(args[0])]
|
return [
|
||||||
|
dict(record) async for record in self.services.channel.query(args[0])
|
||||||
|
]
|
||||||
|
|
||||||
async def __call__(self, data):
|
async def __call__(self, data):
|
||||||
try:
|
try:
|
||||||
@ -150,30 +191,43 @@ class RPCView(BaseView):
|
|||||||
raise Exception("Not allowed")
|
raise Exception("Not allowed")
|
||||||
args = data.get("args") or []
|
args = data.get("args") or []
|
||||||
if hasattr(super(), method_name) or not hasattr(self, method_name):
|
if hasattr(super(), method_name) or not hasattr(self, method_name):
|
||||||
return await self._send_json({"callId": call_id, "data": "Not allowed"})
|
return await self._send_json(
|
||||||
|
{"callId": call_id, "data": "Not allowed"}
|
||||||
|
)
|
||||||
method = getattr(self, method_name.replace(".", "_"), None)
|
method = getattr(self, method_name.replace(".", "_"), None)
|
||||||
if not method:
|
if not method:
|
||||||
raise Exception("Method not found")
|
raise Exception("Method not found")
|
||||||
success = True
|
success = True
|
||||||
try:
|
try:
|
||||||
result = await method(*args)
|
result = await method(*args)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
result = {"exception": str(ex), "traceback": traceback.format_exc()}
|
result = {"exception": str(ex), "traceback": traceback.format_exc()}
|
||||||
success = False
|
success = False
|
||||||
if result != "noresponse":
|
if result != "noresponse":
|
||||||
await self._send_json({"callId": call_id, "success": success, "data": result})
|
await self._send_json(
|
||||||
|
{"callId": call_id, "success": success, "data": result}
|
||||||
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(str(ex), flush=True)
|
print(str(ex), flush=True)
|
||||||
await self._send_json({"callId": call_id, "success": False, "data": str(ex)})
|
await self._send_json(
|
||||||
|
{"callId": call_id, "success": False, "data": str(ex)}
|
||||||
|
)
|
||||||
|
|
||||||
async def _send_json(self, obj):
|
async def _send_json(self, obj):
|
||||||
await self.ws.send_str(json.dumps(obj, default=str))
|
await self.ws.send_str(json.dumps(obj, default=str))
|
||||||
|
|
||||||
|
|
||||||
async def get_online_users(self, channel_uid):
|
async def get_online_users(self, channel_uid):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
|
|
||||||
return [dict(uid=record['uid'],username=record['username'], nick=record['nick'],last_ping=record['last_ping']) async for record in self.services.channel.get_online_users(channel_uid)]
|
return [
|
||||||
|
{
|
||||||
|
"uid": record["uid"],
|
||||||
|
"username": record["username"],
|
||||||
|
"nick": record["nick"],
|
||||||
|
"last_ping": record["last_ping"],
|
||||||
|
}
|
||||||
|
async for record in self.services.channel.get_online_users(channel_uid)
|
||||||
|
]
|
||||||
|
|
||||||
async def echo(self, obj):
|
async def echo(self, obj):
|
||||||
await self.ws.send_json(obj)
|
await self.ws.send_json(obj)
|
||||||
@ -182,12 +236,20 @@ class RPCView(BaseView):
|
|||||||
async def get_users(self, channel_uid):
|
async def get_users(self, channel_uid):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
|
|
||||||
return [dict(uid=record['uid'],username=record['username'], nick=record['nick'],last_ping=record['last_ping']) async for record in self.services.channel.get_users(channel_uid)]
|
return [
|
||||||
|
{
|
||||||
|
"uid": record["uid"],
|
||||||
|
"username": record["username"],
|
||||||
|
"nick": record["nick"],
|
||||||
|
"last_ping": record["last_ping"],
|
||||||
|
}
|
||||||
|
async for record in self.services.channel.get_users(channel_uid)
|
||||||
|
]
|
||||||
|
|
||||||
async def ping(self, callId, *args):
|
async def ping(self, callId, *args):
|
||||||
if self.user_uid:
|
if self.user_uid:
|
||||||
user = await self.services.user.get(uid=self.user_uid)
|
user = await self.services.user.get(uid=self.user_uid)
|
||||||
user['last_ping'] = now()
|
user["last_ping"] = now()
|
||||||
await self.services.user.save(user)
|
await self.services.user.save(user)
|
||||||
return {"pong": args}
|
return {"pong": args}
|
||||||
|
|
||||||
@ -196,8 +258,14 @@ class RPCView(BaseView):
|
|||||||
await ws.prepare(self.request)
|
await ws.prepare(self.request)
|
||||||
if self.request.session.get("logged_in"):
|
if self.request.session.get("logged_in"):
|
||||||
await self.services.socket.add(ws, self.request.session.get("uid"))
|
await self.services.socket.add(ws, self.request.session.get("uid"))
|
||||||
async for subscription in self.services.channel_member.find(user_uid=self.request.session.get("uid"), deleted_at=None, is_banned=False):
|
async for subscription in self.services.channel_member.find(
|
||||||
await self.services.socket.subscribe(ws, subscription["channel_uid"], self.request.session.get("uid"))
|
user_uid=self.request.session.get("uid"),
|
||||||
|
deleted_at=None,
|
||||||
|
is_banned=False,
|
||||||
|
):
|
||||||
|
await self.services.socket.subscribe(
|
||||||
|
ws, subscription["channel_uid"], self.request.session.get("uid")
|
||||||
|
)
|
||||||
rpc = RPCView.RPCApi(self, ws)
|
rpc = RPCView.RPCApi(self, ws)
|
||||||
async for msg in ws:
|
async for msg in ws:
|
||||||
if msg.type == web.WSMsgType.TEXT:
|
if msg.type == web.WSMsgType.TEXT:
|
||||||
@ -209,7 +277,7 @@ class RPCView(BaseView):
|
|||||||
await self.services.socket.delete(ws)
|
await self.services.socket.delete(ws)
|
||||||
break
|
break
|
||||||
elif msg.type == web.WSMsgType.ERROR:
|
elif msg.type == web.WSMsgType.ERROR:
|
||||||
pass
|
pass
|
||||||
elif msg.type == web.WSMsgType.CLOSE:
|
elif msg.type == web.WSMsgType.CLOSE:
|
||||||
pass
|
pass
|
||||||
return ws
|
return ws
|
||||||
|
@ -8,17 +8,17 @@
|
|||||||
# - snek.system.view: Provides the BaseFormView class to facilitate form-related operations in a web context.
|
# - snek.system.view: Provides the BaseFormView class to facilitate form-related operations in a web context.
|
||||||
|
|
||||||
# MIT License
|
# MIT License
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
# in the Software without restriction, including without limitation the rights
|
# in the Software without restriction, including without limitation the rights
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -28,7 +28,6 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
from snek.form.search_user import SearchUserForm
|
from snek.form.search_user import SearchUserForm
|
||||||
from snek.system.view import BaseFormView
|
from snek.system.view import BaseFormView
|
||||||
|
|
||||||
@ -40,14 +39,17 @@ class SearchUserView(BaseFormView):
|
|||||||
users = []
|
users = []
|
||||||
query = self.request.query.get("query")
|
query = self.request.query.get("query")
|
||||||
if query:
|
if query:
|
||||||
users = [user.record for user in await self.app.services.user.search(query)]
|
users = [user.record for user in await self.app.services.user.search(query)]
|
||||||
|
|
||||||
if self.request.path.endswith(".json"):
|
if self.request.path.endswith(".json"):
|
||||||
return await super().get()
|
return await super().get()
|
||||||
current_user = await self.app.services.user.get(uid=self.session.get("uid"))
|
current_user = await self.app.services.user.get(uid=self.session.get("uid"))
|
||||||
return await self.render_template("search_user.html", {"users": users, "query": query or '','current_user': current_user})
|
return await self.render_template(
|
||||||
|
"search_user.html",
|
||||||
|
{"users": users, "query": query or "", "current_user": current_user},
|
||||||
|
)
|
||||||
|
|
||||||
async def submit(self, form):
|
async def submit(self, form):
|
||||||
if await form.is_valid:
|
if await form.is_valid:
|
||||||
return {"redirect_url": "/search-user.html?query=" + form['username']}
|
return {"redirect_url": "/search-user.html?query=" + form["username"]}
|
||||||
return {"is_valid": False}
|
return {"is_valid": False}
|
||||||
|
@ -25,17 +25,18 @@
|
|||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class StatusView(BaseView):
|
class StatusView(BaseView):
|
||||||
async def get(self):
|
async def get(self):
|
||||||
memberships = []
|
memberships = []
|
||||||
user = {}
|
user = {}
|
||||||
|
|
||||||
user_id = self.session.get("uid")
|
user_id = self.session.get("uid")
|
||||||
if user_id:
|
if user_id:
|
||||||
user = await self.app.services.user.get(uid=user_id)
|
user = await self.app.services.user.get(uid=user_id)
|
||||||
if not user:
|
if not user:
|
||||||
return await self.json_response({"error": "User not found"}, status=404)
|
return await self.json_response({"error": "User not found"}, status=404)
|
||||||
|
|
||||||
async for model in self.app.services.channel_member.find(
|
async for model in self.app.services.channel_member.find(
|
||||||
user_uid=user_id, deleted_at=None, is_banned=False
|
user_uid=user_id, deleted_at=None, is_banned=False
|
||||||
):
|
):
|
||||||
@ -69,4 +70,4 @@ class StatusView(BaseView):
|
|||||||
self.app.cache.cache, None
|
self.app.cache.cache, None
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
from snek.system.view import BaseView
|
|
||||||
import aiohttp
|
|
||||||
import asyncio
|
|
||||||
from snek.system.terminal import TerminalSession
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from snek.system.terminal import TerminalSession
|
||||||
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class TerminalSocketView(BaseView):
|
class TerminalSocketView(BaseView):
|
||||||
|
|
||||||
login_required = True
|
login_required = True
|
||||||
|
|
||||||
user_sessions = {}
|
user_sessions = {}
|
||||||
|
|
||||||
async def prepare_drive(self):
|
async def prepare_drive(self):
|
||||||
user = await self.services.user.get(uid=self.session.get("uid"))
|
user = await self.services.user.get(uid=self.session.get("uid"))
|
||||||
root = pathlib.Path("drive").joinpath(user["uid"])
|
root = pathlib.Path("drive").joinpath(user["uid"])
|
||||||
@ -19,40 +21,34 @@ class TerminalSocketView(BaseView):
|
|||||||
destination_path = root.joinpath(path.name)
|
destination_path = root.joinpath(path.name)
|
||||||
if not path.is_dir():
|
if not path.is_dir():
|
||||||
destination_path.write_bytes(path.read_bytes())
|
destination_path.write_bytes(path.read_bytes())
|
||||||
return root
|
return root
|
||||||
|
|
||||||
async def get(self):
|
|
||||||
|
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
|
||||||
ws = aiohttp.web.WebSocketResponse()
|
ws = aiohttp.web.WebSocketResponse()
|
||||||
await ws.prepare(self.request)
|
await ws.prepare(self.request)
|
||||||
user = await self.services.user.get(uid=self.session.get("uid"))
|
user = await self.services.user.get(uid=self.session.get("uid"))
|
||||||
root = await self.prepare_drive()
|
root = await self.prepare_drive()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
command = f"docker run -v ./{root}/:/root -it --memory 512M --cpus=0.5 -w /root snek_ubuntu /bin/bash"
|
command = f"docker run -v ./{root}/:/root -it --memory 512M --cpus=0.5 -w /root snek_ubuntu /bin/bash"
|
||||||
|
|
||||||
session = self.user_sessions.get(user["uid"])
|
session = self.user_sessions.get(user["uid"])
|
||||||
if not session:
|
if not session:
|
||||||
self.user_sessions[user["uid"]] = TerminalSession(command=command)
|
self.user_sessions[user["uid"]] = TerminalSession(command=command)
|
||||||
session = self.user_sessions[user["uid"]]
|
session = self.user_sessions[user["uid"]]
|
||||||
await session.add_websocket(ws)
|
await session.add_websocket(ws)
|
||||||
#asyncio.create_task(session.read_output(ws))
|
# asyncio.create_task(session.read_output(ws))
|
||||||
|
|
||||||
async for msg in ws:
|
async for msg in ws:
|
||||||
if msg.type == aiohttp.WSMsgType.BINARY:
|
if msg.type == aiohttp.WSMsgType.BINARY:
|
||||||
await session.write_input(msg.data.decode())
|
await session.write_input(msg.data.decode())
|
||||||
|
|
||||||
|
|
||||||
return ws
|
return ws
|
||||||
|
|
||||||
|
|
||||||
class TerminalView(BaseView):
|
class TerminalView(BaseView):
|
||||||
|
|
||||||
login_required = True
|
login_required = True
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
request = self.request
|
return await self.request.app.render_template("terminal.html", self.request)
|
||||||
return await self.request.app.render_template('terminal.html',self.request)
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class ThreadsView(BaseView):
|
class ThreadsView(BaseView):
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
@ -12,22 +13,25 @@ class ThreadsView(BaseView):
|
|||||||
if not last_message:
|
if not last_message:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
thread["uid"] = channel['uid']
|
thread["uid"] = channel["uid"]
|
||||||
thread["name"] = await channel_member.get_name()
|
thread["name"] = await channel_member.get_name()
|
||||||
thread["new_count"] = channel_member["new_count"]
|
thread["new_count"] = channel_member["new_count"]
|
||||||
thread["last_message_on"] = channel["last_message_on"]
|
thread["last_message_on"] = channel["last_message_on"]
|
||||||
thread['created_at'] = thread['last_message_on']
|
thread["created_at"] = thread["last_message_on"]
|
||||||
|
|
||||||
|
thread["name_color"] = "#f05a28"
|
||||||
thread['name_color'] = "#f05a28"
|
|
||||||
thread["last_message_text"] = last_message["message"]
|
thread["last_message_text"] = last_message["message"]
|
||||||
thread['last_message_user_uid'] = last_message["user_uid"]
|
thread["last_message_user_uid"] = last_message["user_uid"]
|
||||||
user_last_message = await self.app.services.user.get(uid=last_message["user_uid"])
|
user_last_message = await self.app.services.user.get(
|
||||||
if channel['tag'] == "dm":
|
uid=last_message["user_uid"]
|
||||||
thread['name_color'] = user_last_message['color']
|
)
|
||||||
thread['last_message_user_color'] = user_last_message['color']
|
if channel["tag"] == "dm":
|
||||||
|
thread["name_color"] = user_last_message["color"]
|
||||||
|
thread["last_message_user_color"] = user_last_message["color"]
|
||||||
threads.append(thread)
|
threads.append(thread)
|
||||||
|
|
||||||
threads.sort(key=lambda x: x['last_message_on'] or '', reverse=True)
|
|
||||||
|
|
||||||
return await self.render_template("threads.html", dict(threads=threads,user=user))
|
threads.sort(key=lambda x: x["last_message_on"] or "", reverse=True)
|
||||||
|
|
||||||
|
return await self.render_template(
|
||||||
|
"threads.html", {"threads": threads, "user": user}
|
||||||
|
)
|
||||||
|
@ -1,31 +1,36 @@
|
|||||||
# Written by retoor@molodetz.nl
|
# Written by retoor@molodetz.nl
|
||||||
|
|
||||||
# This code defines a web application for uploading and retrieving files.
|
# This code defines a web application for uploading and retrieving files.
|
||||||
# It includes functionality to upload files through a POST request and retrieve them via a GET request.
|
# It includes functionality to upload files through a POST request and retrieve them via a GET request.
|
||||||
|
|
||||||
# The code uses the following non-standard imports:
|
# The code uses the following non-standard imports:
|
||||||
# - snek.system.view.BaseView: For extending view functionalities.
|
# - snek.system.view.BaseView: For extending view functionalities.
|
||||||
# - aiofiles: For asynchronous file operations.
|
# - aiofiles: For asynchronous file operations.
|
||||||
# - aiohttp: For managing web server requests and responses.
|
# - aiohttp: For managing web server requests and responses.
|
||||||
|
|
||||||
# MIT License: This software is licensed under the MIT License, a permissive free software license.
|
# MIT License: This software is licensed under the MIT License, a permissive free software license.
|
||||||
|
|
||||||
from snek.system.view import BaseView
|
|
||||||
import aiofiles
|
|
||||||
import pathlib
|
import pathlib
|
||||||
from aiohttp import web
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
UPLOAD_DIR = pathlib.Path("./drive")
|
UPLOAD_DIR = pathlib.Path("./drive")
|
||||||
|
|
||||||
|
|
||||||
class UploadView(BaseView):
|
class UploadView(BaseView):
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
uid = self.request.match_info.get("uid")
|
uid = self.request.match_info.get("uid")
|
||||||
drive_item = await self.services.drive_item.get(uid)
|
drive_item = await self.services.drive_item.get(uid)
|
||||||
response = web.FileResponse(drive_item["path"])
|
response = web.FileResponse(drive_item["path"])
|
||||||
response.headers['Cache-Control'] = f'public, max-age={1337*420}'
|
response.headers["Cache-Control"] = f"public, max-age={1337*420}"
|
||||||
response.headers['Content-Disposition'] = f'attachment; filename="{drive_item["name"]}"'
|
response.headers["Content-Disposition"] = (
|
||||||
|
f'attachment; filename="{drive_item["name"]}"'
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
async def post(self):
|
async def post(self):
|
||||||
@ -36,7 +41,9 @@ class UploadView(BaseView):
|
|||||||
|
|
||||||
channel_uid = None
|
channel_uid = None
|
||||||
|
|
||||||
drive = await self.services.drive.get_or_create(user_uid=self.request.session.get("uid"))
|
drive = await self.services.drive.get_or_create(
|
||||||
|
user_uid=self.request.session.get("uid")
|
||||||
|
)
|
||||||
|
|
||||||
extension_types = {
|
extension_types = {
|
||||||
".jpg": "image",
|
".jpg": "image",
|
||||||
@ -47,7 +54,7 @@ class UploadView(BaseView):
|
|||||||
".mp3": "audio",
|
".mp3": "audio",
|
||||||
".pdf": "document",
|
".pdf": "document",
|
||||||
".doc": "document",
|
".doc": "document",
|
||||||
".docx": "document"
|
".docx": "document",
|
||||||
}
|
}
|
||||||
|
|
||||||
while field := await reader.next():
|
while field := await reader.next():
|
||||||
@ -58,32 +65,45 @@ class UploadView(BaseView):
|
|||||||
filename = field.filename
|
filename = field.filename
|
||||||
if not filename:
|
if not filename:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = str(uuid.uuid4()) + pathlib.Path(filename).suffix
|
name = str(uuid.uuid4()) + pathlib.Path(filename).suffix
|
||||||
|
|
||||||
file_path = pathlib.Path(UPLOAD_DIR).joinpath(name)
|
file_path = pathlib.Path(UPLOAD_DIR).joinpath(name)
|
||||||
files.append(file_path)
|
files.append(file_path)
|
||||||
|
|
||||||
async with aiofiles.open(str(file_path.absolute()), 'wb') as f:
|
async with aiofiles.open(str(file_path.absolute()), "wb") as f:
|
||||||
while chunk := await field.read_chunk():
|
while chunk := await field.read_chunk():
|
||||||
await f.write(chunk)
|
await f.write(chunk)
|
||||||
|
|
||||||
drive_item = await self.services.drive_item.create(
|
drive_item = await self.services.drive_item.create(
|
||||||
drive["uid"], filename, str(file_path.absolute()), file_path.stat().st_size, file_path.suffix
|
drive["uid"],
|
||||||
|
filename,
|
||||||
|
str(file_path.absolute()),
|
||||||
|
file_path.stat().st_size,
|
||||||
|
file_path.suffix,
|
||||||
)
|
)
|
||||||
|
|
||||||
type_ = "unknown"
|
|
||||||
extension = "." + filename.split(".")[-1]
|
extension = "." + filename.split(".")[-1]
|
||||||
if extension in extension_types:
|
if extension in extension_types:
|
||||||
type_ = extension_types[extension]
|
extension_types[extension]
|
||||||
|
|
||||||
await self.services.drive_item.save(drive_item)
|
await self.services.drive_item.save(drive_item)
|
||||||
response = "Uploaded [" + filename + "](/drive.bin/" + drive_item["uid"] + ")"
|
response = (
|
||||||
#response = "<iframe width=\"100%\" frameborder=\"0\" allowfullscreen title=\"Embedded\" src=\"" + self.base_url + "/drive.bin/" + drive_item["uid"] + "\"></iframe>\n"
|
"Uploaded [" + filename + "](/drive.bin/" + drive_item["uid"] + ")"
|
||||||
response = "[" + filename + "](/drive.bin/" + drive_item["uid"] + extension + ")"
|
)
|
||||||
|
# response = "<iframe width=\"100%\" frameborder=\"0\" allowfullscreen title=\"Embedded\" src=\"" + self.base_url + "/drive.bin/" + drive_item["uid"] + "\"></iframe>\n"
|
||||||
|
response = (
|
||||||
|
"[" + filename + "](/drive.bin/" + drive_item["uid"] + extension + ")"
|
||||||
|
)
|
||||||
|
|
||||||
await self.services.chat.send(
|
await self.services.chat.send(
|
||||||
self.request.session.get("uid"), channel_uid, response
|
self.request.session.get("uid"), channel_uid, response
|
||||||
)
|
)
|
||||||
|
|
||||||
return web.json_response({"message": "Files uploaded successfully", "files": [str(file) for file in files], "channel_uid": channel_uid})
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"message": "Files uploaded successfully",
|
||||||
|
"files": [str(file) for file in files],
|
||||||
|
"channel_uid": channel_uid,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -25,37 +25,55 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
class WebView(BaseView):
|
class WebView(BaseView):
|
||||||
login_required = True
|
login_required = True
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
if self.login_required and not self.session.get("logged_in"):
|
if self.login_required and not self.session.get("logged_in"):
|
||||||
return web.HTTPFound("/")
|
return web.HTTPFound("/")
|
||||||
channel = await self.services.channel.get(uid=self.request.match_info.get("channel"))
|
channel = await self.services.channel.get(
|
||||||
|
uid=self.request.match_info.get("channel")
|
||||||
|
)
|
||||||
if not channel:
|
if not channel:
|
||||||
user = await self.services.user.get(uid=self.request.match_info.get("channel"))
|
user = await self.services.user.get(
|
||||||
|
uid=self.request.match_info.get("channel")
|
||||||
|
)
|
||||||
if user:
|
if user:
|
||||||
channel = await self.services.channel.get_dm(self.session.get("uid"), user["uid"])
|
channel = await self.services.channel.get_dm(
|
||||||
|
self.session.get("uid"), user["uid"]
|
||||||
|
)
|
||||||
if channel:
|
if channel:
|
||||||
return web.HTTPFound("/channel/{}.html".format(channel["uid"]))
|
return web.HTTPFound("/channel/{}.html".format(channel["uid"]))
|
||||||
if not channel:
|
if not channel:
|
||||||
return web.HTTPNotFound()
|
return web.HTTPNotFound()
|
||||||
|
|
||||||
channel_member = await self.app.services.channel_member.get(user_uid=self.session.get("uid"), channel_uid=channel["uid"])
|
channel_member = await self.app.services.channel_member.get(
|
||||||
|
user_uid=self.session.get("uid"), channel_uid=channel["uid"]
|
||||||
|
)
|
||||||
if not channel_member:
|
if not channel_member:
|
||||||
return web.HTTPNotFound()
|
return web.HTTPNotFound()
|
||||||
|
|
||||||
channel_member['new_count'] = 0
|
channel_member["new_count"] = 0
|
||||||
await self.app.services.channel_member.save(channel_member)
|
await self.app.services.channel_member.save(channel_member)
|
||||||
|
|
||||||
user = await self.services.user.get(uid=self.session.get("uid"))
|
user = await self.services.user.get(uid=self.session.get("uid"))
|
||||||
messages = [await self.app.services.channel_message.to_extended_dict(message) for message in await self.app.services.channel_message.offset(
|
messages = [
|
||||||
channel["uid"]
|
await self.app.services.channel_message.to_extended_dict(message)
|
||||||
)]
|
for message in await self.app.services.channel_message.offset(
|
||||||
|
channel["uid"]
|
||||||
|
)
|
||||||
|
]
|
||||||
for message in messages:
|
for message in messages:
|
||||||
await self.app.services.notification.mark_as_read(self.session.get("uid"),message["uid"])
|
await self.app.services.notification.mark_as_read(
|
||||||
|
self.session.get("uid"), message["uid"]
|
||||||
|
)
|
||||||
|
|
||||||
name = await channel_member.get_name()
|
name = await channel_member.get_name()
|
||||||
return await self.render_template("web.html", {"name": name, "channel": channel,"user": user,"messages": messages})
|
return await self.render_template(
|
||||||
|
"web.html",
|
||||||
|
{"name": name, "channel": channel, "user": user, "messages": messages},
|
||||||
|
)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import asyncssh
|
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import asyncssh
|
||||||
|
|
||||||
asyncssh.set_debug_level(2)
|
asyncssh.set_debug_level(2)
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
# Configuration for SFTP server
|
# Configuration for SFTP server
|
||||||
@ -11,6 +13,7 @@ PASSWORD = "woeii"
|
|||||||
HOST = "localhost"
|
HOST = "localhost"
|
||||||
PORT = 2225
|
PORT = 2225
|
||||||
|
|
||||||
|
|
||||||
class MySFTPServer(asyncssh.SFTPServer):
|
class MySFTPServer(asyncssh.SFTPServer):
|
||||||
def __init__(self, chan):
|
def __init__(self, chan):
|
||||||
super().__init__(chan)
|
super().__init__(chan)
|
||||||
@ -31,8 +34,10 @@ class MySFTPServer(asyncssh.SFTPServer):
|
|||||||
full_path = os.path.join(self.root, path.lstrip("/"))
|
full_path = os.path.join(self.root, path.lstrip("/"))
|
||||||
return await super().listdir(full_path)
|
return await super().listdir(full_path)
|
||||||
|
|
||||||
|
|
||||||
class MySSHServer(asyncssh.SSHServer):
|
class MySSHServer(asyncssh.SSHServer):
|
||||||
"""Custom SSH server to handle authentication"""
|
"""Custom SSH server to handle authentication"""
|
||||||
|
|
||||||
def connection_made(self, conn):
|
def connection_made(self, conn):
|
||||||
print(f"New connection from {conn.get_extra_info('peername')}")
|
print(f"New connection from {conn.get_extra_info('peername')}")
|
||||||
|
|
||||||
@ -46,11 +51,12 @@ class MySSHServer(asyncssh.SSHServer):
|
|||||||
return True # Support password authentication
|
return True # Support password authentication
|
||||||
|
|
||||||
def validate_password(self, username, password):
|
def validate_password(self, username, password):
|
||||||
print(username,password)
|
print(username, password)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
return username == USERNAME and password == PASSWORD
|
return username == USERNAME and password == PASSWORD
|
||||||
|
|
||||||
|
|
||||||
async def start_sftp_server():
|
async def start_sftp_server():
|
||||||
os.makedirs(SFTP_ROOT, exist_ok=True) # Ensure the root directory exists
|
os.makedirs(SFTP_ROOT, exist_ok=True) # Ensure the root directory exists
|
||||||
|
|
||||||
@ -59,11 +65,12 @@ async def start_sftp_server():
|
|||||||
host=HOST,
|
host=HOST,
|
||||||
port=PORT,
|
port=PORT,
|
||||||
server_host_keys=["ssh_host_key"],
|
server_host_keys=["ssh_host_key"],
|
||||||
process_factory=MySFTPServer
|
process_factory=MySFTPServer,
|
||||||
)
|
)
|
||||||
print(f"SFTP server running on {HOST}:{PORT}")
|
print(f"SFTP server running on {HOST}:{PORT}")
|
||||||
await asyncio.Future() # Keep running forever
|
await asyncio.Future() # Keep running forever
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
asyncio.run(start_sftp_server())
|
asyncio.run(start_sftp_server())
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import asyncssh
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import asyncssh
|
||||||
|
|
||||||
# SSH Server Configuration
|
# SSH Server Configuration
|
||||||
HOST = "0.0.0.0"
|
HOST = "0.0.0.0"
|
||||||
PORT = 2225
|
PORT = 2225
|
||||||
@ -9,6 +10,7 @@ USERNAME = "user"
|
|||||||
PASSWORD = "password"
|
PASSWORD = "password"
|
||||||
SHELL = "/bin/sh" # Change to another shell if needed
|
SHELL = "/bin/sh" # Change to another shell if needed
|
||||||
|
|
||||||
|
|
||||||
class CustomSSHServer(asyncssh.SSHServer):
|
class CustomSSHServer(asyncssh.SSHServer):
|
||||||
def connection_made(self, conn):
|
def connection_made(self, conn):
|
||||||
print(f"New connection from {conn.get_extra_info('peername')}")
|
print(f"New connection from {conn.get_extra_info('peername')}")
|
||||||
@ -22,6 +24,7 @@ class CustomSSHServer(asyncssh.SSHServer):
|
|||||||
def validate_password(self, username, password):
|
def validate_password(self, username, password):
|
||||||
return username == USERNAME and password == PASSWORD
|
return username == USERNAME and password == PASSWORD
|
||||||
|
|
||||||
|
|
||||||
async def custom_bash_process(process):
|
async def custom_bash_process(process):
|
||||||
"""Spawns a custom bash shell process"""
|
"""Spawns a custom bash shell process"""
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
@ -29,7 +32,12 @@ async def custom_bash_process(process):
|
|||||||
|
|
||||||
# Start the Bash shell
|
# Start the Bash shell
|
||||||
bash_proc = await asyncio.create_subprocess_exec(
|
bash_proc = await asyncio.create_subprocess_exec(
|
||||||
SHELL, "-i", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env
|
SHELL,
|
||||||
|
"-i",
|
||||||
|
stdin=asyncio.subprocess.PIPE,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
env=env,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def read_output():
|
async def read_output():
|
||||||
@ -48,6 +56,7 @@ async def custom_bash_process(process):
|
|||||||
|
|
||||||
await asyncio.gather(read_output(), read_input())
|
await asyncio.gather(read_output(), read_input())
|
||||||
|
|
||||||
|
|
||||||
async def start_ssh_server():
|
async def start_ssh_server():
|
||||||
"""Starts the AsyncSSH server with Bash"""
|
"""Starts the AsyncSSH server with Bash"""
|
||||||
await asyncssh.create_server(
|
await asyncssh.create_server(
|
||||||
@ -55,14 +64,14 @@ async def start_ssh_server():
|
|||||||
host=HOST,
|
host=HOST,
|
||||||
port=PORT,
|
port=PORT,
|
||||||
server_host_keys=["ssh_host_key"],
|
server_host_keys=["ssh_host_key"],
|
||||||
process_factory=custom_bash_process
|
process_factory=custom_bash_process,
|
||||||
)
|
)
|
||||||
print(f"SSH server running on {HOST}:{PORT}")
|
print(f"SSH server running on {HOST}:{PORT}")
|
||||||
await asyncio.Future() # Keep running
|
await asyncio.Future() # Keep running
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
asyncio.run(start_ssh_server())
|
asyncio.run(start_ssh_server())
|
||||||
except (OSError, asyncssh.Error) as e:
|
except (OSError, asyncssh.Error) as e:
|
||||||
print(f"Error starting SSH server: {e}")
|
print(f"Error starting SSH server: {e}")
|
||||||
|
|
||||||
|
@ -27,45 +27,48 @@
|
|||||||
# The file ``ssh_user_ca`` must exist with a cert-authority entry of
|
# The file ``ssh_user_ca`` must exist with a cert-authority entry of
|
||||||
# the certificate authority which can sign valid client certificates.
|
# the certificate authority which can sign valid client certificates.
|
||||||
|
|
||||||
import asyncio, asyncssh, sys
|
import asyncio
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import asyncssh
|
||||||
|
|
||||||
|
|
||||||
async def handle_client(process: asyncssh.SSHServerProcess) -> None:
|
async def handle_client(process: asyncssh.SSHServerProcess) -> None:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
width, height, pixwidth, pixheight = process.term_size
|
width, height, pixwidth, pixheight = process.term_size
|
||||||
|
|
||||||
process.stdout.write(f'Terminal type: {process.term_type}, '
|
process.stdout.write(
|
||||||
f'size: {width}x{height}')
|
f"Terminal type: {process.term_type}, " f"size: {width}x{height}"
|
||||||
|
)
|
||||||
if pixwidth and pixheight:
|
if pixwidth and pixheight:
|
||||||
process.stdout.write(f' ({pixwidth}x{pixheight} pixels)')
|
process.stdout.write(f" ({pixwidth}x{pixheight} pixels)")
|
||||||
process.stdout.write('\nTry resizing your window!\n')
|
process.stdout.write("\nTry resizing your window!\n")
|
||||||
|
|
||||||
while not process.stdin.at_eof():
|
while not process.stdin.at_eof():
|
||||||
try:
|
try:
|
||||||
await process.stdin.read()
|
await process.stdin.read()
|
||||||
except asyncssh.TerminalSizeChanged as exc:
|
except asyncssh.TerminalSizeChanged as exc:
|
||||||
process.stdout.write(f'New window size: {exc.width}x{exc.height}')
|
process.stdout.write(f"New window size: {exc.width}x{exc.height}")
|
||||||
if exc.pixwidth and exc.pixheight:
|
if exc.pixwidth and exc.pixheight:
|
||||||
process.stdout.write(f' ({exc.pixwidth}'
|
process.stdout.write(f" ({exc.pixwidth}" f"x{exc.pixheight} pixels)")
|
||||||
f'x{exc.pixheight} pixels)')
|
process.stdout.write("\n")
|
||||||
process.stdout.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def start_server() -> None:
|
async def start_server() -> None:
|
||||||
await asyncssh.listen('', 2230, server_host_keys=['ssh_host_key'],
|
await asyncssh.listen(
|
||||||
#authorized_client_keys='ssh_user_ca',
|
"",
|
||||||
process_factory=handle_client)
|
2230,
|
||||||
|
server_host_keys=["ssh_host_key"],
|
||||||
|
# authorized_client_keys='ssh_user_ca',
|
||||||
|
process_factory=handle_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(start_server())
|
loop.run_until_complete(start_server())
|
||||||
except (OSError, asyncssh.Error) as exc:
|
except (OSError, asyncssh.Error) as exc:
|
||||||
sys.exit('Error starting server: ' + str(exc))
|
sys.exit("Error starting server: " + str(exc))
|
||||||
|
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
@ -24,32 +24,39 @@
|
|||||||
# private key in it to use as a server host key. An SSH host certificate
|
# private key in it to use as a server host key. An SSH host certificate
|
||||||
# can optionally be provided in the file ``ssh_host_key-cert.pub``.
|
# can optionally be provided in the file ``ssh_host_key-cert.pub``.
|
||||||
|
|
||||||
import asyncio, asyncssh, bcrypt, sys
|
import asyncio
|
||||||
|
import sys
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
passwords = {'guest': b'', # guest account with no password
|
import asyncssh
|
||||||
'user': bcrypt.hashpw(b'user', bcrypt.gensalt()),
|
import bcrypt
|
||||||
}
|
|
||||||
|
passwords = {
|
||||||
|
"guest": b"", # guest account with no password
|
||||||
|
"user": bcrypt.hashpw(b"user", bcrypt.gensalt()),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def handle_client(process: asyncssh.SSHServerProcess) -> None:
|
def handle_client(process: asyncssh.SSHServerProcess) -> None:
|
||||||
username = process.get_extra_info('username')
|
username = process.get_extra_info("username")
|
||||||
process.stdout.write(f'Welcome to my SSH server, {username}!\n')
|
process.stdout.write(f"Welcome to my SSH server, {username}!\n")
|
||||||
#process.exit(0)
|
# process.exit(0)
|
||||||
|
|
||||||
|
|
||||||
class MySSHServer(asyncssh.SSHServer):
|
class MySSHServer(asyncssh.SSHServer):
|
||||||
def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
|
def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
|
||||||
peername = conn.get_extra_info('peername')[0]
|
peername = conn.get_extra_info("peername")[0]
|
||||||
print(f'SSH connection received from {peername}.')
|
print(f"SSH connection received from {peername}.")
|
||||||
|
|
||||||
def connection_lost(self, exc: Optional[Exception]) -> None:
|
def connection_lost(self, exc: Optional[Exception]) -> None:
|
||||||
if exc:
|
if exc:
|
||||||
print('SSH connection error: ' + str(exc), file=sys.stderr)
|
print("SSH connection error: " + str(exc), file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
print('SSH connection closed.')
|
print("SSH connection closed.")
|
||||||
|
|
||||||
def begin_auth(self, username: str) -> bool:
|
def begin_auth(self, username: str) -> bool:
|
||||||
# If the user's password is the empty string, no auth is required
|
# If the user's password is the empty string, no auth is required
|
||||||
return passwords.get(username) != b''
|
return passwords.get(username) != b""
|
||||||
|
|
||||||
def password_auth_supported(self) -> bool:
|
def password_auth_supported(self) -> bool:
|
||||||
return True
|
return True
|
||||||
@ -60,18 +67,24 @@ class MySSHServer(asyncssh.SSHServer):
|
|||||||
pw = passwords[username]
|
pw = passwords[username]
|
||||||
if not password and not pw:
|
if not password and not pw:
|
||||||
return True
|
return True
|
||||||
return bcrypt.checkpw(password.encode('utf-8'), pw)
|
return bcrypt.checkpw(password.encode("utf-8"), pw)
|
||||||
|
|
||||||
|
|
||||||
async def start_server() -> None:
|
async def start_server() -> None:
|
||||||
await asyncssh.create_server(MySSHServer, '', 2231,
|
await asyncssh.create_server(
|
||||||
server_host_keys=['ssh_host_key'],
|
MySSHServer,
|
||||||
process_factory=handle_client)
|
"",
|
||||||
|
2231,
|
||||||
|
server_host_keys=["ssh_host_key"],
|
||||||
|
process_factory=handle_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(start_server())
|
loop.run_until_complete(start_server())
|
||||||
except (OSError, asyncssh.Error) as exc:
|
except (OSError, asyncssh.Error) as exc:
|
||||||
sys.exit('Error starting server: ' + str(exc))
|
sys.exit("Error starting server: " + str(exc))
|
||||||
|
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
@ -27,11 +27,15 @@
|
|||||||
# The file ``ssh_user_ca`` must exist with a cert-authority entry of
|
# The file ``ssh_user_ca`` must exist with a cert-authority entry of
|
||||||
# the certificate authority which can sign valid client certificates.
|
# the certificate authority which can sign valid client certificates.
|
||||||
|
|
||||||
import asyncio, asyncssh, sys
|
import asyncio
|
||||||
|
import sys
|
||||||
from typing import List, cast
|
from typing import List, cast
|
||||||
|
|
||||||
|
import asyncssh
|
||||||
|
|
||||||
|
|
||||||
class ChatClient:
|
class ChatClient:
|
||||||
_clients: List['ChatClient'] = []
|
_clients: List["ChatClient"] = []
|
||||||
|
|
||||||
def __init__(self, process: asyncssh.SSHServerProcess):
|
def __init__(self, process: asyncssh.SSHServerProcess):
|
||||||
self._process = process
|
self._process = process
|
||||||
@ -40,8 +44,6 @@ class ChatClient:
|
|||||||
async def handle_client(cls, process: asyncssh.SSHServerProcess):
|
async def handle_client(cls, process: asyncssh.SSHServerProcess):
|
||||||
await cls(process).run()
|
await cls(process).run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def readline(self) -> str:
|
async def readline(self) -> str:
|
||||||
return cast(str, self._process.stdin.readline())
|
return cast(str, self._process.stdin.readline())
|
||||||
|
|
||||||
@ -53,54 +55,58 @@ class ChatClient:
|
|||||||
if client != self:
|
if client != self:
|
||||||
client.write(msg)
|
client.write(msg)
|
||||||
|
|
||||||
|
|
||||||
def begin_auth(self, username: str) -> bool:
|
def begin_auth(self, username: str) -> bool:
|
||||||
# If the user's password is the empty string, no auth is required
|
# If the user's password is the empty string, no auth is required
|
||||||
#return False
|
# return False
|
||||||
return True # passwords.get(username) != b''
|
return True # passwords.get(username) != b''
|
||||||
|
|
||||||
def password_auth_supported(self) -> bool:
|
def password_auth_supported(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def validate_password(self, username: str, password: str) -> bool:
|
def validate_password(self, username: str, password: str) -> bool:
|
||||||
#if username not in passwords:
|
# if username not in passwords:
|
||||||
# return False
|
# return False
|
||||||
#pw = passwords[username]
|
# pw = passwords[username]
|
||||||
#if not password and not pw:
|
# if not password and not pw:
|
||||||
# return True
|
# return True
|
||||||
return True
|
return True
|
||||||
#return bcrypt.checkpw(password.encode('utf-8'), pw)
|
# return bcrypt.checkpw(password.encode('utf-8'), pw)
|
||||||
|
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
self.write('Welcome to chat!\n\n')
|
self.write("Welcome to chat!\n\n")
|
||||||
|
|
||||||
self.write('Enter your name: ')
|
self.write("Enter your name: ")
|
||||||
name = (await self.readline()).rstrip('\n')
|
name = (await self.readline()).rstrip("\n")
|
||||||
|
|
||||||
self.write(f'\n{len(self._clients)} other users are connected.\n\n')
|
self.write(f"\n{len(self._clients)} other users are connected.\n\n")
|
||||||
|
|
||||||
self._clients.append(self)
|
self._clients.append(self)
|
||||||
self.broadcast(f'*** {name} has entered chat ***\n')
|
self.broadcast(f"*** {name} has entered chat ***\n")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async for line in self._process.stdin:
|
async for line in self._process.stdin:
|
||||||
self.broadcast(f'{name}: {line}')
|
self.broadcast(f"{name}: {line}")
|
||||||
except asyncssh.BreakReceived:
|
except asyncssh.BreakReceived:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.broadcast(f'*** {name} has left chat ***\n')
|
self.broadcast(f"*** {name} has left chat ***\n")
|
||||||
self._clients.remove(self)
|
self._clients.remove(self)
|
||||||
|
|
||||||
|
|
||||||
async def start_server() -> None:
|
async def start_server() -> None:
|
||||||
await asyncssh.listen('', 2235, server_host_keys=['ssh_host_key'],
|
await asyncssh.listen(
|
||||||
process_factory=ChatClient.handle_client)
|
"",
|
||||||
|
2235,
|
||||||
|
server_host_keys=["ssh_host_key"],
|
||||||
|
process_factory=ChatClient.handle_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(start_server())
|
loop.run_until_complete(start_server())
|
||||||
except (OSError, asyncssh.Error) as exc:
|
except (OSError, asyncssh.Error) as exc:
|
||||||
sys.exit('Error starting server: ' + str(exc))
|
sys.exit("Error starting server: " + str(exc))
|
||||||
|
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
Loading…
Reference in New Issue
Block a user