1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

Start work on 5.5

This commit is contained in:
Steffo 2020-02-04 00:15:33 +01:00
parent cdfd6c3e49
commit c3e77fa5e6
14 changed files with 173 additions and 85 deletions

41
poetry.lock generated
View file

@ -66,6 +66,21 @@ version = "2.8.0"
[package.dependencies] [package.dependencies]
pytz = ">=2015.7" pytz = ">=2015.7"
[[package]]
category = "main"
description = "Modern password hashing for your software and your servers"
name = "bcrypt"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.1.7"
[package.dependencies]
cffi = ">=1.1"
six = ">=1.4.1"
[package.extras]
tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)"]
[[package]] [[package]]
category = "main" category = "main"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
@ -913,8 +928,8 @@ python-versions = "*"
version = "2020.1.24" version = "2020.1.24"
[extras] [extras]
alchemy_easy = ["sqlalchemy", "psycopg2_binary"] alchemy_easy = ["sqlalchemy", "psycopg2_binary", "bcrypt"]
alchemy_hard = ["sqlalchemy", "psycopg2"] alchemy_hard = ["sqlalchemy", "psycopg2", "bcrypt"]
bard = ["ffmpeg_python", "youtube_dl", "eyed3"] bard = ["ffmpeg_python", "youtube_dl", "eyed3"]
coloredlogs = ["coloredlogs"] coloredlogs = ["coloredlogs"]
constellation = ["starlette", "uvicorn", "python-multipart"] constellation = ["starlette", "uvicorn", "python-multipart"]
@ -925,7 +940,7 @@ sentry = ["sentry_sdk"]
telegram = ["python_telegram_bot"] telegram = ["python_telegram_bot"]
[metadata] [metadata]
content-hash = "f275cd948fe28423a90d37d2825eabfec97e8ac0cdf52ee2d20f803d61987b40" content-hash = "218f4a253a7ef17bb871abf541ae7182f1626cd43d55417de12f9561c58ca0e9"
python-versions = "^3.8" python-versions = "^3.8"
[metadata.files] [metadata.files]
@ -963,6 +978,26 @@ babel = [
{file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"},
{file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"},
] ]
bcrypt = [
{file = "bcrypt-3.1.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7"},
{file = "bcrypt-3.1.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31"},
{file = "bcrypt-3.1.7-cp27-cp27m-win32.whl", hash = "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161"},
{file = "bcrypt-3.1.7-cp27-cp27m-win_amd64.whl", hash = "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e"},
{file = "bcrypt-3.1.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0"},
{file = "bcrypt-3.1.7-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052"},
{file = "bcrypt-3.1.7-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105"},
{file = "bcrypt-3.1.7-cp34-cp34m-win32.whl", hash = "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de"},
{file = "bcrypt-3.1.7-cp34-cp34m-win_amd64.whl", hash = "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133"},
{file = "bcrypt-3.1.7-cp35-cp35m-win32.whl", hash = "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5"},
{file = "bcrypt-3.1.7-cp35-cp35m-win_amd64.whl", hash = "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09"},
{file = "bcrypt-3.1.7-cp36-cp36m-win32.whl", hash = "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c"},
{file = "bcrypt-3.1.7-cp36-cp36m-win_amd64.whl", hash = "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89"},
{file = "bcrypt-3.1.7-cp37-cp37m-win32.whl", hash = "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294"},
{file = "bcrypt-3.1.7-cp37-cp37m-win_amd64.whl", hash = "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"},
{file = "bcrypt-3.1.7-cp38-cp38-win32.whl", hash = "sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1"},
{file = "bcrypt-3.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752"},
{file = "bcrypt-3.1.7.tar.gz", hash = "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42"},
]
certifi = [ certifi = [
{file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
{file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},

View file

@ -3,6 +3,7 @@ from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
# noinspection PyAttributeOutsideInit
#set($CAPITALIZED_NAME = $NAME.substring(0,1).toUpperCase() + $NAME.substring(1)) #set($CAPITALIZED_NAME = $NAME.substring(0,1).toUpperCase() + $NAME.substring(1))
class ${CAPITALIZED_NAME}: class ${CAPITALIZED_NAME}:
__tablename__ = "${NAME}" __tablename__ = "${NAME}"

View file

@ -44,6 +44,7 @@
sqlalchemy = {version="^1.3.10", optional=true} sqlalchemy = {version="^1.3.10", optional=true}
psycopg2 = {version="^2.8.4", optional=true} # Requires quite a bit of stuff http://initd.org/psycopg/docs/install.html#install-from-source psycopg2 = {version="^2.8.4", optional=true} # Requires quite a bit of stuff http://initd.org/psycopg/docs/install.html#install-from-source
psycopg2_binary = {version="^2.8.4", optional=true} # Prebuilt alternative to psycopg2, not recommended psycopg2_binary = {version="^2.8.4", optional=true} # Prebuilt alternative to psycopg2, not recommended
bcrypt = {version="^3.1.7", optional=true}
# constellation # constellation
starlette = {version="^0.12.13", optional=true} starlette = {version="^0.12.13", optional=true}
@ -71,8 +72,8 @@
telegram = ["python_telegram_bot"] telegram = ["python_telegram_bot"]
discord = ["discord.py", "pynacl"] discord = ["discord.py", "pynacl"]
matrix = ["matrix-nio"] matrix = ["matrix-nio"]
alchemy_easy = ["sqlalchemy", "psycopg2_binary"] alchemy_easy = ["sqlalchemy", "psycopg2_binary", "bcrypt"]
alchemy_hard = ["sqlalchemy", "psycopg2"] alchemy_hard = ["sqlalchemy", "psycopg2", "bcrypt"]
bard = ["ffmpeg_python", "youtube_dl", "eyed3"] bard = ["ffmpeg_python", "youtube_dl", "eyed3"]
constellation = ["starlette", "uvicorn", "python-multipart"] constellation = ["starlette", "uvicorn", "python-multipart"]
sentry = ["sentry_sdk"] sentry = ["sentry_sdk"]

View file

@ -1,22 +1,10 @@
# Imports go here! # Imports go here!
from .version import VersionCommand from .version import VersionCommand
from .exception import ExceptionCommand
from .excevent import ExceventCommand
from .keyboardtest import KeyboardtestCommand
# Enter the commands of your Pack here! # Enter the commands of your Pack here!
available_commands = [ available_commands = [
VersionCommand, VersionCommand,
] ]
# noinspection PyUnreachableCode
if __debug__:
available_commands = [
*available_commands,
ExceptionCommand,
ExceventCommand,
KeyboardtestCommand,
]
# Don't change this, it should automatically generate __all__ # Don't change this, it should automatically generate __all__
__all__ = [command.__name__ for command in available_commands] __all__ = [command.__name__ for command in available_commands]

View file

@ -1,11 +0,0 @@
import royalnet
from royalnet.commands import *
class ExceptionCommand(Command):
name: str = "exception"
description: str = "Raise an exception in the command."
async def run(self, args: CommandArgs, data: CommandData) -> None:
raise Exception(f"{self.interface.prefix}{self.name} was called")

View file

@ -1,12 +0,0 @@
import royalnet
from royalnet.commands import *
class ExceventCommand(Command):
name: str = "excevent"
description: str = "Call an event that raises an exception."
async def run(self, args: CommandArgs, data: CommandData) -> None:
await self.interface.call_herald_event(self.interface.name, "exception")
await data.reply("✅ Event called!")

View file

@ -1,28 +0,0 @@
from typing import *
from royalnet.commands import *
import functools
import asyncio
class KeyboardtestCommand(Command):
name: str = "keyboardtest"
description: str = "Create a new keyboard with the specified keys."
syntax: str = "{keys}+"
@staticmethod
async def echo(data: CommandData, echo: str):
await data.reply(echo)
async def run(self, args: CommandArgs, data: CommandData) -> None:
keys = []
for arg in args:
# noinspection PyTypeChecker
keys.append(KeyboardKey(interface=self.interface,
short=arg[0],
text=arg,
callback=functools.partial(self.echo, echo=arg)))
async with data.keyboard("This is a test keyboard.", keys):
await asyncio.sleep(10)
await data.reply("The keyboard is no longer in scope.")

View file

@ -0,0 +1,86 @@
from typing import *
import royalnet
import royalnet.commands as rc
import royalnet.utils as ru
from ..tables.telegram import Telegram
from ..tables.discord import Discord
class SyncCommand(rc.Command):
name: str = "sync"
description: str = "Connect your chat account to Royalnet!"
syntax: str = "{username} {password}"
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
username = args[0]
password = " ".join(args[1:])
author = await data.get_author(error_if_none=True)
user = await data.find_user(username)
try:
successful = user.test_password(password)
except ValueError:
raise rc.UserError(f"User {user} has no password set!")
if not successful:
raise rc.InvalidInputError(f"Invalid password!")
if self.interface.name == "telegram":
import telegram
message: telegram.Message = data.message
from_user: telegram.User = message.from_user
TelegramT = self.alchemy.get(Telegram)
tg_user: Telegram = await ru.asyncify(
data.session.query(TelegramT).filter_by(tg_id=from_user.id).one_or_none
)
if tg_user is None:
# Create
tg_user = TelegramT(
user=author,
tg_id=from_user.id,
first_name=from_user.first_name,
last_name=from_user.last_name,
username=from_user.username
)
data.session.add(tg_user)
else:
# Edit
tg_user.first_name = from_user.first_name
tg_user.last_name = from_user.last_name
tg_user.username = from_user.username
await data.session_commit()
await data.reply(f"↔️ Account {tg_user} synced to {author}!")
elif self.interface.name == "discord":
import discord
message: discord.Message = data.message
author: discord.User = message.author
DiscordT = self.alchemy.get(Discord)
ds_user: Discord = await ru.asyncify(
data.session.query(DiscordT).filter_by(discord_id=author.id).one_or_none
)
if ds_user is None:
# Create
ds_user = DiscordT(
user=author,
discord_id=author.id,
username=author.name,
discriminator=author.discriminator,
avatar_url=author.avatar_url
)
data.session.add(ds_user)
else:
# Edit
ds_user.username = author.name
ds_user.discriminator = author.discriminator
ds_user.avatar_url = author.avatar_url
await data.session_commit()
await data.reply(f"↔️ Account {ds_user} synced to {author}!")
elif self.interface.name == "matrix":
raise rc.UnsupportedError(f"{self} hasn't been implemented for Matrix yet")
else:
raise rc.UnsupportedError(f"Unknown interface: {self.interface.name}")

View file

@ -10,26 +10,30 @@ class Alias:
__tablename__ = "aliases" __tablename__ = "aliases"
@declared_attr @declared_attr
def royal_id(self): def user_id(self):
return Column(Integer, ForeignKey("users.uid")) return Column(Integer, ForeignKey("users.uid"), primary_key=True)
@declared_attr @declared_attr
def alias(self): def alias(self):
return Column(String, primary_key=True) return Column(String, primary_key=True)
@declared_attr @declared_attr
def royal(self): def user(self):
return relationship("User", backref="aliases") return relationship("User", backref="aliases")
@classmethod @classmethod
def find_by_alias(cls, alchemy, session, alias: str): def find_user(cls, alchemy, session, alias: str):
result = session.query(alchemy.get(cls)).filter_by(alias=alias.lower()).one_or_none() result = session.query(alchemy.get(cls)).filter_by(alias=alias.lower()).one_or_none()
if result is not None: if result is not None:
result = result.royal result = result.royal
return result return result
def __init__(self, user: str, alias: str):
self.user = user
self.alias = alias.lower()
def __repr__(self): def __repr__(self):
return f"<Alias {str(self)}>" return f"<Alias {str(self)}>"
def __str__(self): def __str__(self):
return f"{self.alias}->{self.royal_id}" return f"{self.alias}->{self.user_id}"

View file

@ -13,9 +13,13 @@ class Discord:
__tablename__ = "discord" __tablename__ = "discord"
@declared_attr @declared_attr
def royal_id(self): def user_id(self):
return Column(Integer, ForeignKey("users.uid")) return Column(Integer, ForeignKey("users.uid"))
@declared_attr
def user(self):
return relationship("User", backref="discord")
@declared_attr @declared_attr
def discord_id(self): def discord_id(self):
return Column(BigInteger, primary_key=True) return Column(BigInteger, primary_key=True)
@ -29,13 +33,9 @@ class Discord:
return Column(String) return Column(String)
@declared_attr @declared_attr
def avatar_hash(self): def avatar_url(self):
return Column(String) return Column(String)
@declared_attr
def royal(self):
return relationship("User", backref="discord")
def __repr__(self): def __repr__(self):
return f"<Discord {str(self)}>" return f"<Discord {str(self)}>"

View file

@ -13,9 +13,13 @@ class Telegram:
__tablename__ = "telegram" __tablename__ = "telegram"
@declared_attr @declared_attr
def royal_id(self): def user_id(self):
return Column(Integer, ForeignKey("users.uid")) return Column(Integer, ForeignKey("users.uid"))
@declared_attr
def user(self):
return relationship("User", backref="telegram")
@declared_attr @declared_attr
def tg_id(self): def tg_id(self):
return Column(BigInteger, primary_key=True) return Column(BigInteger, primary_key=True)
@ -32,10 +36,6 @@ class Telegram:
def username(self): def username(self):
return Column(String) return Column(String)
@declared_attr
def royal(self):
return relationship("User", backref="telegram")
def __repr__(self): def __repr__(self):
return f"<Telegram {str(self)}>" return f"<Telegram {str(self)}>"

View file

@ -1,3 +1,4 @@
import bcrypt
from sqlalchemy import Column, \ from sqlalchemy import Column, \
Integer, \ Integer, \
String, \ String, \
@ -5,6 +6,7 @@ from sqlalchemy import Column, \
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
# noinspection PyAttributeOutsideInit
class User: class User:
__tablename__ = "users" __tablename__ = "users"
@ -36,6 +38,16 @@ class User:
"avatar": self.avatar "avatar": self.avatar
} }
def set_password(self, password: str):
byte_password: bytes = bytes(password, encoding="UTF8")
self.password = bcrypt.hashpw(byte_password, bcrypt.gensalt(14))
def test_password(self, password: str):
if self.password is None:
raise ValueError("No password is set")
byte_password: bytes = bytes(password, encoding="UTF8")
return bcrypt.checkpw(byte_password, self.password)
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__qualname__} {self.username}>" return f"<{self.__class__.__qualname__} {self.username}>"

View file

@ -5,9 +5,11 @@ import asyncio as aio
import royalnet.utils as ru import royalnet.utils as ru
from .errors import UnsupportedError from .errors import UnsupportedError
from .commandinterface import CommandInterface from .commandinterface import CommandInterface
from royalnet.backpack.tables.aliases import Alias
if TYPE_CHECKING: if TYPE_CHECKING:
from .keyboardkey import KeyboardKey from .keyboardkey import KeyboardKey
from royalnet.backpack.tables.users import User
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -64,6 +66,16 @@ class CommandData:
if error_if_unavailable: if error_if_unavailable:
raise UnsupportedError(f"'{self.delete_invoking.__name__}' is not supported") raise UnsupportedError(f"'{self.delete_invoking.__name__}' is not supported")
async def find_user(self, alias: str) -> Optional["User"]:
"""Find the User having a specific Alias.
Parameters:
alias: the Alias to search for."""
return await ru.asyncify(
Alias.find_user(self._interface.alchemy, self.session, alias)
)
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def keyboard(self, text, keys: List["KeyboardKey"]): async def keyboard(self, text, keys: List["KeyboardKey"]):
yield yield

View file

@ -1 +1 @@
semantic = "5.4" semantic = "5.5"