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

Merge remote-tracking branch 'origin/master' into queue_v3

# Conflicts:
#	discordbot.py
#	template_config.ini
This commit is contained in:
Steffo 2019-04-10 15:58:35 +02:00
commit 03a486d626
79 changed files with 3657 additions and 1119 deletions

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
*.ttf filter=lfs diff=lfs merge=lfs -text
*.woff filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text

6
.gitignore vendored
View file

@ -1,9 +1,11 @@
config.ini
.idea/
.vscode/
__pycache__
diario.json
libopus-0.dll
music.opus
opusfiles/
ignored/*
markovmodel.json
ignored/
markovmodels/
logs/

2
application.wsgi Normal file
View file

@ -0,0 +1,2 @@
import webserver
application = webserver.app

31
awardfiorygi.py Normal file
View file

@ -0,0 +1,31 @@
import telegram
import configparser
import db
import strings
config = configparser.ConfigParser()
config.read("config.ini")
telegram_bot = telegram.Bot(config["Telegram"]["bot_token"])
session = db.Session()
name = input("Utente Royalnet: ")
user = session.query(db.Royal).filter(db.Royal.username == name).one()
number = int(input("Fiorygi da aggiungere: "))
user.fiorygi += number
reason = input("Motivazione: ")
fiorygi = f"fioryg{'i' if number != 1 else ''}"
telegram_bot.send_message(config["Telegram"]["main_group"],
strings.safely_format_string(strings.TELEGRAM.FIORYGI_AWARDED,
words={
"mention": user.telegram[0].mention(),
"number": str(number),
"fiorygi": fiorygi,
"reason": reason
}),
parse_mode="HTML",
disable_web_page_preview=True)
session.commit()
session.close()

79
cast.py
View file

@ -1,79 +0,0 @@
import random
import math
import db
def cast(spell_name: str, target_name: str, platform: str) -> str:
spell = spell_name.capitalize()
# Seed the rng with the spell name
# so that spells always deal the same damage
random.seed(spell)
dmg_dice = random.randrange(1, 11)
dmg_max = random.sample([4, 6, 8, 10, 12, 20, 100], 1)[0]
dmg_mod = random.randrange(math.floor(-dmg_max / 5), math.ceil(dmg_max / 5) + 1)
dmg_type = random.sample(["da fuoco", "da freddo", "elettrici", "sonici", "necrotici", "magici",
"da acido", "divini", "nucleari", "psichici", "fisici", "puri", "da taglio",
"da perforazione", "da impatto", "da caduta", "gelato", "onnipotenti", "oscuri",
"di luce", "da velocità", "da cactus", "meta", "dannosi", "da radiazione",
"tuamammici", "da maledizione", "pesanti", "leggeri", "immaginari", "da laser",
"da neutrini", "galattici", "cerebrali", "ritardati", "ritardanti"], 1)[0]
# Reseed the rng with a random value
# so that the dice roll always deals a different damage
random.seed()
total = dmg_mod
# Check for a critical hit
crit = 1
while True:
crit_die = random.randrange(1, 21)
if crit_die == 20:
crit *= 2
else:
break
for dice in range(0, dmg_dice):
total += random.randrange(1, dmg_max + 1)
if crit > 1:
if platform == "telegram":
crit_msg = f"<b>CRITICO ×{crit}{'!' * crit}</b>\n"
elif platform == "discord":
crit_msg = f"**CRITICO ×{crit}{'!' * crit}**\n"
total *= crit
else:
crit_msg = ""
if platform == "telegram":
if dmg_dice == 10 and dmg_max == 100 and dmg_mod == 20:
return f"❇️‼️ Ho lanciato <b>{spell}</b> su " \
f"<i>{target_name}</i>.\n" \
f"Una grande luce illumina il cielo, seguita poco dopo da un fungo di fumo nel luogo" \
f" in cui si trovava <i>{target_name}</i>.\n" \
f"Il fungo si espande a velocità smodata, finchè il fumo non ricopre la Terra intera e le tenebre" \
f" cadono su di essa.\n" \
f"Dopo qualche minuto, la temperatura ambiente raggiunge gli 0 °C, e continua a diminuire.\n" \
f"L'Apocalisse Nucleare è giunta, e tutto per polverizzare <i>{target_name}</i>" \
f" con <b>{spell}</b>.\n" \
f"<i>{target_name}</i> subisce 10d100+20=<b>9999</b> danni apocalittici!"
return f"❇️ Ho lanciato <b>{spell}</b> su " \
f"<i>{target_name}</i>.\n" \
f"{crit_msg}" \
f"<i>{target_name}</i> subisce {dmg_dice}d{dmg_max}" \
f"{'+' if dmg_mod > 0 else ''}{str(dmg_mod) if dmg_mod != 0 else ''}" \
f"{'×' + str(crit) if crit > 1 else ''}" \
f"=<b>{total if total > 0 else 0}</b> danni {dmg_type}!"
elif platform == "discord":
if dmg_dice == 10 and dmg_max == 100 and dmg_mod == 20:
return f"❇️‼️ Ho lanciato **{spell}** su " \
f"_{target_name}_.\n" \
f"Una grande luce illumina il cielo, seguita poco dopo da un fungo di fumo nel luogo" \
f" in cui si trovava _{target_name}_.\n" \
f"Il fungo si espande a velocità smodata, finchè il fumo non ricopre la Terra intera e le tenebre" \
f" cadono su di essa.\n" \
f"Dopo qualche minuto, la temperatura ambiente raggiunge gli 0 °C, e continua a diminuire.\n" \
f"L'Apocalisse Nucleare è giunta, e tutto per polverizzare _{target_name}_" \
f" con **{spell}**.\n" \
f"_{target_name}_ subisce 10d100+20=**9999** danni apocalittici!"
return f"❇️ Ho lanciato **{spell}** su " \
f"_{target_name}_.\n" \
f"{crit_msg}" \
f"_{target_name}_ subisce {dmg_dice}d{dmg_max}" \
f"{'+' if dmg_mod > 0 else ''}{str(dmg_mod) if dmg_mod != 0 else ''}" \
f"{'×' + str(crit) if crit > 1 else ''}" \
f"=**{total if total > 0 else 0}** danni {dmg_type}!"

14
chatasthebot.py Normal file
View file

@ -0,0 +1,14 @@
import telegram
import configparser
config = configparser.ConfigParser()
config.read("config.ini")
telegram_bot = telegram.Bot(config["Telegram"]["bot_token"])
while True:
message = input()
telegram_bot.send_message(config["Telegram"]["main_group"], message,
parse_mode="HTML",
disable_web_page_preview=True)

601
db.py
View file

@ -1,29 +1,35 @@
import datetime
import logging
import os
import typing
import time
import coloredlogs
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.hybrid import hybrid_property
# noinspection PyUnresolvedReferences
from sqlalchemy import Column, BigInteger, Integer, String, DateTime, ForeignKey, Float, Enum, create_engine, \
UniqueConstraint, PrimaryKeyConstraint, Boolean, LargeBinary, Text, Date, func
from sqlalchemy.inspection import inspect
import requests
from errors import NotFoundError, AlreadyExistingError, PrivateError
from utils.errors import NotFoundError, AlreadyExistingError, PrivateError
import re
import enum
# Both packages have different pip names
# noinspection PyPackageRequirements
from discord import User as DiscordUser
# noinspection PyPackageRequirements
from telegram import User as TelegramUser
import loldata
from dirty import Dirty
import query_discord_music
from utils.dirty import Dirty, DirtyDelta
import sql_queries
from flask import escape
import configparser
import typing
from utils import MatchmakingStatus, errors
import strings
if typing.TYPE_CHECKING:
# noinspection PyPackageRequirements
from discord import User as DiscordUser
# noinspection PyPackageRequirements
from telegram import User as TelegramUser
# Init the config reader
import configparser
config = configparser.ConfigParser()
config.read("config.ini")
@ -38,7 +44,76 @@ os.environ["COLOREDLOGS_LOG_FORMAT"] = "%(asctime)s %(levelname)s %(name)s %(mes
coloredlogs.install(level="DEBUG", logger=logger)
class Royal(Base):
def relationship_name_search(_class, keyword) -> typing.Optional[tuple]:
"""Recursively find a relationship with a given name."""
inspected = set()
def search(_mapper, chain):
inspected.add(_mapper)
relationships = _mapper.relationships
try:
return chain + (relationships[keyword],)
except KeyError:
for _relationship in set(relationships):
if _relationship.mapper in inspected:
continue
result = search(_relationship.mapper, chain + (_relationship,))
if result is not None:
return result
return None
return search(inspect(_class), tuple())
def relationship_link_chain(starting_class, ending_class) -> typing.Optional[tuple]:
"""Find the path to follow to get from the starting table to the ending table."""
inspected = set()
def search(_mapper, chain):
inspected.add(_mapper)
if _mapper.class_ == ending_class:
return chain
relationships = _mapper.relationships
for _relationship in set(relationships):
if _relationship.mapper in inspected:
continue
try:
return search(_relationship.mapper, chain + (_relationship,))
except errors.NotFoundError:
continue
raise errors.NotFoundError()
return search(inspect(starting_class), tuple())
class Mini(object):
"""Mixin for every table that has an associated mini."""
_mini_full_name = NotImplemented
_mini_name = NotImplemented
_mini_order = NotImplemented
@classmethod
def mini_get_all(cls, session: Session) -> list:
return session.query(cls).order_by(*cls._mini_order).all()
@classmethod
def mini_get_single(cls, session: Session, **kwargs):
return session.query(cls).filter_by(**kwargs).one_or_none()
@classmethod
def mini_get_single_from_royal(cls, session: Session, royal: "Royal"):
chain = relationship_link_chain(cls, Royal)
if chain is None:
chain = tuple()
start = session.query(cls)
for connection in chain:
start = start.join(connection.mapper.class_)
start = start.filter(Royal.id == royal.id)
mini = start.one()
return mini
class Royal(Base, Mini):
__tablename__ = "royals"
id = Column(Integer, primary_key=True)
@ -47,6 +122,11 @@ class Royal(Base):
role = Column(String)
fiorygi = Column(Integer, default=0)
member_since = Column(Date)
special_title = Column(String)
_mini_full_name = "Royalnet"
_mini_name = "ryg"
_mini_order = [fiorygi.desc()]
@staticmethod
def create(session: Session, username: str):
@ -58,8 +138,12 @@ class Royal(Base):
def __repr__(self):
return f"<db.Royal {self.username}>"
@classmethod
def mini_get_single_from_royal(cls, session: Session, royal: "Royal"):
return royal
class Telegram(Base):
class Telegram(Base, Mini):
__tablename__ = "telegram"
royal_id = Column(Integer, ForeignKey("royals.id"))
@ -70,8 +154,12 @@ class Telegram(Base):
last_name = Column(String)
username = Column(String)
_mini_full_name = "Telegram"
_mini_name = "tg"
_mini_order = [telegram_id]
@staticmethod
def create(session: Session, royal_username, telegram_user: TelegramUser):
def create(session: Session, royal_username, telegram_user: "TelegramUser"):
t = session.query(Telegram).filter_by(telegram_id=telegram_user.id).first()
if t is not None:
raise AlreadyExistingError(repr(t))
@ -104,8 +192,12 @@ class Telegram(Base):
else:
return self.first_name
@classmethod
def mini_get_single_from_royal(cls, session: Session, royal: "Royal"):
return royal.telegram
class Steam(Base):
class Steam(Base, Mini):
__tablename__ = "steam"
royal_id = Column(Integer, ForeignKey("royals.id"))
@ -117,6 +209,10 @@ class Steam(Base):
trade_token = Column(String)
most_played_game_id = Column(BigInteger)
_mini_full_name = "Steam"
_mini_name = "steam"
_mini_order = [steam_id]
def __repr__(self):
if not self.persona_name:
return f"<db.Steam {self.steam_id}>"
@ -192,8 +288,12 @@ class Steam(Base):
return
self.most_played_game_id = j["response"]["games"][0]["appid"]
@classmethod
def mini_get_single_from_royal(cls, session: Session, royal: "Royal"):
return royal.steam
class RocketLeague(Base):
class RocketLeague(Base, Mini):
__tablename__ = "rocketleague"
steam_id = Column(String, ForeignKey("steam.steam_id"), primary_key=True)
@ -219,6 +319,13 @@ class RocketLeague(Base):
wins = Column(Integer)
_mini_full_name = "Rocket League"
_mini_name = "rl"
_mini_order = [solo_std_mmr.desc().nullslast(),
doubles_mmr.desc().nullslast(),
standard_mmr.desc().nullslast(),
single_mmr.desc().nullslast()]
def __repr__(self):
return f"<db.RocketLeague {self.steam_id}>"
@ -254,19 +361,21 @@ class RocketLeague(Base):
return f"https://rocketleaguestats.com/assets/img/rocket_league/ranked/season_four/{rank}.png"
class Dota(Base):
class Dota(Base, Mini):
__tablename__ = "dota"
steam_id = Column(String, ForeignKey("steam.steam_id"), primary_key=True)
steam = relationship("Steam", backref="dota", lazy="joined")
rank_tier = Column(Integer)
wins = Column(Integer)
losses = Column(Integer)
most_played_hero = Column(Integer)
_mini_full_name = "DOTA 2"
_mini_name = "dota"
_mini_order = [rank_tier.desc().nullslast(), wins.desc().nullslast()]
def __repr__(self):
return f"<db.Dota {self.steam_id}>"
@ -365,7 +474,7 @@ class RomanNumerals(enum.Enum):
return self.name
class LeagueOfLegends(Base):
class LeagueOfLegends(Base, Mini):
__tablename__ = "leagueoflegends"
royal_id = Column(Integer, ForeignKey("royals.id"))
@ -375,7 +484,6 @@ class LeagueOfLegends(Base):
summoner_id = Column(String, primary_key=True)
account_id = Column(String)
summoner_name = Column(String)
level = Column(Integer)
solo_division = Column(Enum(LeagueOfLegendsRanks))
solo_rank = Column(Enum(RomanNumerals))
@ -383,9 +491,17 @@ class LeagueOfLegends(Base):
flex_rank = Column(Enum(RomanNumerals))
twtr_division = Column(Enum(LeagueOfLegendsRanks))
twtr_rank = Column(Enum(RomanNumerals))
highest_mastery_champ = Column(Integer)
_mini_full_name = "League of Legends"
_mini_name = "lol"
_mini_order = [solo_division.desc().nullslast(),
solo_rank.desc().nullslast(),
flex_division.desc().nullslast(),
flex_rank.desc().nullslast(),
twtr_division.desc().nullslast(),
twtr_rank.desc().nullslast()]
def __repr__(self):
if not self.summoner_name:
return f"<LeagueOfLegends {self.summoner_id}>"
@ -455,14 +571,14 @@ class LeagueOfLegends(Base):
def highest_mastery_champ_name(self):
champ = loldata.get_champ_by_key(self.highest_mastery_champ)
return champ["name"]
return champ["id"]
def highest_mastery_champ_image(self):
champ = loldata.get_champ_by_key(self.highest_mastery_champ)
return loldata.get_champ_icon(champ["name"])
return loldata.get_champ_icon(champ["id"])
class Osu(Base):
class Osu(Base, Mini):
__tablename__ = "osu"
royal_id = Column(Integer, ForeignKey("royals.id"), nullable=False)
@ -470,11 +586,21 @@ class Osu(Base):
osu_id = Column(Integer, primary_key=True)
osu_name = Column(String)
std_pp = Column(Float, default=0)
std_best_song = Column(BigInteger)
taiko_pp = Column(Float, default=0)
taiko_best_song = Column(BigInteger)
catch_pp = Column(Float, default=0)
catch_best_song = Column(BigInteger)
mania_pp = Column(Float, default=0)
mania_best_song = Column(BigInteger)
std_pp = Column(Float)
taiko_pp = Column(Float)
catch_pp = Column(Float)
mania_pp = Column(Float)
_mini_full_name = "osu!"
_mini_name = "osu"
_mini_order = [mania_pp.desc().nullslast(),
std_pp.desc().nullslast(),
taiko_pp.desc().nullslast(),
catch_pp.desc().nullslast()]
@staticmethod
def create(session: Session, royal_id, osu_name):
@ -504,23 +630,32 @@ class Osu(Base):
# noinspection PyUnusedLocal
def update(self, session=None):
std = DirtyDelta(self.std_pp)
taiko = DirtyDelta(self.taiko_pp)
catch = DirtyDelta(self.catch_pp)
mania = DirtyDelta(self.mania_pp)
r0 = requests.get(f"https://osu.ppy.sh/api/get_user?k={config['Osu!']['ppy_api_key']}&u={self.osu_name}&m=0")
r0.raise_for_status()
j0 = r0.json()[0]
r1 = requests.get(f"https://osu.ppy.sh/api/get_user?k={config['Osu!']['ppy_api_key']}&u={self.osu_name}&m=1")
r1.raise_for_status()
j1 = r1.json()[0]
r2 = requests.get(f"https://osu.ppy.sh/api/get_user?k={config['Osu!']['ppy_api_key']}&u={self.osu_name}&m=2")
r2.raise_for_status()
j2 = r2.json()[0]
r3 = requests.get(f"https://osu.ppy.sh/api/get_user?k={config['Osu!']['ppy_api_key']}&u={self.osu_name}&m=3")
r3.raise_for_status()
j0 = r0.json()[0]
j1 = r1.json()[0]
j2 = r2.json()[0]
j3 = r3.json()[0]
self.osu_name = j0["username"]
self.std_pp = j0["pp_raw"]
self.taiko_pp = j1["pp_raw"]
self.catch_pp = j2["pp_raw"]
self.mania_pp = j3["pp_raw"]
std.value = float(j0["pp_raw"] or 0)
taiko.value = float(j1["pp_raw"] or 0)
catch.value = float(j2["pp_raw"] or 0)
mania.value = float(j3["pp_raw"] or 0)
self.std_pp = std.value
self.taiko_pp = taiko.value
self.catch_pp = catch.value
self.mania_pp = mania.value
return std, taiko, catch, mania
def __repr__(self):
if not self.osu_name:
@ -528,7 +663,7 @@ class Osu(Base):
return f"<db.Osu {self.osu_name}>"
class Discord(Base):
class Discord(Base, Mini):
__tablename__ = "discord"
__table_args__ = tuple(UniqueConstraint("name", "discriminator"))
@ -540,6 +675,10 @@ class Discord(Base):
discriminator = Column(Integer)
avatar_hex = Column(String)
_mini_full_name = "Discord"
_mini_name = "discord"
_mini_order = [discord_id]
def __str__(self):
return f"{self.name}#{self.discriminator}"
@ -547,7 +686,7 @@ class Discord(Base):
return f"<db.Discord {self.discord_id}>"
@staticmethod
def create(session: Session, royal_username, discord_user: DiscordUser):
def create(session: Session, royal_username, discord_user: "DiscordUser"):
d = session.query(Discord).filter(Discord.discord_id == discord_user.id).first()
if d is not None:
raise AlreadyExistingError(repr(d))
@ -572,8 +711,20 @@ class Discord(Base):
return "https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png"
return f"https://cdn.discordapp.com/avatars/{self.discord_id}/{self.avatar_hex}.png?size={size}"
@classmethod
def mini_get_all(cls, session: Session):
return [dict(row) for row in session.execute(sql_queries.all_music_query)]
class Overwatch(Base):
@classmethod
def mini_get_single(cls, session: Session, **kwargs):
return session.execute(sql_queries.one_music_query, {"royal": kwargs["royal"].id}).fetchone()
@classmethod
def mini_get_single_from_royal(cls, session: Session, royal: "Royal"):
return cls.mini_get_single(session, royal=royal)
class Overwatch(Base, Mini):
__tablename__ = "overwatch"
royal_id = Column(Integer, ForeignKey("royals.id"), nullable=False)
@ -582,10 +733,13 @@ class Overwatch(Base):
battletag = Column(String, primary_key=True)
discriminator = Column(Integer, primary_key=True)
icon = Column(String)
level = Column(Integer)
rank = Column(Integer)
_mini_full_name = "Overwatch"
_mini_name = "ow"
_mini_order = [rank.desc().nullslast(), level.desc()]
def __str__(self, separator="#"):
return f"{self.battletag}{separator}{self.discriminator}"
@ -594,45 +748,14 @@ class Overwatch(Base):
@staticmethod
def create(session: Session, royal_id, battletag, discriminator=None):
if discriminator is None:
battletag, discriminator = battletag.split("#", 1)
o = session.query(Overwatch).filter_by(battletag=battletag, discriminator=discriminator).first()
if o is not None:
raise AlreadyExistingError(repr(o))
o = Overwatch(royal_id=royal_id,
battletag=battletag,
discriminator=discriminator)
o.update()
return o
raise NotImplementedError()
def icon_url(self):
return f"https://d1u1mce87gyfbn.cloudfront.net/game/unlocks/{self.icon}.png"
# noinspection PyUnusedLocal
def update(self, session=None):
r = requests.get(f"https://owapi.net/api/v3/u/{self.battletag}-{self.discriminator}/stats", headers={
"User-Agent": "Royal-Bot/4.1",
"From": "ste.pigozzi@gmail.com"
})
r.raise_for_status()
try:
j = r.json()["eu"]["stats"].get("competitive")
if j is None:
logger.debug(f"No stats for {repr(self)}, skipping...")
return
if not j["game_stats"]:
logger.debug(f"No stats for {repr(self)}, skipping...")
return
j = j["overall_stats"]
except TypeError:
logger.debug(f"No stats for {repr(self)}, skipping...")
return
try:
self.icon = re.search(r"https://.+\.cloudfront\.net/game/unlocks/(0x[0-9A-F]+)\.png", j["avatar"]).group(1)
except AttributeError:
logger.debug(f"No icon available for {repr(self)}.")
self.level = j["prestige"] * 100 + j["level"]
self.rank = j["comprank"]
raise NotImplementedError()
def rank_url(self):
if self.rank < 1500:
@ -686,48 +809,15 @@ class Diario(Base):
def __str__(self):
return f"{self.id} - {self.timestamp} - {self.author}: {self.text}"
def to_telegram(self):
return '<a href="https://ryg.steffo.eu/diario#entry-{id}">#{id}</a> di {author}\n{text}'.format(
id=self.id,
author=f"<b>{self.author}</b>" if self.author is not None else strings.DIARIO.ANONYMOUS,
text=escape(self.text))
def to_html(self):
return str(escape(self.text)).replace("\n", "<br>")
@staticmethod
def import_from_json(file):
import json
session = Session()
file = open(file, "r")
j = json.load(file)
author_ids = {
"@Steffo": 25167391,
"@GoodBalu": 19611986,
"@gattopandacorno": 200821462,
"@Albertino04": 131057096,
"@Francesco_Cuoghi": 48371848,
"@VenomousDoc": 48371848,
"@MaxSensei": 1258401,
"@Protoh": 125711787,
"@McspKap": 304117728,
"@FrankRekt": 31436195,
"@EvilBalu": 26842090,
"@Dailir": 135816455,
"@Paltri": 186843362,
"@Doom_darth_vader": 165792255,
"@httpIma": 292086686,
"@DavidoMessori": 509208316,
"@DavidoNiichan": 509208316,
"@Peraemela99": 63804599,
"@infopz": 20403805,
"@Baithoven": 121537369,
"@Tauei": 102833717
}
for n, entry in enumerate(j):
author = author_ids[entry["sender"]] if "sender" in entry and entry["sender"] in author_ids else None
d = Diario(timestamp=datetime.datetime.fromtimestamp(float(entry["timestamp"])),
author_id=author,
text=entry["text"])
print(f"{n} - {d}")
session.add(d)
session.commit()
session.close()
class BaluRage(Base):
__tablename__ = "balurage"
@ -770,7 +860,7 @@ class VoteQuestion(Base):
text = f"<b>{self.question}</b>\n\n"
none, yes, no, abstain = 0, 0, 0, 0
if self.message_id is not None:
query = session.execute(query_discord_music.vote_answers, {"message_id": self.message_id})
query = session.execute(sql_queries.vote_answers, {"message_id": self.message_id})
for record in query:
if record["username"] == "royalgamesbot":
continue
@ -837,6 +927,7 @@ class WikiEntry(Base):
key = Column(String, primary_key=True)
content = Column(Text, nullable=False)
locked = Column(Boolean, default=False)
def __repr__(self):
return f"<WikiEntry {self.key}>"
@ -894,23 +985,6 @@ class Reddit(Base):
return f"<Reddit u/{self.username}>"
class GameLog(Base):
__tablename__ = "gamelog"
royal_id = Column(Integer, ForeignKey("royals.id"))
royal = relationship("Royal", backref="gamelog", lazy="joined")
username = Column(String, primary_key=True)
owned_games = Column(Integer)
unfinished_games = Column(Integer)
beaten_games = Column(Integer)
completed_games = Column(Integer)
mastered_games = Column(Integer)
def __repr__(self):
return f"<GameLog {self.username}>"
class ParsedRedditPost(Base):
__tablename__ = "parsedredditposts"
@ -935,7 +1009,7 @@ class LoginToken(Base):
return f"<LoginToken for {self.royal.username}>"
class Halloween(Base):
class Halloween(Base, Mini):
"""This is some nice spaghetti, don't you think?"""
__tablename__ = "halloween"
@ -943,7 +1017,6 @@ class Halloween(Base):
royal = relationship("Royal", backref="halloween", lazy="joined")
first_trigger = Column(DateTime)
puzzle_piece_a = Column(DateTime)
puzzle_piece_b = Column(DateTime)
puzzle_piece_c = Column(DateTime)
@ -951,9 +1024,12 @@ class Halloween(Base):
puzzle_piece_e = Column(DateTime)
puzzle_piece_f = Column(DateTime)
puzzle_piece_g = Column(DateTime)
boss_battle = Column(DateTime)
_mini_full_name = "Halloween 2018"
_mini_name = "halloween2018"
_mini_order = [first_trigger]
def __getitem__(self, item):
if not isinstance(item, int):
raise TypeError("The index should be an int")
@ -1036,6 +1112,267 @@ class ActivityReport(Base):
return f"<ActivityReport at {self.timestamp.isoformat()}>"
class Quest(Base):
__tablename__ = "quests"
id = Column(Integer, primary_key=True)
title = Column(String)
description = Column(Text)
reward = Column(Integer)
expiration_date = Column(DateTime)
def __repr__(self):
return f"<Quest {self.id}: {self.title}>"
class Terraria13(Base, Mini):
__tablename__ = "terraria13"
game_name = "Terraria 13"
royal_id = Column(Integer, ForeignKey("royals.id"), primary_key=True)
royal = relationship("Royal", backref="terraria13", lazy="joined")
character_name = Column(String)
contribution = Column(Integer)
_mini_full_name = "Terraria 13"
_mini_name = "terraria13"
_mini_order = [contribution.desc()]
def __repr__(self):
return f"<Terraria13 {self.character_name} {self.contribution}>"
class Minecraft2019(Base, Mini):
__tablename__ = "minecraft2019"
game_name = "Minecraft 2019"
royal_id = Column(Integer, ForeignKey("royals.id"), primary_key=True)
royal = relationship("Royal", backref="minecraft2019", lazy="joined")
character_name = Column(String)
contribution = Column(Integer)
_mini_full_name = "Minecraft 2019"
_mini_name = "minecraft2019"
_mini_order = [contribution.desc()]
def __repr__(self):
return f"<Terraria13 {self.character_name} {self.contribution}>"
mini_list = [Royal, Telegram, Steam, Dota, LeagueOfLegends, Osu, Discord, Overwatch, Halloween,
Terraria13]
class Match(Base):
__tablename__ = "matches"
id = Column(Integer, primary_key=True)
timestamp = Column(DateTime)
creator_id = Column(BigInteger, ForeignKey("telegram.telegram_id"))
creator = relationship("Telegram", backref="matches_created", lazy="joined")
match_title = Column(String)
match_desc = Column(Text)
min_players = Column(Integer)
max_players = Column(Integer)
closed = Column(Boolean, default=False)
message_id = Column(BigInteger)
def active_players_count(self):
count = 0
for player in self.players:
if player.status == MatchmakingStatus.READY \
or player.status == MatchmakingStatus.WAIT_FOR_ME \
or player.status == MatchmakingStatus.SOMEONE_ELSE:
count += 1
return count
def generate_text(self, session):
player_list = session.query(MatchPartecipation).filter_by(match=self).all()
title = f"<b>{self.match_title}</b>"
description = f"{self.match_desc}\n" if self.match_desc else ""
if self.min_players:
minimum = f" <i>(minimo {self.min_players})</i>"
else:
minimum = ""
plist = f"Giocatori{minimum}:\n"
ignore_count = 0
for player in player_list:
icon = strings.MATCHMAKING.ENUM_TO_EMOJIS[player.status]
if player.status == MatchmakingStatus.IGNORED:
ignore_count += 1
continue
plist += f"{icon} {player.user.royal.username}\n"
if ignore_count:
ignored = f"❌ <i>{ignore_count} persone non sono interessate.</i>\n"
else:
ignored = ""
if self.max_players:
players = f"[{self.active_players_count()}/{self.max_players}]"
else:
players = f"[{self.active_players_count()}]"
close = f"[matchmaking terminato]\n" if self.closed else ""
message = f"{title} {players}\n" \
f"{description}\n" \
f"{plist}\n" \
f"{ignored}" \
f"{close}"
return message
def __repr__(self):
return f"<Match {self.match_title}>"
def format_dict(self) -> typing.Dict[str, str]:
return {
"id": str(self.id),
"timestamp": self.timestamp.isoformat(),
"creator_id": str(self.creator_id),
"creator_name": self.creator.mention(),
"match_title": self.match_title,
"match_desc": self.match_desc if self.match_desc is not None else "",
"min_players": str(self.min_players) if self.min_players is not None else "",
"max_players": str(self.max_players) if self.max_players is not None else "",
"active_players": str(self.active_players_count()),
"players": str(len(self.players))
}
class MatchPartecipation(Base):
__tablename__ = "matchpartecipations"
__table_args__ = (PrimaryKeyConstraint("user_id", "match_id"),)
user_id = Column(BigInteger, ForeignKey("telegram.telegram_id"))
user = relationship("Telegram", backref="match_partecipations", lazy="joined")
match_id = Column(Integer, ForeignKey("matches.id"))
match = relationship("Match", backref="players", lazy="joined")
status = Column(Integer)
def __repr__(self):
return f"<MatchPartecipation {self.user.username} in {self.match.match_title}>"
class BindingOfIsaac(Base, Mini):
__tablename__ = "bindingofisaac"
steam_id = Column(String, ForeignKey("steam.steam_id"), primary_key=True)
steam = relationship("Steam", backref="binding_of_isaac", lazy="joined")
daily_victories = Column(Integer, default=0)
def __repr__(self):
return f"<db.BindingOfIsaac {self.steam_id}>"
def recalc_victories(self):
raise NotImplementedError() # TODO
class BindingOfIsaacRun(Base):
__tablename__ = "bindingofisaacruns"
__table_args__ = (PrimaryKeyConstraint("date", "player_id"),)
date = Column(Date)
player_id = Column(String, ForeignKey("bindingofisaac.steam_id"))
player = relationship("BindingOfIsaac", backref="runs", lazy="joined")
score = Column(BigInteger)
# time = Column(???)
def __repr__(self):
return f"<db.BindingOfIsaacRun {self.player_id}: {self.score}>"
class Brawlhalla(Base, Mini):
__tablename__ = "brawlhalla"
steam_id = Column(String, ForeignKey("steam.steam_id"), primary_key=True)
steam = relationship("Steam", backref="brawlhalla", lazy="joined")
brawlhalla_id = Column(BigInteger)
name = Column(String)
level = Column(Integer)
main_legend_name = Column(String)
main_legend_level = Column(Integer)
ranked_plays = Column(Integer)
ranked_wins = Column(Integer)
rating = Column(Integer)
best_team_partner_id = Column(BigInteger)
best_team_rating = Column(Integer)
_mini_full_name = "Brawlhalla"
_mini_name = "brawlhalla"
_mini_order = [rating.desc(), level.desc()]
def __repr__(self):
return f"<db.Brawlhalla {self.name}>"
@hybrid_property
def best_team_partner(self) -> typing.Optional["Brawlhalla"]:
# FIXME: dirty hack here
session = Session()
b = session.query(Brawlhalla).filter_by(brawlhalla_id=self.best_team_partner_id).one_or_none()
session.close()
return b
@staticmethod
def init_table():
session = Session()
steam = session.query(Steam).all()
for user in steam:
j = requests.get("https://api.brawlhalla.com/search", params={
"steamid": user.steam_id,
"api_key": config["Brawlhalla"]["brawlhalla_api_key"]
}).json()
if not j:
continue
b = session.query(Brawlhalla).filter_by(steam=user).one_or_none()
if b is None:
b = Brawlhalla(steam_id=user.steam_id, brawlhalla_id=j["brawlhalla_id"], name=j["name"])
session.add(b)
time.sleep(1)
session.commit()
def update(self, session=None):
j = requests.get(f"https://api.brawlhalla.com/player/{self.brawlhalla_id}/stats?api_key={config['Brawlhalla']['brawlhalla_api_key']}").json()
self.name = j.get("name", "unknown")
self.level = j.get("level", 0)
try:
main_legend = max(j.get("legends", []), key=lambda l: l.get("level", 0))
self.main_legend_level = main_legend.get("level", 0)
self.main_legend_name = main_legend.get("legend_name_key", "unknown")
except ValueError:
pass
j = requests.get(f"https://api.brawlhalla.com/player/{self.brawlhalla_id}/ranked?api_key={config['Brawlhalla']['brawlhalla_api_key']}").json()
self.ranked_plays = j.get("games", 0)
self.ranked_wins = j.get("wins", 0)
rating = DirtyDelta(self.rating)
rating.value = j.get("rating")
self.rating = rating.value
best_team_data = Dirty((self.best_team_partner_id, self.best_team_rating))
try:
current_best_team = max(j.get("2v2", []), key=lambda t: t.get("rating", 0))
if current_best_team["brawlhalla_id_one"] == self.brawlhalla_id:
self.best_team_partner_id = current_best_team["brawlhalla_id_two"]
else:
self.best_team_partner_id = current_best_team["brawlhalla_id_one"]
self.best_team_rating = current_best_team["rating"]
best_team_data.value = (self.best_team_partner_id, self.best_team_rating)
except ValueError:
pass
return rating, best_team_data
# If run as script, create all the tables in the db
if __name__ == "__main__":
print("Creating new tables...")

View file

@ -1,13 +0,0 @@
class Dirty:
def __init__(self, initial_value):
self.initial_value = initial_value
self.value = initial_value
def is_clean(self):
return self.initial_value == self.value
def is_dirty(self):
return not self.is_clean()
def __bool__(self):
return self.is_dirty()

55
isaacfetcher.py Normal file
View file

@ -0,0 +1,55 @@
import steamleaderboards
import datetime
import db
import time
import telegram
import configparser
def dates_generator(last_date: datetime.date):
date = datetime.date.today()
while True:
if date < last_date:
return
yield date
date -= datetime.timedelta(days=1)
session = db.Session()
players = session.query(db.BindingOfIsaac).all()
config = configparser.ConfigParser()
config.read("config.ini")
print("Fetching leaderboardgroup...")
isaac = steamleaderboards.LeaderboardGroup(250900)
telegram_bot = telegram.Bot(config["Telegram"]["bot_token"])
for date in dates_generator(datetime.date(year=2017, month=1, day=3)):
lb_name = "{year:04d}{month:02d}{day:02d}_scores+".format(year=date.year, month=date.month, day=date.day)
print(f"Fetching {lb_name}...")
leaderboard = isaac.get(name=lb_name)
print(f"Finding players...")
runs = []
for player in players:
entry = leaderboard.find_entry(player.steam_id)
if entry is None:
continue
print(f"Found new entry: {entry}")
run = db.BindingOfIsaacRun(player=player, score=entry.score, date=date)
runs.append(run)
session.add(run)
if len(runs) > 1:
runs.sort(key=lambda x: x.score)
best = runs[-1]
best.player.daily_victories += 1
try:
telegram_bot.send_message(config["Telegram"]["main_group"],
f"🏆 <b>{best.player.steam.persona_name}</b> ha vinto la Daily Run di Isaac del {date.isoformat()}!",
parse_mode="HTML", disable_web_page_preview=True, disable_notification=True)
except Exception:
pass
session.commit()
print("Sleeping 5s...")
time.sleep(5)

View file

@ -1,4 +1,4 @@
import errors
from utils import errors
champions = {"type": "champion", "format": "standAloneComplex", "version": "8.19.1", "data": {
"Aatrox": {"version": "8.19.1", "id": "Aatrox", "key": "266", "name": "Aatrox", "title": "the Darkin Blade",

View file

@ -6,7 +6,7 @@ user = session.query(db.Royal).filter_by(username=username).one_or_none()
if user is None:
user = db.Royal.create(session, username)
session.add(user)
session.commit()
session.flush()
try:
steam = db.Steam.create(session, user.id, input("Steam ID 1: "))
except Exception as e:

View file

@ -1,7 +1,7 @@
python-telegram-bot
flask
sqlalchemy
youtube-dl
youtube_dl==2019.02.18
requests
psycopg2-binary
PyNaCl
@ -16,3 +16,5 @@ dice
raven[flask]
coloredlogs
sentry_sdk
steamleaderboards
git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py[voice]

View file

@ -1,4 +1,4 @@
all_query = """SELECT
all_music_query = """SELECT
discord.royal_id,
discord.discord_id,
discord.name,
@ -59,7 +59,7 @@ LEFT JOIN
# TODO: can and should be optimized, but I'm too lazy for that
one_query = """SELECT
one_music_query = """SELECT
discord.royal_id,
discord.discord_id,
discord.name,
@ -141,3 +141,18 @@ LEFT JOIN
WHERE votequestion.message_id = :message_id
) answer ON telegram.telegram_id = answer.user_id
ORDER BY answer.choice;"""
activity_by_hour = """SELECT AVG(discord_members_online) online_members_avg,
AVG(discord_members_ingame) ingame_members_avg,
AVG(discord_members_cv) cv_members_avg,
AVG(discord_channels_used) channels_used_avg,
AVG(discord_cv) cv_avg,
extract(hour from timestamp) h
FROM (
SELECT *,
extract(month from timestamp) month_
FROM activityreports
) withmonth
WHERE withmonth.month_ = :current_month
GROUP BY h
ORDER BY h;"""

View file

@ -1,46 +0,0 @@
listona = ["della secca", "del seccatore", "del secchiello", "del secchio", "del secchione", "del secondino",
"del sedano", "del sedativo", "della sedia", "del sedicente", "del sedile", "della sega", "del segale",
"della segatura", "della seggiola", "del seggiolino", "della seggiovia", "della segheria", "del seghetto",
"del segnalibro", "del segnaposto", "del segno", "del segretario", "della segreteria", "del seguace",
"del segugio", "della selce", "della sella", "della selz", "della selva", "della selvaggina", "del semaforo",
"del seme", "del semifreddo", "del seminario", "della seminarista", "della semola", "del semolino",
"del semplicione", "della senape", "del senatore", "del seno", "del sensore", "della sentenza",
"della sentinella", "del sentore", "della seppia", "del sequestratore", "della serenata", "del sergente",
"del sermone", "della serpe", "del serpente", "della serpentina", "della serra", "del serraglio",
"del serramanico", "della serranda", "della serratura", "del servitore", "della servitù", "del servizievole",
"del servo", "del set", "della seta", "della setola", "del sidecar", "del siderurgico", "del sidro",
"della siepe", "del sifone", "della sigaretta", "del sigaro", "del sigillo", "della signora",
"della signorina", "del silenziatore", "della silhouette", "del silicio", "del silicone", "del siluro",
"della sinagoga", "della sindacalista", "del sindacato", "del sindaco", "della sindrome", "della sinfonia",
"del sipario", "del sire", "della sirena", "della siringa", "del sismografo", "del sobborgo",
"del sobillatore", "del sobrio", "del soccorritore", "del socio", "del sociologo", "della soda", "del sofà",
"della soffitta", "del software", "dello sogghignare", "del soggiorno", "della sogliola", "del sognatore",
"della soia", "del solaio", "del solco", "del soldato", "del soldo", "del sole", "della soletta",
"della solista", "del solitario", "del sollazzare", "del sollazzo", "del sollecito", "del solleone",
"del solletico", "del sollevare", "del sollievo", "del solstizio", "del solubile", "del solvente",
"della soluzione", "del somaro", "del sombrero", "del sommergibile", "del sommo", "della sommossa",
"del sommozzatore", "del sonar", "della sonda", "del sondaggio", "del sondare", "del sonnacchioso",
"del sonnambulo", "del sonnellino", "del sonnifero", "del sonno", "della sonnolenza", "del sontuoso",
"del soppalco", "del soprabito", "del sopracciglio", "del sopraffare", "del sopraffino", "del sopraluogo",
"del sopramobile", "del soprannome", "del soprano", "del soprappensiero", "del soprassalto",
"del soprassedere", "del sopravvento", "del sopravvivere", "del soqquadro", "del sorbetto", "del sordido",
"della sordina", "del sordo", "della sorella", "della sorgente", "del sornione", "del sorpasso",
"della sorpresa", "del sorreggere", "del sorridere", "della sorsata", "del sorteggio", "del sortilegio",
"del sorvegliante", "del sorvolare", "del sosia", "del sospettoso", "del sospirare", "della sosta",
"della sostanza", "del sostegno", "del sostenitore", "del sostituto", "del sottaceto", "della sottana",
"del sotterfugio", "del sotterraneo", "del sottile", "del sottilizzare", "del sottintendere",
"del sottobanco", "del sottobosco", "del sottomarino", "del sottopassaggio", "del sottoposto",
"del sottoscala", "della sottoscrizione", "del sottostare", "del sottosuolo", "del sottotetto",
"del sottotitolo", "del sottovalutare", "del sottovaso", "della sottoveste", "del sottovuoto",
"del sottufficiale", "della soubrette", "del souvenir", "del soverchiare", "del sovrano", "del sovrapprezzo",
"della sovvenzione", "del sovversivo", "del sozzo", "dello suadente", "del sub", "del subalterno",
"del subbuglio", "del subdolo", "del sublime", "del suburbano", "del successore", "del succo",
"della succube", "del succulento", "della succursale", "del sudario", "della sudditanza", "del suddito",
"del sudicio", "del suffisso", "del suffragio", "del suffumigio", "del suggeritore", "del sughero",
"del sugo", "del suino", "della suite", "del sulfureo", "del sultano", "di Steffo", "di Spaggia",
"di Sabrina", "del sas", "del ses", "del sis", "del sos", "del sus", "della supremazia", "del Santissimo",
"della scatola", "del supercalifragilistichespiralidoso", "del sale", "del salame", "di (Town of) Salem",
"di Stronghold", "di SOMA", "dei Saints", "di S.T.A.L.K.E.R.", "di Sanctum", "dei Sims", "di Sid",
"delle Skullgirls", "di Sonic", "di Spiral (Knights)", "di Spore", "di Starbound", "di SimCity", "di Sensei",
"di Ssssssssssssss... Boom! E' esploso il dizionario", "della scala", "di Sakura", "di Suzie", "di Shinji",
"del senpai", "del support", "di Superman"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dea386e1d6ef2f0004aa045385f37f559247d9f5a17010174b4be5b6465dfeb6
size 50018

219
static/LogoRoyalGames.svg Normal file
View file

@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1000"
height="1000"
viewBox="0 0 264.58332 264.58333"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="LogoRoyalGames.svg"
inkscape:export-filename="C:\Users\stepi\Pictures\LogoRoyalGames.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<title
id="title5190">Royal Games</title>
<defs
id="defs2">
<linearGradient
id="linearGradient5149">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5147" />
</linearGradient>
<linearGradient
id="linearGradient5042">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5040" />
</linearGradient>
<linearGradient
id="linearGradient4971">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4969" />
</linearGradient>
<linearGradient
id="Principale"
osb:paint="solid">
<stop
style="stop-color:#a1ccff;stop-opacity:1;"
offset="0"
id="stop4895" />
</linearGradient>
<linearGradient
id="Sfondo"
osb:paint="solid">
<stop
style="stop-color:#0d193b;stop-opacity:1;"
offset="0"
id="stop817" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient821"
x1="34.745037"
y1="124.61114"
x2="175.3288"
y2="124.61114"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.8820335,0,0,2.1427478,-65.391324,-102.30192)" />
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5332"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.9722906,-1.7276436,1.7276436,4.9722906,-481.91887,-479.70806)"
x1="55.743366"
y1="152.60051"
x2="62.71452"
y2="152.60051" />
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5334"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.8721103,-1.6928355,1.6928355,4.8721103,-559.10679,-570.69534)"
x1="65.251831"
y1="186.99634"
x2="72.946449"
y2="186.99634" />
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient5336"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(5.0448436,-1.7528523,1.7528523,5.0448436,-614.18765,-850.35741)"
x1="63.593018"
y1="224.03799"
x2="71.024086"
y2="224.03799" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath5457">
<use
x="0"
y="0"
xlink:href="#g5453"
id="use5459"
width="100%"
height="100%" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="287.44765"
inkscape:cy="453.73086"
inkscape:document-units="px"
inkscape:current-layer="main-layer"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:measure-start="384.018,406.607"
inkscape:measure-end="388.429,418.649"
showguides="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="false"
inkscape:snap-bbox="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-nodes="true"
inkscape:snap-grids="false"
inkscape:snap-others="false"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1272"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-page="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid1186"
originx="0"
originy="1.5e-005" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Royal Games</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Main Layer"
inkscape:groupmode="layer"
id="main-layer"
transform="translate(0,-32.416689)"
style="display:inline">
<rect
style="fill:url(#linearGradient821);fill-opacity:1;stroke-width:0.53132677"
id="background"
width="264.58334"
height="264.58334"
x="0"
y="32.416656" />
<g
id="logo"
transform="matrix(0.88827897,0,0,0.88827923,7.5532469,20.037367)">
<path
inkscape:connector-curvature="0"
id="star"
d="m 55.674623,47.499965 23.70863,68.248035 5.283398,-7.9251 5.291666,7.9375 5.291667,-7.9375 v 27.78125 c 0,0 -3.96081,0.36871 -8.719881,0.71727 l 36.012267,103.66498 105.39511,38.24676 -69.9983,-88.16309 66.02232,-87.15178 -103.69547,36.69078 z"
style="display:inline;opacity:0.98999999;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
inkscape:connector-curvature="0"
id="crown"
d="m 86.530103,136.32142 -7.14685,-20.57342 -0.0083,0.0124 -5.291667,-7.93751 -5.291666,7.93751 -5.291622,-7.9375 -5.291667,7.93751 -5.291666,-7.93751 v 27.78126 c 0,0 14.097344,1.32291 21.166666,1.32291 3.331782,0 8.204375,-0.29493 12.446786,-0.60565 z"
style="display:inline;opacity:1;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
d="m 72.507099,179.7588 3.39258,9.76411 q 0.982062,2.82645 1.575007,3.45555 0.609709,0.59447 1.635113,0.72773 1.025404,0.13327 3.415041,-0.69702 l 0.330331,0.95072 -17.832346,6.19592 -0.330331,-0.95071 q 2.415332,-0.83922 3.111495,-1.57065 0.712931,-0.76606 0.796984,-1.60159 0.109751,-0.84445 -0.872311,-3.6709 l -7.856504,-22.61162 q -0.982062,-2.82645 -1.591772,-3.42092 -0.592945,-0.6291 -1.618348,-0.76236 -1.025404,-0.13327 -3.415041,0.69702 l -0.330331,-0.95072 16.187864,-5.62454 q 6.320976,-2.19625 9.553754,-2.3404 3.232782,-0.14414 5.904185,1.577 2.662477,1.69545 3.7606,4.85593 1.339177,3.85425 -0.560957,7.33657 -1.219429,2.20912 -4.128851,4.11271 l 12.474459,8.88354 q 2.439061,1.71548 3.329239,2.03972 1.330261,0.4305 2.744571,0.0255 l 0.33033,0.95072 -10.971772,3.81219 -16.746132,-11.97806 z m -5.937017,-17.08719 5.303137,15.26284 1.464616,-0.50889 q 3.571608,-1.24097 5.121371,-2.49936 1.540832,-1.2841 1.953701,-3.32815 0.429633,-2.07869 -0.525644,-4.82805 -1.383817,-3.98273 -3.920215,-5.23243 -2.5107,-1.25862 -6.647599,0.17876 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5332);fill-opacity:1;stroke:none;stroke-width:1.39273477"
id="letter-r"
inkscape:connector-curvature="0" />
<path
d="m 130.68189,137.70908 0.33514,0.96457 q -1.66517,0.84148 -2.33195,1.94973 -0.92304,1.54784 -2.04475,7.40121 l -3.217,15.4634 2.97106,8.55096 q 0.9511,2.73733 1.47316,3.3156 0.513,0.55218 1.52862,0.78362 1.03243,0.19649 2.4402,-0.29264 l 1.98136,-0.68843 0.33514,0.96457 -19.83925,6.89323 -0.33514,-0.96457 1.85095,-0.64312 q 1.5642,-0.54349 2.28645,-1.408 0.55091,-0.60076 0.63405,-1.62275 0.0692,-0.72522 -0.85477,-3.38436 l -2.46381,-7.09103 -13.57909,-12.2571 q -4.034666,-3.62353 -5.323131,-4.05231 -1.297544,-0.45491 -3.001119,0.10777 l -0.335146,-0.96457 16.945476,-5.88778 0.33515,0.96457 -0.75607,0.2627 q -1.53812,0.53443 -2.00989,1.19475 -0.44555,0.65179 -0.27353,1.14689 0.32609,0.93849 3.72516,3.99398 l 10.45421,9.48617 2.65629,-12.87275 q 1.01366,-4.73481 0.5698,-6.01227 -0.24455,-0.70385 -1.08543,-0.93761 -1.11283,-0.34377 -3.53051,0.35017 l -0.33515,-0.96457 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5336);fill-opacity:1;stroke:none;stroke-width:1.41305673"
id="letter-y"
inkscape:connector-curvature="0" />
<path
d="m 102.64486,200.4253 4.18153,12.03476 -0.93156,0.32368 q -3.40148,-4.34864 -7.667536,-5.7445 -4.266052,-1.39586 -8.269247,-0.005 -3.826954,1.32969 -5.617541,4.37848 -1.79933,3.02363 -1.518338,7.24317 0.280991,4.21955 1.689414,8.2731 1.705854,4.90957 4.149964,8.20824 2.444105,3.29865 5.615805,4.1436 3.196874,0.83619 6.746879,-0.39727 1.23369,-0.42865 2.43025,-1.12657 1.21299,-0.73185 2.39761,-1.70779 l -2.46693,-7.1 q -0.69983,-2.01419 -1.17799,-2.49704 -0.4869,-0.50803 -1.53432,-0.68021 -1.022237,-0.18094 -2.255926,0.24771 l -0.881207,0.30618 -0.323675,-0.93156 16.591858,-5.76491 0.32368,0.93156 q -1.84457,0.78197 -2.45992,1.44726 -0.59892,0.63134 -0.68683,1.6777 -0.0604,0.5571 0.58696,2.42022 l 2.46693,7.1 q -2.76567,2.59752 -6.06198,4.56113 -3.26239,1.98004 -7.06416,3.30099 -4.859227,1.68835 -8.536819,1.49887 -3.661163,-0.22341 -6.872121,-1.50618 -3.194524,-1.3167 -5.515311,-3.52953 -2.969632,-2.86211 -4.465535,-7.16744 -2.676879,-7.70426 0.890426,-14.89749 3.567304,-7.19323 11.775112,-10.04506 2.54291,-0.88355 4.722241,-1.1893 1.177786,-0.18349 3.977586,-0.0841 2.81623,0.0655 3.269421,-0.0919 0.70497,-0.24494 1.13426,-0.95843 0.42055,-0.73868 0.42546,-2.34875 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'times new roman';-inkscape-font-specification:'times new roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5334);fill-opacity:1;stroke:none;stroke-width:1.36467421"
id="letter-g"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d91f90f19b96654c753f515c19f4191ad9881cefec249ac4decb5f872d344049
size 6499

View file

@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="250"
height="150"
viewBox="0 0 66.145827 39.687498"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="LogoRoyalGamesDota.svg"
inkscape:export-filename="C:\Users\stepi\PycharmProjects\royalnet\static\LogoRoyalGamesDota.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<title
id="title5190">Royal Games</title>
<defs
id="defs2">
<linearGradient
id="linearGradient5149">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5147" />
</linearGradient>
<linearGradient
id="linearGradient5042">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5040" />
</linearGradient>
<linearGradient
id="linearGradient4971">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4969" />
</linearGradient>
<linearGradient
id="Principale"
osb:paint="solid">
<stop
style="stop-color:#a1ccff;stop-opacity:1;"
offset="0"
id="stop4895" />
</linearGradient>
<linearGradient
id="Sfondo"
osb:paint="solid">
<stop
style="stop-color:#0d193b;stop-opacity:1;"
offset="0"
id="stop817" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient821"
x1="34.745037"
y1="124.61114"
x2="175.3288"
y2="124.61114"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.47050836,0,0,0.32141214,-16.347828,237.10472)" />
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5332"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.9722906,-1.7276436,1.7276436,4.9722906,-481.91887,-479.70806)"
x1="55.743366"
y1="152.60051"
x2="62.71452"
y2="152.60051" />
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5334"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.8721103,-1.6928355,1.6928355,4.8721103,-559.10679,-570.69534)"
x1="65.251831"
y1="186.99634"
x2="72.946449"
y2="186.99634" />
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient5336"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(5.0448436,-1.7528523,1.7528523,5.0448436,-614.18765,-850.35741)"
x1="63.593018"
y1="224.03799"
x2="71.024086"
y2="224.03799" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath5457">
<use
x="0"
y="0"
xlink:href="#g5453"
id="use5459"
width="100%"
height="100%" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="122.82373"
inkscape:cy="83.346105"
inkscape:document-units="px"
inkscape:current-layer="main-layer"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:measure-start="384.018,406.607"
inkscape:measure-end="388.429,418.649"
showguides="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="false"
inkscape:snap-bbox="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-nodes="true"
inkscape:snap-grids="false"
inkscape:snap-others="false"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1272"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-page="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid1186"
originx="0"
originy="1e-006" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Royal Games</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Main Layer"
inkscape:groupmode="layer"
id="main-layer"
transform="translate(0,-257.3125)"
style="display:inline">
<rect
style="fill:url(#linearGradient821);fill-opacity:1;stroke-width:0.10289098"
id="background"
width="66.145828"
height="39.687496"
x="0"
y="257.31253" />
<g
id="logo"
transform="matrix(0.13324183,0,0,0.13324186,14.362153,255.4556)">
<path
inkscape:connector-curvature="0"
id="star"
d="m 55.674623,47.499965 23.70863,68.248035 5.283398,-7.9251 5.291666,7.9375 5.291667,-7.9375 v 27.78125 c 0,0 -3.96081,0.36871 -8.719881,0.71727 l 36.012267,103.66498 105.39511,38.24676 -69.9983,-88.16309 66.02232,-87.15178 -103.69547,36.69078 z"
style="display:inline;opacity:0.98999999;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
inkscape:connector-curvature="0"
id="crown"
d="m 86.530103,136.32142 -7.14685,-20.57342 -0.0083,0.0124 -5.291667,-7.93751 -5.291666,7.93751 -5.291622,-7.9375 -5.291667,7.93751 -5.291666,-7.93751 v 27.78126 c 0,0 14.097344,1.32291 21.166666,1.32291 3.331782,0 8.204375,-0.29493 12.446786,-0.60565 z"
style="display:inline;opacity:1;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
d="m 72.507099,179.7588 3.39258,9.76411 q 0.982062,2.82645 1.575007,3.45555 0.609709,0.59447 1.635113,0.72773 1.025404,0.13327 3.415041,-0.69702 l 0.330331,0.95072 -17.832346,6.19592 -0.330331,-0.95071 q 2.415332,-0.83922 3.111495,-1.57065 0.712931,-0.76606 0.796984,-1.60159 0.109751,-0.84445 -0.872311,-3.6709 l -7.856504,-22.61162 q -0.982062,-2.82645 -1.591772,-3.42092 -0.592945,-0.6291 -1.618348,-0.76236 -1.025404,-0.13327 -3.415041,0.69702 l -0.330331,-0.95072 16.187864,-5.62454 q 6.320976,-2.19625 9.553754,-2.3404 3.232782,-0.14414 5.904185,1.577 2.662477,1.69545 3.7606,4.85593 1.339177,3.85425 -0.560957,7.33657 -1.219429,2.20912 -4.128851,4.11271 l 12.474459,8.88354 q 2.439061,1.71548 3.329239,2.03972 1.330261,0.4305 2.744571,0.0255 l 0.33033,0.95072 -10.971772,3.81219 -16.746132,-11.97806 z m -5.937017,-17.08719 5.303137,15.26284 1.464616,-0.50889 q 3.571608,-1.24097 5.121371,-2.49936 1.540832,-1.2841 1.953701,-3.32815 0.429633,-2.07869 -0.525644,-4.82805 -1.383817,-3.98273 -3.920215,-5.23243 -2.5107,-1.25862 -6.647599,0.17876 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5332);fill-opacity:1;stroke:none;stroke-width:1.39273477"
id="letter-r"
inkscape:connector-curvature="0" />
<path
d="m 130.68189,137.70908 0.33514,0.96457 q -1.66517,0.84148 -2.33195,1.94973 -0.92304,1.54784 -2.04475,7.40121 l -3.217,15.4634 2.97106,8.55096 q 0.9511,2.73733 1.47316,3.3156 0.513,0.55218 1.52862,0.78362 1.03243,0.19649 2.4402,-0.29264 l 1.98136,-0.68843 0.33514,0.96457 -19.83925,6.89323 -0.33514,-0.96457 1.85095,-0.64312 q 1.5642,-0.54349 2.28645,-1.408 0.55091,-0.60076 0.63405,-1.62275 0.0692,-0.72522 -0.85477,-3.38436 l -2.46381,-7.09103 -13.57909,-12.2571 q -4.034666,-3.62353 -5.323131,-4.05231 -1.297544,-0.45491 -3.001119,0.10777 l -0.335146,-0.96457 16.945476,-5.88778 0.33515,0.96457 -0.75607,0.2627 q -1.53812,0.53443 -2.00989,1.19475 -0.44555,0.65179 -0.27353,1.14689 0.32609,0.93849 3.72516,3.99398 l 10.45421,9.48617 2.65629,-12.87275 q 1.01366,-4.73481 0.5698,-6.01227 -0.24455,-0.70385 -1.08543,-0.93761 -1.11283,-0.34377 -3.53051,0.35017 l -0.33515,-0.96457 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5336);fill-opacity:1;stroke:none;stroke-width:1.41305673"
id="letter-y"
inkscape:connector-curvature="0" />
<path
d="m 102.64486,200.4253 4.18153,12.03476 -0.93156,0.32368 q -3.40148,-4.34864 -7.667536,-5.7445 -4.266052,-1.39586 -8.269247,-0.005 -3.826954,1.32969 -5.617541,4.37848 -1.79933,3.02363 -1.518338,7.24317 0.280991,4.21955 1.689414,8.2731 1.705854,4.90957 4.149964,8.20824 2.444105,3.29865 5.615805,4.1436 3.196874,0.83619 6.746879,-0.39727 1.23369,-0.42865 2.43025,-1.12657 1.21299,-0.73185 2.39761,-1.70779 l -2.46693,-7.1 q -0.69983,-2.01419 -1.17799,-2.49704 -0.4869,-0.50803 -1.53432,-0.68021 -1.022237,-0.18094 -2.255926,0.24771 l -0.881207,0.30618 -0.323675,-0.93156 16.591858,-5.76491 0.32368,0.93156 q -1.84457,0.78197 -2.45992,1.44726 -0.59892,0.63134 -0.68683,1.6777 -0.0604,0.5571 0.58696,2.42022 l 2.46693,7.1 q -2.76567,2.59752 -6.06198,4.56113 -3.26239,1.98004 -7.06416,3.30099 -4.859227,1.68835 -8.536819,1.49887 -3.661163,-0.22341 -6.872121,-1.50618 -3.194524,-1.3167 -5.515311,-3.52953 -2.969632,-2.86211 -4.465535,-7.16744 -2.676879,-7.70426 0.890426,-14.89749 3.567304,-7.19323 11.775112,-10.04506 2.54291,-0.88355 4.722241,-1.1893 1.177786,-0.18349 3.977586,-0.0841 2.81623,0.0655 3.269421,-0.0919 0.70497,-0.24494 1.13426,-0.95843 0.42055,-0.73868 0.42546,-2.34875 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'times new roman';-inkscape-font-specification:'times new roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5334);fill-opacity:1;stroke:none;stroke-width:1.36467421"
id="letter-g"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:af8a851565bba2e4a20bb0ad0492249941c220d1b3e6d6f480406f9488728e50
size 11441

View file

@ -0,0 +1,346 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256"
height="256"
viewBox="0 0 67.73333 67.733333"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="LogoRoyalGamesDotaBanner.svg"
inkscape:export-filename="C:\Users\stepi\PycharmProjects\royalnet\static\LogoRoyalGamesDotaBanner.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<title
id="title5190">Royal Games</title>
<defs
id="defs2">
<linearGradient
id="linearGradient5149">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5147" />
</linearGradient>
<linearGradient
id="linearGradient5042">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5040" />
</linearGradient>
<linearGradient
id="linearGradient4971">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4969" />
</linearGradient>
<linearGradient
id="Principale"
osb:paint="solid">
<stop
style="stop-color:#a1ccff;stop-opacity:1;"
offset="0"
id="stop4895" />
</linearGradient>
<linearGradient
id="Sfondo"
osb:paint="solid">
<stop
style="stop-color:#0d193b;stop-opacity:1;"
offset="0"
id="stop817" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient821"
x1="34.745037"
y1="124.61114"
x2="175.3288"
y2="124.61114"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.24090027,0,0,0.2742717,-8.3700906,212.02064)" />
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5332"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.9722906,-1.7276436,1.7276436,4.9722906,-481.91887,-479.70806)"
x1="55.743366"
y1="152.60051"
x2="62.71452"
y2="152.60051" />
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5334"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.8721103,-1.6928355,1.6928355,4.8721103,-559.10679,-570.69534)"
x1="65.251831"
y1="186.99634"
x2="72.946449"
y2="186.99634" />
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient5336"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(5.0448436,-1.7528523,1.7528523,5.0448436,-614.18765,-850.35741)"
x1="63.593018"
y1="224.03799"
x2="71.024086"
y2="224.03799" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath5457">
<use
x="0"
y="0"
xlink:href="#g5453"
id="use5459"
width="100%"
height="100%" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5332-9"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.9722906,-1.7276436,1.7276436,4.9722906,-481.91887,-479.70806)"
x1="55.743366"
y1="152.60051"
x2="62.71452"
y2="152.60051" />
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient5336-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(5.0448436,-1.7528523,1.7528523,5.0448436,-614.18765,-850.35741)"
x1="63.593018"
y1="224.03799"
x2="71.024086"
y2="224.03799" />
<linearGradient
inkscape:collect="always"
xlink:href="#Principale"
id="linearGradient5334-8"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.8721103,-1.6928355,1.6928355,4.8721103,-559.10679,-570.69534)"
x1="65.251831"
y1="186.99634"
x2="72.946449"
y2="186.99634" />
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient821-3"
x1="34.745037"
y1="124.61114"
x2="175.3288"
y2="124.61114"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.24090028,0,0,0.27427171,25.496575,212.02064)" />
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient821-8"
x1="34.745037"
y1="124.61114"
x2="175.3288"
y2="124.61114"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.24090028,0,0,0.27427171,-8.3700894,245.8873)" />
<linearGradient
inkscape:collect="always"
xlink:href="#Sfondo"
id="linearGradient821-8-2"
x1="34.745037"
y1="124.61114"
x2="175.3288"
y2="124.61114"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.24090029,0,0,0.27427171,25.496574,245.8873)" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4142136"
inkscape:cx="280.19406"
inkscape:cy="207.76729"
inkscape:document-units="px"
inkscape:current-layer="main-layer"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:measure-start="384.018,406.607"
inkscape:measure-end="388.429,418.649"
showguides="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="false"
inkscape:snap-bbox="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-nodes="true"
inkscape:snap-grids="false"
inkscape:snap-others="false"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1272"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-page="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:lockguides="false">
<inkscape:grid
type="xygrid"
id="grid1186"
originx="0"
originy="1.5e-005" />
<sodipodi:guide
position="33.866664,33.868736"
orientation="1,0"
id="guide1482"
inkscape:locked="false" />
<sodipodi:guide
position="33.866664,33.868736"
orientation="0,1"
id="guide1549"
inkscape:locked="false" />
<sodipodi:guide
position="16.933333,6.5481033"
orientation="1,0"
id="guide1597"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Royal Games</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Main Layer"
inkscape:groupmode="layer"
id="main-layer"
transform="translate(0,-229.26669)"
style="display:inline">
<rect
style="fill:url(#linearGradient821);fill-opacity:1;stroke-width:0.06800982"
id="background"
width="33.866665"
height="33.866665"
x="-1.772949e-006"
y="229.26462" />
<g
id="logo"
transform="matrix(0.1136997,0,0,0.11369973,0.96681376,227.68007)">
<path
inkscape:connector-curvature="0"
id="star"
d="m 55.674623,47.499965 23.70863,68.248035 5.283398,-7.9251 5.291666,7.9375 5.291667,-7.9375 v 27.78125 c 0,0 -3.96081,0.36871 -8.719881,0.71727 l 36.012267,103.66498 105.39511,38.24676 -69.9983,-88.16309 66.02232,-87.15178 -103.69547,36.69078 z"
style="display:inline;opacity:0.98999999;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
inkscape:connector-curvature="0"
id="crown"
d="m 86.530103,136.32142 -7.14685,-20.57342 -0.0083,0.0124 -5.291667,-7.93751 -5.291666,7.93751 -5.291622,-7.9375 -5.291667,7.93751 -5.291666,-7.93751 v 27.78126 c 0,0 14.097344,1.32291 21.166666,1.32291 3.331782,0 8.204375,-0.29493 12.446786,-0.60565 z"
style="display:inline;opacity:1;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
d="m 72.507099,179.7588 3.39258,9.76411 q 0.982062,2.82645 1.575007,3.45555 0.609709,0.59447 1.635113,0.72773 1.025404,0.13327 3.415041,-0.69702 l 0.330331,0.95072 -17.832346,6.19592 -0.330331,-0.95071 q 2.415332,-0.83922 3.111495,-1.57065 0.712931,-0.76606 0.796984,-1.60159 0.109751,-0.84445 -0.872311,-3.6709 l -7.856504,-22.61162 q -0.982062,-2.82645 -1.591772,-3.42092 -0.592945,-0.6291 -1.618348,-0.76236 -1.025404,-0.13327 -3.415041,0.69702 l -0.330331,-0.95072 16.187864,-5.62454 q 6.320976,-2.19625 9.553754,-2.3404 3.232782,-0.14414 5.904185,1.577 2.662477,1.69545 3.7606,4.85593 1.339177,3.85425 -0.560957,7.33657 -1.219429,2.20912 -4.128851,4.11271 l 12.474459,8.88354 q 2.439061,1.71548 3.329239,2.03972 1.330261,0.4305 2.744571,0.0255 l 0.33033,0.95072 -10.971772,3.81219 -16.746132,-11.97806 z m -5.937017,-17.08719 5.303137,15.26284 1.464616,-0.50889 q 3.571608,-1.24097 5.121371,-2.49936 1.540832,-1.2841 1.953701,-3.32815 0.429633,-2.07869 -0.525644,-4.82805 -1.383817,-3.98273 -3.920215,-5.23243 -2.5107,-1.25862 -6.647599,0.17876 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5332);fill-opacity:1;stroke:none;stroke-width:1.39273477"
id="letter-r"
inkscape:connector-curvature="0" />
<path
d="m 130.68189,137.70908 0.33514,0.96457 q -1.66517,0.84148 -2.33195,1.94973 -0.92304,1.54784 -2.04475,7.40121 l -3.217,15.4634 2.97106,8.55096 q 0.9511,2.73733 1.47316,3.3156 0.513,0.55218 1.52862,0.78362 1.03243,0.19649 2.4402,-0.29264 l 1.98136,-0.68843 0.33514,0.96457 -19.83925,6.89323 -0.33514,-0.96457 1.85095,-0.64312 q 1.5642,-0.54349 2.28645,-1.408 0.55091,-0.60076 0.63405,-1.62275 0.0692,-0.72522 -0.85477,-3.38436 l -2.46381,-7.09103 -13.57909,-12.2571 q -4.034666,-3.62353 -5.323131,-4.05231 -1.297544,-0.45491 -3.001119,0.10777 l -0.335146,-0.96457 16.945476,-5.88778 0.33515,0.96457 -0.75607,0.2627 q -1.53812,0.53443 -2.00989,1.19475 -0.44555,0.65179 -0.27353,1.14689 0.32609,0.93849 3.72516,3.99398 l 10.45421,9.48617 2.65629,-12.87275 q 1.01366,-4.73481 0.5698,-6.01227 -0.24455,-0.70385 -1.08543,-0.93761 -1.11283,-0.34377 -3.53051,0.35017 l -0.33515,-0.96457 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5336);fill-opacity:1;stroke:none;stroke-width:1.41305673"
id="letter-y"
inkscape:connector-curvature="0" />
<path
d="m 102.64486,200.4253 4.18153,12.03476 -0.93156,0.32368 q -3.40148,-4.34864 -7.667536,-5.7445 -4.266052,-1.39586 -8.269247,-0.005 -3.826954,1.32969 -5.617541,4.37848 -1.79933,3.02363 -1.518338,7.24317 0.280991,4.21955 1.689414,8.2731 1.705854,4.90957 4.149964,8.20824 2.444105,3.29865 5.615805,4.1436 3.196874,0.83619 6.746879,-0.39727 1.23369,-0.42865 2.43025,-1.12657 1.21299,-0.73185 2.39761,-1.70779 l -2.46693,-7.1 q -0.69983,-2.01419 -1.17799,-2.49704 -0.4869,-0.50803 -1.53432,-0.68021 -1.022237,-0.18094 -2.255926,0.24771 l -0.881207,0.30618 -0.323675,-0.93156 16.591858,-5.76491 0.32368,0.93156 q -1.84457,0.78197 -2.45992,1.44726 -0.59892,0.63134 -0.68683,1.6777 -0.0604,0.5571 0.58696,2.42022 l 2.46693,7.1 q -2.76567,2.59752 -6.06198,4.56113 -3.26239,1.98004 -7.06416,3.30099 -4.859227,1.68835 -8.536819,1.49887 -3.661163,-0.22341 -6.872121,-1.50618 -3.194524,-1.3167 -5.515311,-3.52953 -2.969632,-2.86211 -4.465535,-7.16744 -2.676879,-7.70426 0.890426,-14.89749 3.567304,-7.19323 11.775112,-10.04506 2.54291,-0.88355 4.722241,-1.1893 1.177786,-0.18349 3.977586,-0.0841 2.81623,0.0655 3.269421,-0.0919 0.70497,-0.24494 1.13426,-0.95843 0.42055,-0.73868 0.42546,-2.34875 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'times new roman';-inkscape-font-specification:'times new roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5334);fill-opacity:1;stroke:none;stroke-width:1.36467421"
id="letter-g"
inkscape:connector-curvature="0" />
</g>
<rect
style="display:inline;fill:url(#linearGradient821-3);fill-opacity:1;stroke-width:0.06800982"
id="background-1"
width="33.866665"
height="33.866665"
x="33.866665"
y="229.26462" />
<g
style="display:inline"
id="logo-3"
transform="matrix(-0.1136997,0,0,0.11369973,66.766512,227.68007)">
<path
inkscape:connector-curvature="0"
id="star-7"
d="m 55.674623,47.499965 23.70863,68.248035 5.283398,-7.9251 5.291666,7.9375 5.291667,-7.9375 v 27.78125 c 0,0 -3.96081,0.36871 -8.719881,0.71727 l 36.012267,103.66498 105.39511,38.24676 -69.9983,-88.16309 66.02232,-87.15178 -103.69547,36.69078 z"
style="display:inline;opacity:0.98999999;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
inkscape:connector-curvature="0"
id="crown-7"
d="m 86.530103,136.32142 -7.14685,-20.57342 -0.0083,0.0124 -5.291667,-7.93751 -5.291666,7.93751 -5.291622,-7.9375 -5.291667,7.93751 -5.291666,-7.93751 v 27.78126 c 0,0 14.097344,1.32291 21.166666,1.32291 3.331782,0 8.204375,-0.29493 12.446786,-0.60565 z"
style="display:inline;opacity:1;fill:#a1ccff;fill-opacity:1;stroke-width:0.33488649" />
<path
d="m 72.507099,179.7588 3.39258,9.76411 q 0.982062,2.82645 1.575007,3.45555 0.609709,0.59447 1.635113,0.72773 1.025404,0.13327 3.415041,-0.69702 l 0.330331,0.95072 -17.832346,6.19592 -0.330331,-0.95071 q 2.415332,-0.83922 3.111495,-1.57065 0.712931,-0.76606 0.796984,-1.60159 0.109751,-0.84445 -0.872311,-3.6709 l -7.856504,-22.61162 q -0.982062,-2.82645 -1.591772,-3.42092 -0.592945,-0.6291 -1.618348,-0.76236 -1.025404,-0.13327 -3.415041,0.69702 l -0.330331,-0.95072 16.187864,-5.62454 q 6.320976,-2.19625 9.553754,-2.3404 3.232782,-0.14414 5.904185,1.577 2.662477,1.69545 3.7606,4.85593 1.339177,3.85425 -0.560957,7.33657 -1.219429,2.20912 -4.128851,4.11271 l 12.474459,8.88354 q 2.439061,1.71548 3.329239,2.03972 1.330261,0.4305 2.744571,0.0255 l 0.33033,0.95072 -10.971772,3.81219 -16.746132,-11.97806 z m -5.937017,-17.08719 5.303137,15.26284 1.464616,-0.50889 q 3.571608,-1.24097 5.121371,-2.49936 1.540832,-1.2841 1.953701,-3.32815 0.429633,-2.07869 -0.525644,-4.82805 -1.383817,-3.98273 -3.920215,-5.23243 -2.5107,-1.25862 -6.647599,0.17876 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5332-9);fill-opacity:1;stroke:none;stroke-width:1.39273477"
id="letter-r-1"
inkscape:connector-curvature="0" />
<path
d="m 130.68189,137.70908 0.33514,0.96457 q -1.66517,0.84148 -2.33195,1.94973 -0.92304,1.54784 -2.04475,7.40121 l -3.217,15.4634 2.97106,8.55096 q 0.9511,2.73733 1.47316,3.3156 0.513,0.55218 1.52862,0.78362 1.03243,0.19649 2.4402,-0.29264 l 1.98136,-0.68843 0.33514,0.96457 -19.83925,6.89323 -0.33514,-0.96457 1.85095,-0.64312 q 1.5642,-0.54349 2.28645,-1.408 0.55091,-0.60076 0.63405,-1.62275 0.0692,-0.72522 -0.85477,-3.38436 l -2.46381,-7.09103 -13.57909,-12.2571 q -4.034666,-3.62353 -5.323131,-4.05231 -1.297544,-0.45491 -3.001119,0.10777 l -0.335146,-0.96457 16.945476,-5.88778 0.33515,0.96457 -0.75607,0.2627 q -1.53812,0.53443 -2.00989,1.19475 -0.44555,0.65179 -0.27353,1.14689 0.32609,0.93849 3.72516,3.99398 l 10.45421,9.48617 2.65629,-12.87275 q 1.01366,-4.73481 0.5698,-6.01227 -0.24455,-0.70385 -1.08543,-0.93761 -1.11283,-0.34377 -3.53051,0.35017 l -0.33515,-0.96457 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5336-0);fill-opacity:1;stroke:none;stroke-width:1.41305673"
id="letter-y-8"
inkscape:connector-curvature="0" />
<path
d="m 102.64486,200.4253 4.18153,12.03476 -0.93156,0.32368 q -3.40148,-4.34864 -7.667536,-5.7445 -4.266052,-1.39586 -8.269247,-0.005 -3.826954,1.32969 -5.617541,4.37848 -1.79933,3.02363 -1.518338,7.24317 0.280991,4.21955 1.689414,8.2731 1.705854,4.90957 4.149964,8.20824 2.444105,3.29865 5.615805,4.1436 3.196874,0.83619 6.746879,-0.39727 1.23369,-0.42865 2.43025,-1.12657 1.21299,-0.73185 2.39761,-1.70779 l -2.46693,-7.1 q -0.69983,-2.01419 -1.17799,-2.49704 -0.4869,-0.50803 -1.53432,-0.68021 -1.022237,-0.18094 -2.255926,0.24771 l -0.881207,0.30618 -0.323675,-0.93156 16.591858,-5.76491 0.32368,0.93156 q -1.84457,0.78197 -2.45992,1.44726 -0.59892,0.63134 -0.68683,1.6777 -0.0604,0.5571 0.58696,2.42022 l 2.46693,7.1 q -2.76567,2.59752 -6.06198,4.56113 -3.26239,1.98004 -7.06416,3.30099 -4.859227,1.68835 -8.536819,1.49887 -3.661163,-0.22341 -6.872121,-1.50618 -3.194524,-1.3167 -5.515311,-3.52953 -2.969632,-2.86211 -4.465535,-7.16744 -2.676879,-7.70426 0.890426,-14.89749 3.567304,-7.19323 11.775112,-10.04506 2.54291,-0.88355 4.722241,-1.1893 1.177786,-0.18349 3.977586,-0.0841 2.81623,0.0655 3.269421,-0.0919 0.70497,-0.24494 1.13426,-0.95843 0.42055,-0.73868 0.42546,-2.34875 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'times new roman';-inkscape-font-specification:'times new roman, Bold';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient5334-8);fill-opacity:1;stroke:none;stroke-width:1.36467421"
id="letter-g-3"
inkscape:connector-curvature="0" />
</g>
<path
style="display:inline;fill:url(#linearGradient821-8);fill-opacity:1;stroke-width:0.06800982"
d="M 6.7199593e-8,263.13129 H 33.866665 v 33.86666 c 0,0 -25.7079536,-10.33088 -16.933332,-10.33088 C 25.758426,286.66707 6.719959e-8,296.99795 6.719959e-8,296.99795 Z"
id="background-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccscc" />
<path
style="display:inline;fill:url(#linearGradient821-8-2);fill-opacity:1;stroke-width:0.06800982"
d="M 33.866664,263.13129 H 67.73333 v 33.86666 c 0,0 -25.707954,-10.33088 -16.933333,-10.33088 8.825093,0 -16.933333,10.33088 -16.933333,10.33088 z"
id="background-2-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccscc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 131 B

View file

@ -18,10 +18,6 @@ h1, h2, h3, h4, h5, h6 {
color: @accent-color;
}
h1 .whatsthis {
font-size: large;
}
a {
color: @link-color;
text-decoration: none;
@ -75,7 +71,7 @@ code {
font-family: "Consolas", "Source Code Pro", monospace;
}
input[type="text"], input[type="password"] {
input[type="text"], input[type="password"], input[type="email"] {
background-color: rgba(red(@text-color), green(@text-color), blue(@text-color), 0.1);
color: @text-color;
border: none;
@ -213,21 +209,60 @@ table {
}
}
ul {
list-style: none;
&.multicolumn {
column-width: 192px;
word-wrap: break-word;
}
li {
&::before {
content: "•";
display: inline-block;
width: 1em;
margin-left: -1em;
}
&.list-admin {
&::before {
color: #ffff00;
}
}
&.list-locked {
&::before {
color: #555555;
}
}
}
}
img {
max-width: 100%;
@media (max-width:792px) {
max-width: initial;
}
}
.gravatar {
border-radius: 16px;
width: 32px;
height: 32px;
vertical-align: middle;
}
.whatsthis {
font-size: large;
}
.omnicontainer {
width: 380px;
width: 100%;
margin-left: auto;
margin-right: auto;
@media (min-width: 792px)
{
width: 776px;
}
@media (min-width: 1180px)
{
width: 1164px;
}
@media (min-width: 1568px) {
width: 1552px;
}
@ -270,18 +305,6 @@ table {
}
}
@media (min-width:601px) {
.mobile-only {
display: none;
}
}
@media (max-width:600px) {
.desktop-only {
display: none;
}
}
.game-panel {
display: inline-block;
margin: 4px;
@ -511,6 +534,10 @@ table {
img.medal {
height: 60px;
&.locked {
opacity: 0.3;
}
}
}
}
@ -696,30 +723,78 @@ table {
.ryg {
background-color: rgba(red(@text-color), green(@text-color), blue(@text-color), 0.1);
padding: 18px;
grid-template-columns: 80% 20%;
grid-template-rows: 32px 16px 16px 36px;
grid-template-columns: 33.3% 33.3% 33.3%;
.player {
.username {
grid-row: 1;
grid-column-start: 1;
grid-column-end: 3;
font-weight: bold;
grid-column: 1 / 3;
font-size: x-large;
align-self: center;
justify-self: flex-start;
}
.member-status {
.special-title {
grid-row: 1;
grid-column: 3;
font-size: small;
align-self: center;
justify-self: flex-end;
font-style: italic;
color: @accent-color;
}
.join-date-title {
grid-row: 3;
grid-column: 1;
grid-row-start: 2;
grid-row-end: 4;
font-size: x-large;
margin-top: auto;
margin-bottom: auto;
font-size: xx-small;
align-self: center;
justify-self: center;
}
.join-date {
grid-row: 4;
grid-column: 1;
font-size: large;
align-self: center;
justify-self: center;
}
.wiki-edits-title {
grid-row: 3;
grid-column: 2;
font-size: xx-small;
align-self: center;
justify-self: center;
}
.wiki-edits {
grid-row: 4;
grid-column: 2;
font-size: large;
align-self: center;
justify-self: center;
}
.fiorygi-title {
grid-row: 3;
grid-column: 3;
font-size: xx-small;
align-self: center;
justify-self: center;
}
.fiorygi {
grid-column: 2;
grid-row: 4;
grid-column: 3;
font-size: large;
align-self: center;
justify-self: center;
}
.fiorygi.game-score {
font-size: x-large;
.unknown {
opacity: 0.3;
}
}
@ -809,13 +884,50 @@ table {
}
}
}
.terraria13 {
border: 3px solid black;
padding: 15px;
background-image: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url("/static/stoneslab.png");
font-family: "Andy", sans-serif;
font-weight: bold;
color: white;
grid-row-gap: 5px;
.character {
grid-row: 1;
font-size: x-large;
}
.contribution-status {
grid-row: 2;
.contribution {
font-size: xx-large;
&.blue {
color: #8686e5;
}
&.green {
color: #92f892;
}
&.orange {
color: #e9b688;
}
}
}
}
}
.wiki {
.wiki-log {
.wiki-log, .wiki-locked {
font-family: "Consolas", "Source Code Pro", monospace;
font-size: small;
margin-bottom: 12px;
opacity: 0.4;
.last-reason {
font-style: italic;
@ -837,7 +949,7 @@ table {
}
}
.entry {
.diario {
display: grid;
.left {
@ -854,15 +966,15 @@ table {
font-size: smaller;
}
.entry-id {
.diario-id {
text-align: right;
}
}
.main-page {
.two-columns {
display: grid;
@media (min-width:601px) {
@media (min-width:791px) {
//Desktop
grid-template-columns: 50% 50%;
@ -876,7 +988,7 @@ table {
}
}
@media (max-width:600px) {
@media (max-width:792px) {
//Mobile
grid-template-columns: 100%;
@ -894,11 +1006,6 @@ table {
grid-row-end: 3;
}
}
ul {
column-width: 200px;
word-wrap: break-word;
}
}
.event {
@ -942,6 +1049,65 @@ table {
}
}
.quest {
padding: 8px;
color: @accent-color;
font-family: "Verdana", sans-serif;
background-image: url("/static/plank.jpg");
display: grid;
grid-template-columns: auto 64px;
grid-row-gap: 2px;
margin-bottom: 1px;
margin-top: 1px;
border-radius: 4px;
.title {
grid-row: 1;
grid-column: 1;
font-weight: bold;
}
.description {
grid-row: 2;
grid-column: 1;
p {
margin: 0;
}
}
.reward {
grid-row-start: 1;
grid-row-end: 3;
grid-column: 2;
text-align: center;
display: flex;
flex-direction: column;
.number {
font-size: xx-large;
}
.subtext {
font-size: smaller;
}
}
}
#royal-games {
font-size: 64px;
text-align: center;
@media (max-width:792px) {
font-size: 32px;
}
img {
height: 128px;
vertical-align: middle;
}
}
#debug-mode {
color: red;
font-weight: bold;

3
static/plank.jpg Normal file
View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ec59480cf8db818b449242d83ec720ae23154eff9f8cdf33ac6c2a53bfb4f3aa
size 1459923

Binary file not shown.

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ebabd07ca06e02e43486019112ffaa65cb068aa15340797d5c46980c32a6116f
size 37273

Binary file not shown.

3
static/stoneslab.png Normal file
View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ddb8b02a67b7890df9530ffe7ccc1c1b2fc700045b2ffd483694bbdffd561f0
size 12108

3
static/terrariafont.ttf Normal file
View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7697ec2286230bcfbb4c668968193d0da1621e7b97bd15137e51811b482eaf2d
size 83083

Binary file not shown.

View file

@ -11,8 +11,8 @@ import telegram
import sys
import coloredlogs
import requests
from dirty import Dirty
from sentry_sdk import configure_scope
import strings
from utils import Dirty, DirtyDelta, reply_msg
logging.getLogger().disabled = True
logger = logging.getLogger(__name__)
@ -30,21 +30,16 @@ sentry = raven.Client(config["Sentry"]["token"],
hook_libraries=[])
telegram_bot = telegram.Bot(config["Telegram"]["bot_token"])
main_chat_id = config["Telegram"]["main_group"]
def update_block(session: db.Session, block: list, delay: float=0, change_callback: typing.Callable=None):
def update_block(session: db.Session, block: list, delay: float = 0, change_callback: typing.Callable = None):
for item in block:
logger.debug(f"Updating {repr(item)}.")
t = time.clock()
try:
change = item.update(session=session)
except requests.exceptions.HTTPError as e:
with configure_scope() as scope:
if str(e.response.status_code).startswith("5"):
scope.level = "warning"
logger.warning(f"Server error {sys.exc_info()} while updating {repr(item)}.")
else:
scope.level = "error"
logger.error(f"Error {sys.exc_info()} while updating {repr(item)}.")
sentry.extra_context({
"item": repr(item),
@ -76,12 +71,11 @@ def new_dota_rank(item: db.Dota, change):
f" {item.get_rank_name()} {item.get_rank_number()} su Dota 2!")
except Exception:
logger.warning(f"Couldn't notify on Telegram: {item}")
sentry.captureException()
def new_lol_rank(item, change: typing.Tuple[Dirty]):
def new_lol_rank(item, change: typing.Tuple[Dirty, Dirty, Dirty]):
# It always gets called, even when there is no change
# Assignment inspection is wrong
# noinspection PyTupleAssignmentBalance
solo, flex, twtr = change
try:
if solo:
@ -107,11 +101,65 @@ def new_lol_rank(item, change: typing.Tuple[Dirty]):
parse_mode="Markdown")
except Exception:
logger.warning(f"Couldn't notify on Telegram: {item}")
sentry.captureException()
def osu_pp_change(item, change: typing.Tuple[DirtyDelta, DirtyDelta, DirtyDelta, DirtyDelta]):
std, taiko, catch, mania = change
try:
if std.delta >= 1:
telegram_bot.send_message(config["Telegram"]["main_group"],
f"✳️ {item.royal.username} ha ora <b>{int(std.value)}pp</b> (+{int(std.delta)}) su osu!",
parse_mode="HTML")
if taiko.delta >= 1:
telegram_bot.send_message(config["Telegram"]["main_group"],
f"✳️ {item.royal.username} ha ora <b>{int(taiko.value)}pp</b> (+{int(taiko.delta)}) su osu!taiko!",
parse_mode="HTML")
if catch.delta >= 1:
telegram_bot.send_message(config["Telegram"]["main_group"],
f"✳️ {item.royal.username} ha ora <b>{int(catch.value)}pp</b> (+{int(catch.delta)}) su osu!catch!",
parse_mode="HTML")
if mania.delta >= 1:
telegram_bot.send_message(config["Telegram"]["main_group"],
f"✳️ {item.royal.username} ha ora <b>{int(mania.value)}pp</b> (+{int(mania.delta)}) su osu!mania!",
parse_mode="HTML")
except Exception:
logger.warning(f"Couldn't notify on Telegram: {item}")
sentry.captureException()
def brawlhalla_rank_change(item, change: typing.Tuple[DirtyDelta, Dirty]):
solo, team = change
try:
if solo.delta >= 10:
reply_msg(telegram_bot, main_chat_id, strings.STATSUPDATE.BRAWLHALLA.SOLO,
username=item.steam.royal.telegram.mention(),
rating=solo.value,
delta=solo.difference_string())
if team.is_dirty():
partner = item.best_team_partner
if partner is None:
other = "qualcun altro"
else:
other = partner.steam.royal.telegram.mention()
reply_msg(telegram_bot, main_chat_id, strings.STATSUPDATE.BRAWLHALLA.TEAM,
username=item.steam.royal.telegram.mention(),
rating=team.value[1],
other=other)
except Exception:
logger.warning(f"Couldn't notify on Telegram: {item}")
sentry.captureException()
def process():
while True:
session = db.Session()
logger.info("Now updating League of Legends data.")
update_block(session, session.query(db.Brawlhalla).all(), delay=5, change_callback=brawlhalla_rank_change)
session.commit()
logger.info("Now updating osu! data.")
update_block(session, session.query(db.Osu).all(), delay=5, change_callback=osu_pp_change)
session.commit()
logger.info("Now updating Steam data.")
update_block(session, session.query(db.Steam).all())
session.commit()
@ -121,12 +169,6 @@ def process():
logger.info("Now updating League of Legends data.")
update_block(session, session.query(db.LeagueOfLegends).all(), delay=5, change_callback=new_lol_rank)
session.commit()
logger.info("Now updating osu! data.")
update_block(session, session.query(db.Osu).all(), delay=5)
session.commit()
logger.info("Now updating Overwatch data.")
update_block(session, session.query(db.Overwatch).all(), delay=5)
session.commit()
logger.info("Pausing for 30 minutes.")
time.sleep(1800)

268
strings.py Normal file
View file

@ -0,0 +1,268 @@
import utils
import dice
import typing
class SafeDict(dict):
def __missing__(self, key):
return "{" + key + "}"
def safely_format_string(string: str, words: typing.Dict[str, str] = None, ignore_escaping=False) -> str:
if words is None:
words = {}
if ignore_escaping:
escaped = words
else:
escaped = {}
for key in words:
escaped[key] = str(words[key]).replace("<", "&lt;").replace(">", "&gt;")
return string.format_map(SafeDict(**escaped))
# Generic telegram errors
class TELEGRAM:
BOT_STARTED = "✅ Hai autorizzato il bot ad inviarti messaggi privati."
FIORYGI_AWARDED = "⭐️ {mention} è stato premiato con <b>{number} {fiorygi}</b> per <i>{reason}</i>!"
class ERRORS:
CRITICAL_ERROR = "☢ <b>ERRORE CRITICO!</b>\nIl bot ha ignorato il comando.\nUna segnalazione di errore è stata automaticamente mandata a @Steffo.\n\nDettagli dell'errore:\n<pre>{exc_info}</pre>"
CRITICAL_ERROR_QUERY = "☢ ERRORE CRITICO!"
UNAUTHORIZED_USER = "⚠ Non sono autorizzato a inviare messaggi a {mention}.\nPer piacere, {mention}, inviami un messaggio in privata!"
UNAUTHORIZED_GROUP = "⚠ Non sono autorizzato a inviare messaggi in <i>{group}</i>.\n@Steffo, aggiungimi al gruppo o concedimi i permessi!"
PONG = "🏓 Pong!"
ESCAPE = "{text}"
# Ah, non lo so io.
class AHNONLOSOIO:
ONCE = "😐 Ah, non lo so io!"
AGAIN = "😐 Ah, non lo so nemmeno io..."
# Bridge commands between Discord and Telegram
class BRIDGE:
SUCCESS = "✅ Comando inoltrato a Discord."
FAILURE = "❎ Errore nell'esecuzione del comando su Discord."
class ERRORS:
INVALID_SYNTAX = "⚠ Non hai specificato un comando!\nSintassi: <code>/bridge (comando)</code>"
INACTIVE_BRIDGE = "⚠ Il collegamento tra Telegram e Discord non è attivo al momento."
# Random spellslinging
class CAST:
class ERRORS:
NOT_YET_AVAILABLE = "⚠ Il nuovo cast non è ancora disponibile! Per un'anteprima sulle nuove funzioni, usa <code>/spell</code>."
# Ciao Ruozi!
class CIAORUOZI:
THE_LEGEND_HIMSELF = "👋 Ciao me!"
SOMEBODY_ELSE = "👋 Ciao Ruozi!"
# The /color meme, from Octeon
COLOR = "<i>I am sorry, unknown error occured during working with your request, Admin were notified</i>"
# Diario
class DIARIO:
SUCCESS = "✅ Riga aggiunta al diario:\n{diario}"
ANONYMOUS = "Anonimo"
class ERRORS:
INVALID_SYNTAX = "⚠ Sintassi del comando errata.\nSintassi: <code>/diario (frase)</code>, oppure rispondi a un messaggio con <code>/diario</code>."
NO_TEXT = "⚠ Il messaggio a cui hai risposto non contiene testo."
# Diario search
class DIARIOSEARCH:
HEADER = " Risultati della ricerca di {term}:\n"
class ERRORS:
INVALID_SYNTAX = "⚠ Non hai specificato un termine da cercare!\nSintassi: <code>/{command} (termine)</code>"
RESULTS_TOO_LONG = "⚠ Sono presenti troppi risultati da visualizzare! Prova a restringere la ricerca."
# Eat!
class EAT:
FOODS = {
"_default": "🍗 Hai mangiato {food}!\n<i>Ma non succede nulla.</i>",
"tonnuooooooro": "👻 Il {food} che hai mangiato era posseduto.\n<i>Spooky!</i>",
"uranio": "☢️ L'{food} che hai mangiato era radioattivo.\n<i>Stai brillando di verde!</i>",
"pollo": '🍗 Il {food} che hai appena mangiato proveniva <a href="https://store.steampowered.com/app/353090/Chicken_Invaders_5/">dallo spazio</a>.\n<i>Coccodè?</i>',
"ragno": "🕸 Hai mangiato un {food}.\n<i>Ewww!</i>",
"curry": "🔥 BRUCIAAAAAAAAAA! Il {food} era piccantissimo!\n<i>Stai sputando fiamme!</i>",
"torta": "⬜️ Non hai mangiato niente.\n<i>La {food} è una menzogna!</i>",
"cake": "⬜️ Non hai mangiato niente.\n<i>The {food} is a lie!</i>",
"biscotto": "🍪 Hai mangiato un {food} di contrabbando.\n<i>L'Inquisizione non lo saprà mai!</i>",
"biscotti": "🍪 Hai mangiato tanti {food} di contrabbando.\n<i>Attento! L'Inquisizione è sulle tue tracce!</i>",
"tango": "🌳 Hai mangiato un {food}, e un albero insieme ad esso.\n<i>Senti il tuo corpo curare le tue ferite.</i>",
"sasso": "🥌 Il {food} che hai mangiato era duro come un {food}\n<i>Stai soffrendo di indigestione!</i>",
"gnocchetti": "🥘 Ullà, sono duri 'sti {food}!\n<i>Fai fatica a digerirli.</i>",
"tide pods": "☣️ I {food} che hai mangiato erano buonissimi.\n<i>Stai sbiancando!</i>"
}
class ERRORS:
INVALID_SYNTAX = "⚠ Non hai specificato cosa mangiare!\nSintassi: <code>/eat (cibo)</code>"
# Emojify a string
class EMOJIFY:
RESPONSE = "{emojified}"
class ERRORS:
INVALID_SYNTAX = "⚠ Non hai specificato una frase!\nSintassi: <code>/emojify (testo)</code>"
# Royalnet linking
class LINK:
SUCCESS = "✅ Collegamento riuscito!"
class ERRORS:
INVALID_SYNTAX = "⚠ Non hai specificato un username!\nSintassi: <code>/link (username)</code>"
NOT_FOUND = "⚠ Non esiste nessun account Royalnet con quel nome.\nNota: gli username sono case-sensitive, e iniziano sempre con una maiuscola!"
ALREADY_EXISTING = "⚠ Questo account è già collegato a un account Royalnet."
ROYALNET_NOT_LINKED = "⚠ Il tuo account Telegram non è connesso a Royalnet! Connettilo con <code>/link (username)</code>."
# Markov strings
class MARKOV:
class ERRORS:
NO_MODEL = "⚠ La catena di Markov non è disponibile."
GENERATION_FAILED = "⚠ <code>markovify</code> non è riuscito a generare una frase. Prova di nuovo?\n E' un'avvenimento sorprendentemente raro..."
SPECIFIC_WORD_FAILED = "⚠ <code>markovify</code> non è riuscito a generare una frase partendo da questa parola. Provane una diversa..."
MISSING_WORD = "⚠ La parola specificata non è presente nella catena di Markov. Provane una diversa..."
# Matchmaking service strings
class MATCHMAKING:
EMOJIS = {
"ready": "🔵",
"wait_for_me": "🕒",
"maybe": "",
"ignore": "",
"close": "🚩",
"cancel": "🗑"
}
ENUM_TO_EMOJIS = {
utils.MatchmakingStatus.READY: EMOJIS["ready"],
utils.MatchmakingStatus.WAIT_FOR_ME: EMOJIS["wait_for_me"],
utils.MatchmakingStatus.MAYBE: EMOJIS["maybe"],
utils.MatchmakingStatus.IGNORED: EMOJIS["ignore"],
}
BUTTONS = {
"match_ready": f"{EMOJIS['ready']} Sono pronto per iniziare!",
"match_wait_for_me": f"{EMOJIS['wait_for_me']} Ci sarò, aspettatemi!",
"match_maybe": f"{EMOJIS['maybe']} Forse vengo, se non ci sono fate senza di me.",
"match_ignore": f"{EMOJIS['ignore']} Non ci sarò.",
"match_close": f"{EMOJIS['close']} ADMIN: Avvia la partita",
"match_cancel": f"{EMOJIS['cancel']} ADMIN: Annulla la partita"
}
TICKER_TEXT = {
"match_ready": f"{EMOJIS['ready']} Hai detto che sei pronto per giocare!",
"match_wait_for_me": f"{EMOJIS['wait_for_me']} Hai chiesto agli altri di aspettarti.",
"match_maybe": f"{EMOJIS['maybe']} Hai detto che forse ci sarai.",
"match_ignore": f"{EMOJIS['ignore']} Non hai intenzione di partecipare.",
"match_close": f"{EMOJIS['close']} Hai notificato tutti che la partita sta iniziando.",
"match_cancel": f"{EMOJIS['cancel']} Hai annullato la partita."
}
GAME_START = {
int(utils.MatchmakingStatus.READY): "🔵 Che <b>{match_title}</b> abbia inizio!",
int(utils.MatchmakingStatus.WAIT_FOR_ME): "🕒 Sbrigati! <b>{match_title}</b> sta per iniziare!",
int(utils.MatchmakingStatus.MAYBE): "❓ <b>{match_title}</b> sta iniziando. Se vuoi partecipare, fai in fretta!",
}
class ERRORS:
INVALID_SYNTAX = "⚠ Sintassi del comando errata.\nSintassi: <pre>/mm [minplayers-][maxplayers] ['per'] (gamename) \\n[descrizione]</pre>"
NOT_ADMIN = "⚠ Non sei il creatore di questo match!"
MATCH_CLOSED = "⚠ Il matchmaking per questa partita è terminato."
# Pug sender
class PUG:
HERE_HAVE_A_PUG = '🐶 Ecco, tieni un <a href="{image_url}">carlino</a>.'
class ERRORS:
PRIVATE_CHAT_ONLY = "⚠ Foto di carlini possono essere inviate esclusivamente in chat privata, in seguito al Disegno di Legge Intergalattico n. 5116."
# Dice roller
class ROLL:
SUCCESS = "🎲 {result}"
SYMBOLS = {
dice.elements.Div: "/",
dice.elements.Mul: "*",
dice.elements.Sub: "-",
dice.elements.Add: "+",
dice.elements.Modulo: "%",
dice.elements.AddEvenSubOdd: "+-",
dice.elements.Highest: "h",
dice.elements.Lowest: "l",
dice.elements.Middle: "m",
dice.elements.Again: "a",
dice.elements.Successes: "e",
dice.elements.SuccessFail: "f",
dice.elements.ArrayAdd: ".+",
dice.elements.ArraySub: ".-",
dice.elements.Array: ",",
dice.elements.Extend: "|",
dice.elements.Reroll: "r",
dice.elements.Explode: "x",
dice.elements.ForceReroll: "rr"
}
class ERRORS:
INVALID_SYNTAX = "⚠ Sintassi del tiro di dadi non valida."
DICE_ERROR = "⚠ Il tiro di dadi è fallito."
# Ship creator
class SHIP:
RESULT = "💕 {one} + {two} = <b>{result}</b>"
class ERRORS:
INVALID_SYNTAX = "⚠ Non hai specificato correttamente i due nomi!\nSintassi: <code>/ship (nome) (nome)</code>"
INVALID_NAMES = "⚠ I nomi specificati non sono validi.\nRiprova con dei nomi diversi!"
# Get information about a spell
class SPELL:
HEADER = "🔍 La magia <b>{name}</b> ha le seguenti proprietà (v{version}):\n"
ACCURACY = "Precisione - <b>{accuracy}%</b>\n"
DAMAGE = "Danni - <b>{number}d{type}{constant}</b> <i>(in media {avg})</i>\n"
TYPE = "Tipo - <b>{type}</b>\n"
REPEAT = "Multiattacco - <b>×{repeat}</b>\n"
HEALING = "Cura - <b>{number}d{type}{constant}</b> <i>(in media {avg})</i>\n"
STAT = "Attrib. - <b>{name}{change}</b>\n"
STATUS_EFFECT = "Infligge - <b>{effect}</b> (<b>{chance}%</b> di probabilità)"
NOTHING = "<i>Chi la usa sguazza nell'acqua, senza ottenere alcun effetto.</i>"
class ERRORS:
INVALID_SYNTAX = "⚠ Non hai specificato la magia di cui vuoi conoscere i dettagli!\nSintassi: <code>/spell (nome)</code>"
# Game stats updates
class STATSUPDATE:
class BRAWLHALLA:
SOLO = "✳️ {username} ha ora <b>{rating}</b> ({delta}) Elo 1v1 su Brawlhalla!"
TEAM = "✳️ {username}+{other} hanno ora <b>{rating}</b> Elo 2v2 su Brawlhalla!"
# Secondo me, è colpa delle stringhe.
SMECDS = "🤔 Secondo me, è colpa {ds}."
# Wiki notifications
class WIKI:
PAGE_LOCKED = '🔒 La pagina wiki <a href="https://ryg.steffo.eu/wiki/{key}">{key}</a> è stata bloccata da <b>{user}</b>.'
PAGE_UNLOCKED = '🔓 La pagina wiki <a href="https://ryg.steffo.eu/wiki/{key}">{key}</a> è stata sbloccata da <b>{user}</b>.'
PAGE_UPDATED = '📖 La pagina wiki <a href="https://ryg.steffo.eu/wiki/{key}">{key}</a> è stata modificata da <b>{user}</b>.\n{reason} [{change}]'

View file

@ -2,30 +2,40 @@ import datetime
import random
import typing
import db
import errors
import stagismo
from utils import smecds, cast, errors, emojify, reply_msg
# python-telegram-bot has a different name
# noinspection PyPackageRequirements
from telegram import Bot, Update, InlineKeyboardMarkup, InlineKeyboardButton
import telegram
# noinspection PyPackageRequirements
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
# noinspection PyPackageRequirements
from telegram.error import TimedOut, Unauthorized, BadRequest, TelegramError
import dice
import sys
import os
import cast
import re
import logging
import configparser
import markovify
import raven
import coloredlogs
import strings
import time
import requests
IKMarkup = telegram.InlineKeyboardMarkup
IKButton = telegram.InlineKeyboardButton
# Markov model
# Markov models
try:
with open("markovmodel.json") as file:
model = markovify.Text.from_json(file.read())
with open("markovmodels/default.json") as file:
default_model = markovify.Text.from_json(file.read())
except Exception:
model = None
default_model = None
try:
with open("markovmodels/dnd4.json") as file:
dnd4_model = markovify.Text.from_json(file.read())
except Exception:
dnd4_model = None
logging.getLogger().disabled = True
logger = logging.getLogger(__name__)
@ -35,6 +45,7 @@ coloredlogs.install(level="DEBUG", logger=logger)
# Init the config reader
config = configparser.ConfigParser()
config.read("config.ini")
main_group_id = int(config["Telegram"]["main_group"])
discord_connection = None
@ -45,26 +56,39 @@ sentry = raven.Client(config["Sentry"]["token"],
hook_libraries=[])
def catch_and_report(func: "function"):
def new_func(bot: Bot, update: Update):
def reply(bot: telegram.Bot, update: telegram.Update, string: str, ignore_escaping=False, disable_web_page_preview=True, **kwargs) -> telegram.Message:
while True:
try:
return reply_msg(bot, update.message.chat.id, string, ignore_escaping=ignore_escaping, disable_web_page_preview=disable_web_page_preview, **kwargs)
except Unauthorized:
if update.message.chat.type == telegram.Chat.PRIVATE:
return reply_msg(bot, main_group_id, strings.TELEGRAM.ERRORS.UNAUTHORIZED_USER,
mention=update.message.from_user.mention_html())
else:
return reply_msg(bot, main_group_id, strings.TELEGRAM.ERRORS.UNAUTHORIZED_GROUP,
group=(update.message.chat.title or update.message.chat.id))
except TimedOut:
time.sleep(1)
pass
# noinspection PyUnresolvedReferences
def command(func: "function"):
def new_func(bot: telegram.Bot, update: telegram.Update):
# noinspection PyBroadException
try:
bot.send_chat_action(update.message.chat.id, telegram.ChatAction.TYPING)
return func(bot, update)
except TimedOut:
logger.warning(f"Telegram timed out in {update}")
except Exception:
logger.error(f"Critical error: {sys.exc_info()}")
# noinspection PyBroadException
try:
bot.send_message(int(config["Telegram"]["main_group"]),
"☢ **ERRORE CRITICO!** \n"
f"Il bot ha ignorato il comando.\n"
f"Una segnalazione di errore è stata automaticamente mandata a @Steffo.\n\n"
f"Dettagli dell'errore:\n"
f"```\n"
f"{sys.exc_info()}\n"
f"```", parse_mode="Markdown")
reply_msg(bot, main_group_id, strings.TELEGRAM.ERRORS.CRITICAL_ERROR,
exc_info=repr(sys.exc_info()))
except Exception:
logger.error(f"Double critical error: {sys.exc_info()}")
if not __debug__:
sentry.user_context({
"id": update.effective_user.id,
"telegram": {
@ -80,40 +104,58 @@ def catch_and_report(func: "function"):
return new_func
@catch_and_report
def cmd_ping(bot: Bot, update: Update):
bot.send_message(update.message.chat.id, "🏓 Pong!")
@catch_and_report
def cmd_register(bot: Bot, update: Update):
# noinspection PyUnresolvedReferences
def database_access(func: "function"):
def new_func(bot: telegram.Bot, update: telegram.Update):
try:
session = db.Session()
return func(bot, update, session)
except Exception:
logger.error(f"Database error: {sys.exc_info()}")
sentry.captureException()
finally:
try:
session.close()
except Exception:
pass
return new_func
@command
def cmd_ping(bot: telegram.Bot, update: telegram.Update):
reply(bot, update, strings.PONG)
@command
@database_access
def cmd_link(bot: telegram.Bot, update: telegram.Update, session: db.Session):
try:
username = update.message.text.split(" ", 1)[1]
except IndexError:
bot.send_message(update.message.chat.id, "⚠️ Non hai specificato un username!")
reply(bot, update, strings.LINK.ERRORS.INVALID_SYNTAX)
session.close()
return
try:
t = db.Telegram.create(session,
royal_username=username,
telegram_user=update.message.from_user)
except errors.NotFoundError:
reply(bot, update, strings.LINK.ERRORS.NOT_FOUND)
session.close()
return
except errors.AlreadyExistingError:
bot.send_message(update.message.chat.id, "⚠ Il tuo account Telegram è già collegato a un account RYG o"
" l'account RYG che hai specificato è già collegato a un account"
" Telegram.")
reply(bot, update, strings.LINK.ERRORS.ALREADY_EXISTING)
session.close()
return
session.add(t)
session.commit()
bot.send_message(update.message.chat.id, "✅ Sincronizzazione completata!")
session.close()
reply(bot, update, strings.LINK.SUCCESS)
@catch_and_report
def cmd_discord(bot: Bot, update: Update):
@command
def cmd_cv(bot: telegram.Bot, update: telegram.Update):
if discord_connection is None:
bot.send_message(update.message.chat.id, "⚠ Il bot non è collegato a Discord al momento.")
reply(bot, update, strings.BRIDGE.ERRORS.INACTIVE_BRIDGE)
return
# dirty hack as usual
if update.message.text.endswith("full"):
@ -121,70 +163,49 @@ def cmd_discord(bot: Bot, update: Update):
else:
discord_connection.send("get cv")
message = discord_connection.recv()
bot.send_message(update.message.chat.id, message, disable_web_page_preview=True, parse_mode="HTML")
reply(bot, update, message)
@catch_and_report
def cmd_cast(bot: Bot, update: Update):
try:
spell: str = update.message.text.split(" ", 1)[1]
except IndexError:
bot.send_message(update.message.chat.id, "⚠️ Non hai specificato nessun incantesimo!\n"
"Sintassi corretta: `/cast <nome_incantesimo>`", parse_mode="Markdown")
return
# Open a new db session
session = db.Session()
# Find a target for the spell
target = random.sample(session.query(db.Telegram).all(), 1)[0]
# Close the session
session.close()
# END
bot.send_message(update.message.chat.id, cast.cast(spell_name=spell,
target_name=target.username if target.username is not None
else target.first_name, platform="telegram"),
parse_mode="HTML")
@command
def cmd_cast(bot: telegram.Bot, update: telegram.Update):
reply(bot, update, strings.CAST.ERRORS.NOT_YET_AVAILABLE)
@catch_and_report
def cmd_color(bot: Bot, update: Update):
bot.send_message(update.message.chat.id, "I am sorry, unknown error occured during working with your request,"
" Admin were notified")
@command
def cmd_color(bot: telegram.Bot, update: telegram.Update):
reply(bot, update, strings.COLOR)
@catch_and_report
def cmd_smecds(bot: Bot, update: Update):
ds = random.sample(stagismo.listona, 1)[0]
bot.send_message(update.message.chat.id, f"Secondo me, è colpa {ds}.")
@command
def cmd_smecds(bot: telegram.Bot, update: telegram.Update):
ds = random.sample(smecds, 1)[0]
reply(bot, update, strings.SMECDS, ds=ds)
@catch_and_report
def cmd_ciaoruozi(bot: Bot, update: Update):
@command
def cmd_ciaoruozi(bot: telegram.Bot, update: telegram.Update):
if update.message.from_user.username.lstrip("@") == "MeStakes":
bot.send_message(update.message.chat.id, "Ciao me!")
reply(bot, update, strings.CIAORUOZI.THE_LEGEND_HIMSELF)
else:
bot.send_message(update.message.chat.id, "Ciao Ruozi!")
reply(bot, update, strings.CIAORUOZI.SOMEBODY_ELSE)
@catch_and_report
def cmd_ahnonlosoio(bot: Bot, update: Update):
@command
def cmd_ahnonlosoio(bot: telegram.Bot, update: telegram.Update):
if update.message.reply_to_message is not None and update.message.reply_to_message.text in [
"/ahnonlosoio", "/ahnonlosoio@royalgamesbot", "Ah, non lo so io!", "Ah, non lo so neppure io!"
"/ahnonlosoio", "/ahnonlosoio@royalgamesbot", strings.AHNONLOSOIO.ONCE, strings.AHNONLOSOIO.AGAIN
]:
bot.send_message(update.message.chat.id, "Ah, non lo so neppure io!")
reply(bot, update, strings.AHNONLOSOIO.AGAIN)
else:
bot.send_message(update.message.chat.id, "Ah, non lo so io!")
reply(bot, update, strings.AHNONLOSOIO.ONCE)
@catch_and_report
def cmd_balurage(bot: Bot, update: Update):
session = db.Session()
try:
@command
@database_access
def cmd_balurage(bot: telegram.Bot, update: telegram.Update, session: db.Session):
user = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none()
if user is None:
bot.send_message(update.message.chat.id,
"⚠ Il tuo account Telegram non è registrato al RYGdb!\n\n"
"Registrati con `/register@royalgamesbot <nomeutenteryg>`.",
parse_mode="Markdown")
reply(bot, update, strings.LINK.ERRORS.ROYALNET_NOT_LINKED)
return
try:
reason = update.message.text.split(" ", 1)[1]
@ -194,62 +215,64 @@ def cmd_balurage(bot: Bot, update: Update):
session.add(br)
session.commit()
bot.send_message(update.message.chat.id, f"😡 Stai sfogando la tua ira sul bot!")
except Exception:
raise
finally:
session.close()
@catch_and_report
def cmd_diario(bot: Bot, update: Update):
session = db.Session()
try:
def parse_diario(session: db.Session, text: str):
match = re.match(r'"?([^"]+)"? (?:—|-{1,2}) ?@?([A-Za-z0-9_]+)$', text)
if match is None:
return None, text
text_string = match.group(1)
author_string = match.group(2).lower()
royal = session.query(db.Royal).filter(db.func.lower(db.Royal.username) == author_string).first()
if royal is not None:
author = royal.telegram[0]
return author, text_string
author = session.query(db.Telegram).filter(db.func.lower(db.Telegram.username) == author_string).first()
if author is not None:
return author, text_string
return None, text_string
@command
@database_access
def cmd_diario(bot: telegram.Bot, update: telegram.Update, session: db.Session):
user = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none()
if user is None:
bot.send_message(update.message.chat.id, "⚠ Il tuo account Telegram non è registrato al RYGdb!"
" Registrati con `/register@royalgamesbot <nomeutenteryg>`.",
parse_mode="Markdown")
reply(bot, update, strings.LINK.ERRORS.ROYALNET_NOT_LINKED)
return
try:
text = update.message.text.split(" ", 1)[1]
author = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none()
saver = author
saver = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none()
author, actual_text = parse_diario(session, text)
except IndexError:
if update.message.reply_to_message is None:
bot.send_message(update.message.chat.id,
f"⚠ Non hai specificato cosa aggiungere al diario!\n\n"
f"Puoi rispondere `/diario@royalgamesbot` al messaggio che vuoi salvare nel diario"
f" oppure scrivere `/diario@royalgamesbot <messaggio>`"
f" per aggiungere quel messaggio nel diario.",
parse_mode="Markdown")
reply(bot, update, strings.DIARIO.ERRORS.INVALID_SYNTAX)
return
text = update.message.reply_to_message.text
if update.message.forward_from:
author = session.query(db.Telegram) \
.filter_by(telegram_id=update.message.forward_from.id) \
.one_or_none()
else:
author = session.query(db.Telegram)\
.filter_by(telegram_id=update.message.reply_to_message.from_user.id)\
.one_or_none()
saver = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none()
if text is None:
bot.send_message(update.message.chat.id, f"⚠ Il messaggio a cui hai risposto non contiene testo.")
reply(bot, update, strings.DIARIO.ERRORS.NO_TEXT)
return
diario = db.Diario(timestamp=datetime.datetime.now(),
saver=saver,
author=author,
text=text)
text=actual_text)
session.add(diario)
session.commit()
bot.send_message(update.message.chat.id,
f"✅ Riga [#{diario.id}](https://ryg.steffo.eu/diario#entry-{diario.id}) aggiunta al diario!",
parse_mode="Markdown", disable_web_page_preview=True)
except Exception:
raise
finally:
session.close()
reply(bot, update, strings.DIARIO.SUCCESS, ignore_escaping=True, diario=diario.to_telegram())
@catch_and_report
def cmd_vote(bot: Bot, update: Update):
session = db.Session()
try:
@command
@database_access
def cmd_vote(bot: telegram.Bot, update: telegram.Update, session: db.Session):
user = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none()
if user is None:
bot.send_message(update.message.chat.id,
@ -274,41 +297,115 @@ def cmd_vote(bot: Bot, update: Update):
return
session.add(vote)
session.flush()
inline_keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("🔵 Sì", callback_data="vote_yes")],
[InlineKeyboardButton("🔴 No", callback_data="vote_no")],
[InlineKeyboardButton("⚫️ Astieniti", callback_data="vote_abstain")]])
inline_keyboard = IKMarkup([[IKButton("🔵 Sì", callback_data="vote_yes")],
[IKButton("🔴 No", callback_data="vote_no")],
[IKButton("⚫️ Astieniti", callback_data="vote_abstain")]])
message = bot.send_message(update.message.chat.id, vote.generate_text(session=session),
reply_markup=inline_keyboard,
parse_mode="HTML")
vote.message_id = message.message_id
session.commit()
except Exception:
raise
finally:
session.close()
@catch_and_report
def on_callback_query(bot: Bot, update: Update):
def generate_search_message(term, entries):
msg = strings.DIARIOSEARCH.HEADER.format(term=term)
if len(entries) < 100:
for entry in entries[:5]:
msg += f'<a href="https://ryg.steffo.eu/diario#entry-{entry.id}">#{entry.id}</a> di <i>{entry.author or "Anonimo"}</i>\n{entry.text}\n\n'
if len(entries) > 5:
msg += "I termini comapiono anche nelle righe:\n"
for entry in entries[5:]:
msg += f'<a href="https://ryg.steffo.eu/diario#entry-{entry.id}">#{entry.id}</a> '
else:
for entry in entries[:100]:
msg += f'<a href="https://ryg.steffo.eu/diario#entry-{entry.id}">#{entry.id}</a> '
for entry in entries[100:]:
msg += f"#{entry.id} "
return msg
@command
@database_access
def cmd_search(bot: telegram.Bot, update: telegram.Update, session: db.Session):
try:
query = update.message.text.split(" ", 1)[1]
except IndexError:
reply(bot, update, strings.DIARIOSEARCH.ERRORS.INVALID_SYNTAX, command="search")
return
query = query.replace('%', '\\%').replace('_', '\\_')
entries = session.query(db.Diario)\
.filter(db.Diario.text.op("~*")(r"(?:[\s\.,:;!?\"'<{([]+|^)"
+ query +
r"(?:[\s\.,:;!?\"'>\})\]]+|$)"))\
.order_by(db.Diario.id.desc())\
.all()
bot.send_message(update.message.chat.id, generate_search_message(f"<b>{query}</b>", entries), parse_mode="HTML")
@command
@database_access
def cmd_regex(bot: telegram.Bot, update: telegram.Update, session: db.Session):
try:
query = update.message.text.split(" ", 1)[1]
except IndexError:
reply(bot, update, strings.DIARIOSEARCH.ERRORS.INVALID_SYNTAX, command="regex")
return
query = query.replace('%', '\\%').replace('_', '\\_')
entries = session.query(db.Diario).filter(db.Diario.text.op("~*")(query)).order_by(db.Diario.id.desc()).all()
try:
bot.send_message(update.message.chat.id, generate_search_message(f"<code>{query}</code>", entries), parse_mode="HTML")
except (BadRequest, TelegramError):
reply(bot, update, strings.DIARIOSEARCH.ERRORS.RESULTS_TOO_LONG)
@command
@database_access
def cmd_mm(bot: telegram.Bot, update: telegram.Update, session: db.Session):
user = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none()
if user is None:
reply(bot, update, strings.LINK.ERRORS.ROYALNET_NOT_LINKED)
return
match = re.match(r"/(?:mm|matchmaking)(?:@royalgamesbot)?(?: (?:([0-9]+)-)?([0-9]+))? (?:per )?([A-Za-z0-9!\-_. ]+)(?:.*\n(.+))?",
update.message.text)
if match is None:
reply(bot, update, strings.MATCHMAKING.ERRORS.INVALID_SYNTAX)
return
min_players, max_players, match_name, match_desc = match.group(1, 2, 3, 4)
db_match = db.Match(timestamp=datetime.datetime.now(),
match_title=match_name,
match_desc=match_desc,
min_players=min_players,
max_players=max_players,
creator=user)
session.add(db_match)
session.flush()
inline_keyboard = IKMarkup([([IKButton(strings.MATCHMAKING.BUTTONS[key], callback_data=key)]) for key in strings.MATCHMAKING.BUTTONS])
message = bot.send_message(config["Telegram"]["announcement_group"], db_match.generate_text(session=session),
parse_mode="HTML",
reply_markup=inline_keyboard)
db_match.message_id = message.message_id
session.commit()
def on_callback_query(bot: telegram.Bot, update: telegram.Update):
try:
session = db.Session()
if update.callback_query.data.startswith("vote_"):
if update.callback_query.data == "vote_yes":
choice = db.VoteChoices.YES
status = db.VoteChoices.YES
emoji = "🔵"
elif update.callback_query.data == "vote_no":
choice = db.VoteChoices.NO
status = db.VoteChoices.NO
emoji = "🔴"
elif update.callback_query.data == "vote_abstain":
choice = db.VoteChoices.ABSTAIN
status = db.VoteChoices.ABSTAIN
emoji = "⚫️"
else:
raise NotImplementedError()
if update.callback_query.data.startswith("vote_"):
session = db.Session()
try:
user = session.query(db.Telegram).filter_by(telegram_id=update.callback_query.from_user.id).one_or_none()
if user is None:
bot.answer_callback_query(update.callback_query.id, show_alert=True,
text="⚠ Il tuo account Telegram non è registrato al RYGdb!"
" Registrati con `/register@royalgamesbot <nomeutenteryg>`.",
text=strings.LINK.ERRORS.ROYALNET_NOT_LINKED,
parse_mode="Markdown")
return
question = session.query(db.VoteQuestion)\
@ -316,106 +413,177 @@ def on_callback_query(bot: Bot, update: Update):
.one()
answer = session.query(db.VoteAnswer).filter_by(question=question, user=user).one_or_none()
if answer is None:
answer = db.VoteAnswer(question=question, choice=choice, user=user)
answer = db.VoteAnswer(question=question, choice=status, user=user)
session.add(answer)
bot.answer_callback_query(update.callback_query.id, text=f"Hai votato {emoji}.", cache_time=1)
elif answer.choice == choice:
elif answer.choice == status:
session.delete(answer)
bot.answer_callback_query(update.callback_query.id, text=f"Hai ritratto il tuo voto.", cache_time=1)
else:
answer.choice = choice
answer.choice = status
bot.answer_callback_query(update.callback_query.id, text=f"Hai cambiato il tuo voto in {emoji}.",
cache_time=1)
session.commit()
inline_keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("🔵 Sì",
callback_data="vote_yes")],
[InlineKeyboardButton("🔴 No",
callback_data="vote_no")],
[InlineKeyboardButton("⚫️ Astieniti",
callback_data="vote_abstain")]])
inline_keyboard = IKMarkup([[IKButton("🔵 Sì", callback_data="vote_yes")],
[IKButton("🔴 No", callback_data="vote_no")],
[IKButton("⚫️ Astieniti", callback_data="vote_abstain")]])
bot.edit_message_text(message_id=update.callback_query.message.message_id,
chat_id=update.callback_query.message.chat.id,
text=question.generate_text(session),
reply_markup=inline_keyboard,
parse_mode="HTML")
elif update.callback_query.data.startswith("match_"):
user = session.query(db.Telegram).filter_by(telegram_id=update.callback_query.from_user.id).one_or_none()
if user is None:
bot.answer_callback_query(update.callback_query.id,
show_alert=True,
text=strings.LINK.ERRORS.ROYALNET_NOT_LINKED,
parse_mode="Markdown")
return
match = session.query(db.Match).filter_by(message_id=update.callback_query.message.message_id).one()
if update.callback_query.data == "match_close":
if not (match.creator == user or user.telegram_id == 25167391):
bot.answer_callback_query(update.callback_query.id,
show_alert=True,
text=strings.MATCHMAKING.ERRORS.NOT_ADMIN)
return
match.closed = True
for partecipation in match.players:
if int(partecipation.status) >= 1:
try:
reply_msg(bot, partecipation.user.telegram_id, strings.MATCHMAKING.GAME_START[int(partecipation.status)], **match.format_dict())
except Unauthorized:
reply_msg(bot, main_group_id, strings.TELEGRAM.ERRORS.UNAUTHORIZED_USER,
mention=partecipation.user.mention())
elif update.callback_query.data == "match_cancel":
if not (match.creator == user or user.telegram_id == 25167391):
bot.answer_callback_query(update.callback_query.id,
show_alert=True,
text=strings.MATCHMAKING.ERRORS.NOT_ADMIN)
return
match.closed = True
status = {
"match_ready": db.MatchmakingStatus.READY,
"match_wait_for_me": db.MatchmakingStatus.WAIT_FOR_ME,
"match_maybe": db.MatchmakingStatus.MAYBE,
"match_ignore": db.MatchmakingStatus.IGNORED,
"match_close": None,
"match_cancel": None,
}.get(update.callback_query.data)
if status:
if match.closed:
bot.answer_callback_query(update.callback_query.id,
show_alert=True,
text=strings.MATCHMAKING.ERRORS.MATCH_CLOSED)
return
partecipation = session.query(db.MatchPartecipation).filter_by(match=match, user=user).one_or_none()
if partecipation is None:
partecipation = db.MatchPartecipation(match=match, status=status.value, user=user)
session.add(partecipation)
else:
partecipation.status = status.value
session.commit()
bot.answer_callback_query(update.callback_query.id,
text=strings.MATCHMAKING.TICKER_TEXT[update.callback_query.data],
cache_time=1)
if not match.closed:
inline_keyboard = IKMarkup([([IKButton(strings.MATCHMAKING.BUTTONS[key], callback_data=key)]) for key in strings.MATCHMAKING.BUTTONS])
else:
inline_keyboard = None
while True:
try:
bot.edit_message_text(message_id=update.callback_query.message.message_id,
chat_id=config["Telegram"]["announcement_group"],
text=match.generate_text(session),
reply_markup=inline_keyboard,
parse_mode="HTML")
break
except BadRequest:
break
except TimedOut:
time.sleep(1)
except Exception:
raise
try:
bot.answer_callback_query(update.callback_query.id,
show_alert=True,
text=strings.TELEGRAM.ERRORS.CRITICAL_ERROR_QUERY)
except Exception:
pass
logger.error(f"Critical error: {sys.exc_info()}")
sentry.user_context({
"id": update.effective_user.id,
"telegram": {
"username": update.effective_user.username,
"first_name": update.effective_user.first_name,
"last_name": update.effective_user.last_name
}
})
sentry.extra_context({
"update": update.to_dict()
})
sentry.captureException()
finally:
try:
# noinspection PyUnboundLocalVariable
session.close()
except Exception:
pass
@catch_and_report
def cmd_eat(bot: Bot, update: Update):
@command
def cmd_eat(bot: telegram.Bot, update: telegram.Update):
try:
food: str = update.message.text.split(" ", 1)[1].capitalize()
except IndexError:
bot.send_message(update.message.chat.id, "⚠️ Non hai specificato cosa mangiare!\n"
"Sintassi corretta: `/eat <cibo>`", parse_mode="Markdown")
reply(bot, update, strings.EAT.ERRORS.INVALID_SYNTAX)
return
if "tonnuooooooro" in food.lower():
bot.send_message(update.message.chat.id, "👻 Il pesce che hai mangiato era posseduto.\n"
"Spooky!")
return
bot.send_message(update.message.chat.id, f"🍗 Hai mangiato {food}!")
reply(bot, update, strings.EAT.FOODS.get(food.lower(), strings.EAT.FOODS["_default"]), food=food)
@catch_and_report
def cmd_ship(bot: Bot, update: Update):
@command
def cmd_ship(bot: telegram.Bot, update: telegram.Update):
try:
_, name_one, name_two = update.message.text.split(" ", 2)
names = update.message.text.split(" ")
name_one = names[1]
name_two = names[2]
except ValueError:
bot.send_message(update.message.chat.id, "⚠️ Non hai specificato correttamente i due nomi!\n"
"Sintassi corretta: `/ship <nome> <nome>`", parse_mode="Markdown")
reply(bot, update, strings.SHIP.ERRORS.INVALID_SYNTAX)
return
name_one = name_one.lower()
name_two = name_two.lower()
part_one = re.search(r"^[A-Za-z][^aeiouAEIOU]*[aeiouAEIOU]?", name_one)
part_two = re.search(r"[^aeiouAEIOU]*[aeiouAEIOU]?[A-Za-z]$", name_two)
try:
mixed = part_one.group(0) + part_two.group(0)
except Exception:
bot.send_message(update.message.chat.id, "⚠ I nomi specificati non sono validi.\n"
"Riprova con dei nomi diversi!")
match_one = re.search(r"^[A-Za-z][^aeiouAEIOU]*[aeiouAEIOU]?", name_one)
if match_one is None:
part_one = name_one[:int(len(name_one) / 2)]
else:
part_one = match_one.group(0)
match_two = re.search(r"[^aeiouAEIOU]*[aeiouAEIOU]?[A-Za-z]$", name_two)
if match_two is None:
part_two = name_two[int(len(name_two) / 2):]
else:
part_two = match_two.group(0)
mixed = part_one + part_two # TODO: find out what exceptions this could possibly raise
reply(bot, update, strings.SHIP.RESULT,
one=name_one.capitalize(),
two=name_two.capitalize(),
result=mixed.capitalize())
@command
def cmd_bridge(bot: telegram.Bot, update: telegram.Update):
if discord_connection is None:
reply(bot, update, strings.BRIDGE.ERRORS.INACTIVE_BRIDGE)
return
if part_one is None or part_two is None:
bot.send_message(update.message.chat.id, "⚠ I nomi specificati non sono validi.\n"
"Riprova con dei nomi diversi!")
return
bot.send_message(update.message.chat.id, f"💕 {name_one.capitalize()} + {name_two.capitalize()} ="
f" {mixed.capitalize()}")
@catch_and_report
def cmd_profile(bot: Bot, update: Update):
session = db.Session()
user = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).join(db.Royal).one_or_none()
session.close()
if user is None:
bot.send_message(update.message.chat.id, "⚠ Non sei connesso a Royalnet!\n"
"Per registrarti, utilizza il comando /register.")
return
bot.send_message(update.message.chat.id, f"👤 [Profilo di {user.royal.username}]"
f"(http://ryg.steffo.eu/profile/{user.royal.username})\n"
f"Attualmente, hai **{user.royal.fiorygi}** fiorygi.",
parse_mode="Markdown")
@catch_and_report
def cmd_bridge(bot: Bot, update: Update):
try:
data = update.message.text.split(" ", 1)[1]
except IndexError:
bot.send_message(update.message.chat.id,
"⚠ Non hai specificato un comando!\n"
"Sintassi corretta: `/bridge <comando> <argomenti>`",
parse_mode="Markdown")
reply(bot, update, strings.BRIDGE.ERRORS.INVALID_SYNTAX)
return
discord_connection.send(f"!{data}")
result = discord_connection.recv()
if result == "error":
bot.send_message(update.message.chat.id, "⚠ Il comando specificato non esiste.")
reply(bot, update, strings.BRIDGE.FAILURE)
if result == "success":
bot.send_message(update.message.chat.id, "⏩ Comando eseguito su Discord.")
reply(bot, update, strings.BRIDGE.SUCCESS)
def parse_timestring(timestring: str) -> typing.Union[datetime.timedelta, datetime.datetime]:
@ -466,8 +634,9 @@ def parse_timestring(timestring: str) -> typing.Union[datetime.timedelta, dateti
raise ValueError("Nothing was found.")
@catch_and_report
def cmd_newevent(bot: Bot, update: Update):
@command
@database_access
def cmd_newevent(bot: telegram.Bot, update: telegram.Update, session: db.Session):
try:
_, timestring, name_desc = update.message.text.split(" ", 2)
except ValueError:
@ -499,7 +668,6 @@ def cmd_newevent(bot: Bot, update: Update):
"per favore inserisci una data futura.\n", parse_mode="Markdown")
return
# Create the event
session = db.Session()
telegram_user = session.query(db.Telegram)\
.filter_by(telegram_id=update.message.from_user.id)\
.join(db.Royal)\
@ -516,15 +684,13 @@ def cmd_newevent(bot: Bot, update: Update):
# Save the event
session.add(event)
session.commit()
session.close()
bot.send_message(update.message.chat.id, "✅ Evento aggiunto al Calendario Royal Games!")
@catch_and_report
def cmd_calendar(bot: Bot, update: Update):
session = db.Session()
@command
@database_access
def cmd_calendar(bot: telegram.Bot, update: telegram.Update, session: db.Session):
next_events = session.query(db.Event).filter(db.Event.time > datetime.datetime.now()).order_by(db.Event.time).all()
session.close()
msg = "📆 Prossimi eventi\n"
for event in next_events:
if event.time_left.days >= 1:
@ -537,39 +703,128 @@ def cmd_calendar(bot: Bot, update: Update):
bot.send_message(update.message.chat.id, msg, parse_mode="HTML", disable_web_page_preview=True)
@catch_and_report
def cmd_markov(bot: Bot, update: Update):
if model is None:
bot.send_message(update.message.chat.id, "⚠️ Il modello Markov non è disponibile.")
@command
def cmd_markov(bot: telegram.Bot, update: telegram.Update):
if default_model is None:
reply(bot, update, strings.MARKOV.ERRORS.NO_MODEL)
return
try:
_, first_word = update.message.text.split(" ", 1)
except ValueError:
sentence = model.make_sentence(tries=1000)
first_word = update.message.text.split(" ")[1]
except IndexError:
# Any word
sentence = default_model.make_sentence(tries=1000)
if sentence is None:
bot.send_message(update.message.chat.id, "⚠ Complimenti! Hai vinto la lotteria di Markov!\n"
"O forse l'hai persa.\n"
"Non sono riuscito a generare una frase, riprova.")
reply(bot, update, strings.MARKOV.ERRORS.GENERATION_FAILED)
return
bot.send_message(update.message.chat.id, sentence)
reply(bot, update, sentence)
return
# Specific word
try:
sentence = default_model.make_sentence_with_start(first_word, tries=1000)
except KeyError:
reply(bot, update, strings.MARKOV.ERRORS.MISSING_WORD)
return
if sentence is None:
reply(bot, update, strings.MARKOV.ERRORS.SPECIFIC_WORD_FAILED)
return
reply(bot, update, sentence)
@command
def cmd_dndmarkov(bot: telegram.Bot, update: telegram.Update):
if dnd4_model is None:
reply(bot, update, strings.MARKOV.ERRORS.NO_MODEL)
return
try:
first_word = update.message.text.split(" ")[1]
except IndexError:
# Any word
sentence = dnd4_model.make_sentence(tries=1000)
if sentence is None:
reply(bot, update, strings.MARKOV.ERRORS.GENERATION_FAILED)
return
reply(bot, update, sentence)
return
# Specific word
try:
sentence = dnd4_model.make_sentence_with_start(first_word, tries=1000)
except KeyError:
reply(bot, update, strings.MARKOV.ERRORS.MISSING_WORD)
return
if sentence is None:
reply(bot, update, strings.MARKOV.ERRORS.SPECIFIC_WORD_FAILED)
return
reply(bot, update, sentence)
def exec_roll(roll) -> str:
result = int(roll.evaluate())
string = ""
if isinstance(roll, dice.elements.Dice):
string += f"<b>{result}</b>"
else:
sentence = model.make_sentence_with_start(first_word, tries=1000)
if sentence is None:
bot.send_message(update.message.chat.id, "⚠ Non è stato possibile generare frasi partendo da questa"
" parola.")
return
bot.send_message(update.message.chat.id, sentence)
for index, operand in enumerate(roll.original_operands):
if operand != roll.operands[index]:
string += f"<i>{roll.operands[index]}</i>"
else:
string += f"{operand}"
if index + 1 != len(roll.original_operands):
string += strings.ROLL.SYMBOLS[roll.__class__]
string += f"=<b>{result}</b>"
return string
@catch_and_report
def cmd_roll(bot: Bot, update: Update):
@command
def cmd_roll(bot: telegram.Bot, update: telegram.Update):
dice_string = update.message.text.split(" ", 1)[1]
try:
result = dice.roll(f"{dice_string}t")
roll = dice.roll(f"{dice_string}", raw=True)
except dice.DiceBaseException:
bot.send_message(update.message.chat.id, "⚠ Il tiro dei dadi è fallito. Controlla la sintassi!")
reply(bot, update, strings.ROLL.ERRORS.INVALID_SYNTAX)
return
bot.send_message(update.message.chat.id, f"🎲 {result}")
try:
result = exec_roll(roll)
except dice.DiceFatalException:
reply(bot, update, strings.ROLL.ERRORS.DICE_ERROR)
return
reply(bot, update, strings.ROLL.SUCCESS, result=result, ignore_escaping=True)
@command
def cmd_start(bot: telegram.Bot, update: telegram.Update):
reply(bot, update, strings.TELEGRAM.BOT_STARTED)
@command
def cmd_spell(bot: telegram.Bot, update: telegram.Update):
try:
spell_name: str = update.message.text.split(" ", 1)[1]
except IndexError:
reply(bot, update, strings.SPELL.ERRORS.INVALID_SYNTAX)
return
spell = cast.Spell(spell_name)
reply(bot, update, spell.stringify())
@command
def cmd_emojify(bot: telegram.Bot, update: telegram.Update):
try:
string: str = update.message.text.split(" ", 1)[1]
except IndexError:
reply(bot, update, strings.EMOJIFY.ERRORS.INVALID_SYNTAX)
return
msg = emojify(string)
reply(bot, update, strings.EMOJIFY.RESPONSE, emojified=msg)
@command
def cmd_pug(bot: telegram.Bot, update: telegram.Update):
if update.effective_chat.type != telegram.Chat.PRIVATE:
reply(bot, update, strings.PUG.ERRORS.PRIVATE_CHAT_ONLY)
return
j = requests.get("https://dog.ceo/api/breed/pug/images/random").json()
reply(bot, update, strings.PUG.HERE_HAVE_A_PUG, disable_web_page_preview=False, image_url=j["message"])
def process(arg_discord_connection):
@ -580,27 +835,40 @@ def process(arg_discord_connection):
u = Updater(config["Telegram"]["bot_token"])
logger.info("Registering handlers...")
u.dispatcher.add_handler(CommandHandler("ping", cmd_ping))
u.dispatcher.add_handler(CommandHandler("register", cmd_register))
u.dispatcher.add_handler(CommandHandler("discord", cmd_discord))
u.dispatcher.add_handler(CommandHandler("cv", cmd_discord))
u.dispatcher.add_handler(CommandHandler("pong", cmd_ping))
u.dispatcher.add_handler(CommandHandler("link", cmd_link))
u.dispatcher.add_handler(CommandHandler("discord", cmd_cv))
u.dispatcher.add_handler(CommandHandler("cv", cmd_cv))
u.dispatcher.add_handler(CommandHandler("cast", cmd_cast))
u.dispatcher.add_handler(CommandHandler("color", cmd_color))
u.dispatcher.add_handler(CommandHandler("error", cmd_color))
u.dispatcher.add_handler(CommandHandler("smecds", cmd_smecds))
u.dispatcher.add_handler(CommandHandler("ciaoruozi", cmd_ciaoruozi))
u.dispatcher.add_handler(CommandHandler("ahnonlosoio", cmd_ahnonlosoio))
u.dispatcher.add_handler(CommandHandler("balurage", cmd_balurage))
u.dispatcher.add_handler(CommandHandler("diario", cmd_diario))
u.dispatcher.add_handler(CommandHandler("spaggia", cmd_diario))
u.dispatcher.add_handler(CommandHandler("spaggio", cmd_diario))
u.dispatcher.add_handler(CommandHandler("vote", cmd_vote))
u.dispatcher.add_handler(CommandHandler("eat", cmd_eat))
u.dispatcher.add_handler(CommandHandler("ship", cmd_ship))
u.dispatcher.add_handler(CommandHandler("profile", cmd_profile))
u.dispatcher.add_handler(CommandHandler("bridge", cmd_bridge))
u.dispatcher.add_handler(CommandHandler("newevent", cmd_newevent))
u.dispatcher.add_handler(CommandHandler("calendar", cmd_calendar))
u.dispatcher.add_handler(CommandHandler("markov", cmd_markov))
u.dispatcher.add_handler(CommandHandler("dndmarkov", cmd_dndmarkov))
u.dispatcher.add_handler(CommandHandler("roll", cmd_roll))
u.dispatcher.add_handler(CommandHandler("r", cmd_roll))
u.dispatcher.add_handler(CommandHandler("mm", cmd_mm))
u.dispatcher.add_handler(CommandHandler("matchmaking", cmd_mm))
u.dispatcher.add_handler(CommandHandler("search", cmd_search))
u.dispatcher.add_handler(CommandHandler("regex", cmd_regex))
u.dispatcher.add_handler(CommandHandler("start", cmd_start))
u.dispatcher.add_handler(CommandHandler("spell", cmd_spell))
u.dispatcher.add_handler(CommandHandler("emojify", cmd_emojify))
u.dispatcher.add_handler(CommandHandler("pug", cmd_pug))
u.dispatcher.add_handler(CommandHandler("carlino", cmd_pug))
u.dispatcher.add_handler(CommandHandler("carlini", cmd_pug))
u.dispatcher.add_handler(CallbackQueryHandler(on_callback_query))
logger.info("Handlers registered.")
u.start_polling()

View file

@ -11,4 +11,14 @@
<p>
Il tuo browser ha inviato una richiesta non valida. Magari non hai riempito qualche campo di un form?
</p>
<blockquote id="entry-966" class="entry ">
<div class="left">
<p>
<span class="text">Villa di Von Shdfisjz</span>
</p>
<p>
<cite><a class="author" href="{{ url_for("page_profile", name="Steffo") }}">Steffo</a>, <span class="timestamp">2017-07-26 18:46:43 </span> </cite>
</p>
</div>
</blockquote>
{% endblock %}

View file

@ -9,6 +9,25 @@
403 - Forbidden
</h1>
<p>
Non puoi accedere a questa pagina. Magari hai sbagliato password?
Non puoi accedere a questa pagina.
</p>
{% if g.logged_in %}
<p>
Forse dovresti provare a fare il <a href="{{ url_for("page_login") }}">login</a>...
</p>
{% else %}
<p>
Temo che questa pagina sia riservata agli amministratori...
</p>
{% endif %}
<blockquote class="entry">
<div class="left">
<p>
<span class="text">Io sono il padrone, questo è champagne, buon Natale!</span>
</p>
<p>
<cite><span class="author anonymous">Anonimo</span>, <span class="timestamp">2017-02-10 09:11:00</span></cite>
</p>
</div>
</blockquote>
{% endblock %}

View file

@ -11,14 +11,18 @@
<p>
Il server è crashato mentre cercava di generare questa pagina. Oops.
</p>
<blockquote>
<blockquote class="entry">
<div class="left">
<p>
I am sorry, unknown error occured during working with your request, Admin were notified
<span class="text">I am sorry, unknown error occured during working with your request, Admin were notified</span>
</p>
<p>
<cite><a class="author" href="https://github.com/ProtoxiDe22/Octeon">OcteonRygBot</a>, <span class="timestamp">2017-09-14 14:11:00</span></cite>
</p>
</div>
</blockquote>
<p>
L'errore <i>dovrebbe</i> essere stato segnalato a Steffo.
Se riesci e ne hai voglia, spiegagli anche cosa ha provocato l'errore.
Se riesci e ne hai voglia, spiegagli cosa ha provocato l'errore.
</p>
<i>@OcteonRygBot, 2017</i>
{% endblock %}

View file

@ -18,7 +18,7 @@
<h1>
Attività su Discord negli ultimi 7 giorni
</h1>
<canvas class="graph members-graph-7d" id="discord-members-graph-7d" height="240px"></canvas>
<canvas class="graph members-graph-7d" id="discord-members-graph-7d" height="60px"></canvas>
<script>
new Chart("discord-members-graph-7d",
{
@ -77,7 +77,7 @@
}
});
</script>
<canvas class="graph cv-graph-7d" id="discord-cv-graph-7d" height="240px"></canvas>
<canvas class="graph cv-graph-7d" id="discord-cv-graph-7d" height="60px"></canvas>
<script>
new Chart("discord-cv-graph-7d",
{
@ -122,7 +122,7 @@
}
});
</script>
<canvas class="graph channels-graph-7d" id="discord-channels-graph-7d" height="240px"></canvas>
<canvas class="graph channels-graph-7d" id="discord-channels-graph-7d" height="60px"></canvas>
<script>
new Chart("discord-channels-graph-7d",
{
@ -151,6 +151,129 @@
},
"options":{
}
});
</script>
<h1>
Attività per ogni ora nell'ultimo mese
</h1>
<canvas class="graph members-graph-hour-bucket" id="discord-members-hour-bucket" height="60px"></canvas>
<script>
new Chart("discord-members-hour-bucket",
{
"type": "line",
"data": {
"labels": [
{% for point in hourly_avg %}
"{{ point.h|int }}:00",
{% endfor %}
],
"datasets": [
{
"label": "In cv",
"borderColor": "#fe7f00",
"backgroundColor": "#fe7f0022",
"borderWidth": 4,
"cubicInterpolationMode": "monotone",
"fill": "origin",
"data": [
{% for point in hourly_avg %}
{{ point.cv_members_avg }},
{% endfor %}
]
},
{
"label": "In game",
"borderColor": "#9ae915",
"backgroundColor": "#9ae91511",
"borderWidth": 1,
"borderDash": [2],
"cubicInterpolationMode": "monotone",
"fill": "disabled",
"data": [
{% for point in hourly_avg %}
{{ point.ingame_members_avg }},
{% endfor %}
]
},
{
"label": "Online",
"borderColor": "#6dcff6",
"backgroundColor": "#6dcff611",
"borderWidth": 1,
"cubicInterpolationMode": "monotone",
"fill": 0,
"data": [
{% for point in hourly_avg %}
{{ point.online_members_avg }},
{% endfor %}
]
}
]
},
"options":{
}
});
</script>
<h1>
Confronto cv con il mese scorso
</h1>
<canvas class="graph members-graph-hour-comp" id="discord-members-hour-comp" height="60px"></canvas>
<script>
new Chart("discord-members-hour-comp",
{
"type": "line",
"data": {
"labels": [
{% for point in hourly_avg %}
"{{ point.h|int }}:00",
{% endfor %}
],
"datasets": [
{
"label": "Adesso",
"borderColor": "#a0ccff",
"backgroundColor": "#a0ccff22",
"borderWidth": 4,
"cubicInterpolationMode": "monotone",
"fill": "origin",
"data": [
{% for point in hourly_avg %}
{{ point.cv_members_avg }},
{% endfor %}
]
},
{
"label": "Il mese scorso",
"borderColor": "#d3a1ff",
"backgroundColor": "#d3a1ff11",
"borderWidth": 1,
"cubicInterpolationMode": "monotone",
"fill": "origin",
"data": [
{% for point in hourly_comp %}
{{ point.cv_members_avg }},
{% endfor %}
]
},
{
"label": "Il mese prima",
"borderColor": "#ffa3d0",
"backgroundColor": "#ffa3d011",
"borderWidth": 1,
"cubicInterpolationMode": "monotone",
"fill": "origin",
"data": [
{% for point in hourly_before %}
{{ point.cv_members_avg }},
{% endfor %}
]
}
]
},
"options":{
}
});
</script>

View file

@ -14,7 +14,7 @@
<body>
<nav>
<div class="left">
<img src="{{ url_for('static', filename='FixMeRYGLogo.jpg') }}" alt="" class="ryg-logo">
<img src="{{ url_for('static', filename='LogoRoyalGames.svg') }}" alt="" title="👑⭐️ RYG" class="ryg-logo">
<b>Royalnet</b>
<a href="/">Home</a>
</div>
@ -23,8 +23,8 @@
<span id="debug-mode">DEBUG MODE</span>
{% endif %}
<span class="login-status">
{% if session.get('username') is not none %}
<a href="{{ url_for('page_profile', name=session.get('username')) }}">{{ session.get('username') }}</a>
{% if g.user %}
<a href="{{ url_for('page_profile', name=g.user.username) }}">{{ g.user.username }}</a>
<a class="btn" href="{{ url_for('page_logout') }}">Logout</a>
{% else %}
<a class="btn" href="{{ url_for('page_login') }}">Login</a>
@ -32,6 +32,8 @@
</span>
</div>
</nav>
<div class="omnicontainer">
{% block body %}{% endblock %}
</div>
</body>
</html>

View file

@ -1,13 +1,23 @@
<blockquote id="entry-{{ entry.id }}" class="entry {% if entry.spoiler %}spoiler{% endif %}">
<blockquote id="diario-{{ entry.id }}" class="diario {% if entry.spoiler %}spoiler{% endif %}">
<div class="left">
<p>
<span class="text">{{ entry.to_html() | safe }}</span>
</p>
<p>
<cite>— {% if entry.author is not none %}<a class="author" href="{{ url_for("page_profile", name=entry.author.royal.username) }}">{{ entry.author.royal.username }}</a>{% else %}<span class="author anonymous">Anonimo</span>{% endif %}, <span class="timestamp">{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S %Z') }}</span> {% if entry.saver is not none and entry.saver != entry.author %}<span class="saver">(salvato da <a href="/profile/{{ entry.saver.royal.username }}">{{ entry.saver.royal.username }}</a>)</span>{% endif %}</cite>
<cite>
{% if entry.author is not none %}
<a class="author known" href="{{ url_for("page_profile", name=entry.author.royal.username) }}">{{ entry.author.royal.username }}</a>
{% else %}
<span class="author anonymous">Anonimo</span>
{% endif %},
<span class="timestamp">{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S %Z') }}</span>
{% if entry.saver is not none and entry.saver != entry.author %}
<span class="saver">(salvato da <a href="/profile/{{ entry.saver.royal.username }}">{{ entry.saver.royal.username }}</a>)</span>
{% endif %}
</cite>
</p>
</div>
<div class="right">
<a class="entry-id" href="{{ url_for("page_diario") }}#entry-{{ entry.id }}">#{{ entry.id }}</a>
<a class="diario-id" href="#diario-{{ entry.id }}">#{{ entry.id }}</a>
</div>
</blockquote>

View file

@ -0,0 +1,9 @@
<div class="box">
<div class="upper-box">
Citazione casuale dal diario
</div>
<div class="lower-box">
{% include "components/diarioentry.html" %}
<a href="{{ url_for("page_diario") }}">Visualizza tutto il diario</a>
</div>
</div>

View file

@ -0,0 +1,10 @@
<div class="box">
<div class="upper-box">
Prossimi eventi
</div>
<div class="lower-box">
{% for event in events %}
{% include "components/event.html" %}
{% endfor %}
</div>
</div>

View file

@ -0,0 +1,19 @@
<div class="box">
<div class="upper-box">
Noi in vari giochi
</div>
<div class="lower-box">
<ul class="multicolumn">
<li><a href="{{ url_for("page_game", name="ryg") }}">Royal Games</a></li>
<li><a href="{{ url_for("page_game", name="halloween2018") }}">Halloween 2018</a></li>
<li><a href="{{ url_for("page_game", name="tg") }}">Telegram</a></li>
<li><a href="{{ url_for("page_game", name="discord") }}">Discord</a></li>
<li><a href="{{ url_for("page_game", name="steam") }}">Steam</a></li>
<li><a href="{{ url_for("page_game", name="dota") }}">Dota 2</a></li>
<li><a href="{{ url_for("page_game", name="lol") }}">League of Legends</a></li>
<li><a href="{{ url_for("page_game", name="ow") }}">Overwatch</a></li>
<li><a href="{{ url_for("page_game", name="osu") }}">osu!</a></li>
<li><a href="{{ url_for("page_game", name="terraria13") }}">Terraria 13</a></li>
</ul>
</div>
</div>

View file

@ -0,0 +1,26 @@
<div class="box">
<div class="upper-box">
Link riservati ai membri
</div>
<div class="lower-box">
<ul>
{% if g.rygconf['Telegram']['invite_link'] %}
<li><a href="{{ g.rygconf['Telegram']['invite_link'] }}">Link di unione a Telegram</a></li>
{% else %}
<li><i>Link di unione a Telegram disattivato</i></li>
{% endif %}
{% if g.rygconf['Discord']['invite_link'] %}
<li><a href="{{ g.rygconf['Discord']['invite_link'] }}">Link di invito a Discord</a></li>
{% else %}
<li><i>Link di invito a Discord disattivato</i></li>
{% endif %}
<li><a href="https://steamcommunity.com/groups/royalgamescastle">Gruppo Steam Community</a></li>
<li><a href="https://new.reddit.com/r/RoyalGames/">/r/RoyalGames</a></li>
<li><a href="{{ url_for("page_music") }}">Statistiche su Royal Music</a></li>
<li><a href="http://amazon.steffo.eu/royal-music-cache/">File in cache di Royal Music</a></li>
<li><a href="{{ url_for("page_activity") }}">Statistiche sull'attività</a></li>
<li><a href="https://github.com/Steffo99/royalnet">Codice sorgente di Royalnet</a></li>
<li><a href="https://ryg.challonge.com/">Pagina Challonge (Tornei)</a></li>
</ul>
</div>
</div>

View file

@ -0,0 +1,12 @@
<div class="box" id="members-box">
<div class="upper-box">
Membri
</div>
<div class="lower-box">
<ul class="multicolumn">
{% for royal in royals %}
<li class="{% if royal.role == "Admin" %}list-admin{% endif %}"><a href="/profile/{{ royal.username }}">{{ royal.username }}</a></li>
{% endfor %}
</ul>
</div>
</div>

View file

@ -0,0 +1,14 @@
<div class="quest">
<div class="title">
{{ quest.title }}
</div>
<div class="description">
{{ quest.description | markdown }}
</div>
<div class="reward">
{% if quest.reward %}
<div class="number">{{ quest.reward }}</div>
<div class="subtext">fioryg{% if quest.reward != 1 %}i{% endif %}</div>
{% endif %}
</div>
</div>

View file

@ -0,0 +1,14 @@
<div class="box">
<div class="upper-box">
Tabellone delle quest
</div>
<div class="lower-box">
{% if quests %}
{% for quest in quests %}
{% include "components/quest.html" %}
{% endfor %}
{% else %}
<i>Non ci sono quest al momento.</i>
{% endif %}
</div>
</div>

View file

@ -0,0 +1,8 @@
<div class="box">
<div class="upper-box">
Benvenuti!
</div>
<div class="lower-box">
Benvenuti al sito della community Royal Games!
</div>
</div>

View file

@ -0,0 +1,9 @@
<div class="box">
<div class="upper-box">
Cosa siamo?
</div>
<div class="lower-box">
La Royal Games è una community di gamer di tutti i tipi che sono capitati insieme più o meno per caso, e continuano a trovarsi spesso online per parlare e giocare insieme.<br>
Essendo nata <a href="{{ url_for("page_wiki", key="Storia della Royal Games") }}">parecchi anni fa</a>, all'interno di essa si è sviluppata una particolare cultura che la rende unica in tutto l'Internet.
</div>
</div>

View file

@ -9,11 +9,13 @@
Pagine Wiki
</div>
<div class="lower-box">
<ul>
<ul class="multicolumn">
{% for page in wiki_pages %}
<li><a href="/wiki/{{ page.key }}">{{ page.key }}</a></li>
<li class="{% if page.locked %}list-locked{% endif %}"><a href="/wiki/{{ page.key }}">{{ page.key }}</a></li>
{% endfor %}
</ul>
{% if g.user %}
oppure... <input id="input-key" name="key" type="text" placeholder="Nome pagina" class="half"><button onclick="createpage()">Crea</button>
{% endif %}
</div>
</div>

View file

@ -12,11 +12,9 @@
<h1>
Diario <a href="{{ url_for("page_wiki", key="Diario") }}" class="whatsthis">Cos'è?</a>
</h1>
<div class="omnicontainer">
<div class="diario">
{% for entry in entries %}
{% include "components/diarioentry.html" %}
{% endfor %}
</div>
</div>
{% endblock %}

View file

@ -6,22 +6,23 @@
{% endblock %}
{% block pagetitle %}
{{ game_name }}
{{ mini_type._mini_full_name }}
{% endblock %}
{% block body %}
<h1>
Royal Games su {{ game_name }}
Royal Games su {{ mini_type._mini_full_name }}
</h1>
<div class="omnicontainer">
{% if mini_type._mini_name == "ow" %}
<div>
Overwatch updates are currently disabled.
</div>
{% endif %}
<div class="game">
<div class="game-panels">
{% for mini in minis %}
{% with record = mini %}
{% include "minis/" + game_short_name + ".html" %}
{% endwith %}
{% for record in mini_data %}
{% include "minis/" + mini_type._mini_name + ".html" %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View file

@ -9,90 +9,36 @@
{% endblock %}
{% block body %}
<h1 id="main-title">
Royal Games
<h1 id="royal-games">
<img src="{{ url_for('static', filename='LogoRoyalGames.svg') }}" alt=""> Royal Games
</h1>
<div class="main-page">
<div class="two-columns">
<div class="left">
<div class="box">
{% if next_events %}
<div class="upper-box">
Prossimi eventi
</div>
<div class="lower-box">
{% for event in next_events %}
{% include "components/event.html" %}
{% endfor %}
</div>
{% if not g.user %}
{% include "components/welcome.html" %}
{% endif %}
</div>
<div class="box">
<div class="upper-box">
Citazione casuale dal diario
</div>
<div class="lower-box">
{% include "components/diarioentry.html" %}
<a href="{{ url_for("page_diario") }}">Visualizza tutto</a>
</div>
</div>
{% if g.user %}
{% if events %}
{% include "components/eventlist.html" %}
{% endif %}
{% endif %}
{% include "components/memberbox.html" %}
{% if g.user %}
{% include "components/wikibox.html" %}
{% endif %}
</div>
<div class="right">
<div class="box">
<div class="upper-box">
Link utili
</div>
<div class="lower-box">
<ul>
{% if g.rygconf['Telegram']['invite_link'] %}
<li><a href="{{ g.rygconf['Telegram']['invite_link'] }}">Link di unione a Telegram</a></li>
{% else %}
<li><i>Link di unione a Telegram disattivato</i></li>
{% if not g.user %}
{% include "components/whatarewe.html" %}
{% endif %}
{% if g.rygconf['Discord']['invite_link'] %}
<li><a href="{{ g.rygconf['Discord']['invite_link'] }}">Link di invito a Discord</a></li>
{% else %}
<li><i>Link di invito a Discord disattivato</i></li>
{% if g.user %}
{% include "components/questboard.html" %}
{% endif %}
{% include "components/gamestatsbox.html" %}
{% if g.user %}
{% include "components/links.html" %}
{% include "components/diariooftheday.html" %}
{% endif %}
<li><a href="https://steamcommunity.com/groups/royalgamescastle">Gruppo Steam Community</a></li>
<li><a href="https://new.reddit.com/r/RoyalGames/">/r/RoyalGames</a></li>
<li><a href="{{ url_for("page_music") }}">Statistiche su Royal Music</a></li>
<li><a href="http://amazon.steffo.eu/royal-music-cache/">File in cache di Royal Music</a></li>
<li><a href="{{ url_for("page_activity") }}">Statistiche sull'attività</a></li>
<li><a href="https://github.com/Steffo99/royalnet">Codice sorgente di Royalnet</a></li>
</ul>
</div>
</div>
<div class="box">
<div class="upper-box">
Membri
</div>
<div class="lower-box">
<ul>
{% for royal in royals %}
<li><a href="/profile/{{ royal.username }}">{{ royal.username }}</a></li>
{% endfor %}
</ul>
</div>
</div>
<div class="box">
<div class="upper-box">
Resoconti
</div>
<div class="lower-box">
<ul>
<li><a href="{{ url_for("page_game", name="ryg") }}">Royal Games</a></li>
<li><a href="{{ url_for("page_game", name="halloween2018") }}">Halloween 2018</a></li>
<li><a href="{{ url_for("page_game", name="tg") }}">Telegram</a></li>
<li><a href="{{ url_for("page_game", name="discord") }}">Discord</a></li>
<li><a href="{{ url_for("page_game", name="steam") }}">Steam</a></li>
<li><a href="{{ url_for("page_game", name="dota") }}">Dota 2</a></li>
<li><a href="{{ url_for("page_game", name="lol") }}">League of Legends</a></li>
<li><a href="{{ url_for("page_game", name="ow") }}">Overwatch</a></li>
<li><a href="{{ url_for("page_game", name="osu") }}">osu!</a></li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -21,11 +21,14 @@
<span>{{ record.wins }}</span>
</div>
<div class="rank">
{% if record.rank_tier is none %}
<img class="medal" src="https://www.opendota.com/assets/images/dota2/rank_icons/rank_icon_0.png" alt="">
{% if (record.wins + record.losses) < 100 %}
<img class="medal locked" src="{{ url_for("static", filename="rank_icon_locked.png") }}" alt="">
<span class="text">{{ 100 - record.wins - record.losses }} partite rimaste</span>
{% elif record.rank_tier is none %}
<img class="medal unranked" src="https://www.opendota.com/assets/images/dota2/rank_icons/rank_icon_0.png" alt="">
<span class="text">Non classificato</span>
{% elif record.rank_tier < 10 %}
<img class="medal" src="https://www.opendota.com/assets/images/dota2/rank_icons/rank_icon_0.png" alt="">
<img class="medal unranked" src="https://www.opendota.com/assets/images/dota2/rank_icons/rank_icon_0.png" alt="">
<span class="text">{{ record.rank_tier }} piazzamenti completati</span>
{% else %}
<img class="medal" src="{{ record.get_rank_icon_url() }}" alt="">{% if record.get_rank_stars_url() %}<img class="stars" src="{{ record.get_rank_stars_url() }}" alt="">{% endif %}

View file

@ -1,16 +1,64 @@
<div class="game-panel">
<div class="game-grid ryg">
<div class="player">
<span class="player-name"><a href="https://ryg.steffo.eu/profile/{{ record.username }}">{{ record.username }}</a></span>
<div class="game-grid ryg role-{{ record.role }}">
<div class="username">
<a href="{{ url_for("page_profile", name=record.username) }}">{{ record.username }}</a>
</div>
<div class="member-status">
<span class="role">{{ record.role }}</span> dal <span class="member-since">{% if record.member_since %}{{ record.member_since }}{% else %}????-??-??{% endif %}</span>
{% if record.special_title %}
<div class="special-title">
{{ record.special_title }}
</div>
<div class="game-title fiorygi">
{% endif %}
{% if record.username == "Steffo" %}
<div class="join-date-title steffo">
Admin dall'
</div>
<div class="join-date steffo">
inizio!
</div>
{% elif record.member_since %}
<div class="join-date-title member">
{{ record.role }} dal
</div>
<div class="join-date member">
{{ record.member_since.isoformat() }}
</div>
{% else %}
<div class="join-date-title unknown">
{{ record.role }} dal
</div>
<div class="join-date unknown" title="Data di unione sconosciuta">
?
</div>
{% endif %}
{% if record.wiki_edits|length > 0 %}
<div class="wiki-edits-title">
Contributi wiki
</div>
<div class="wiki-edits">
{{ record.wiki_edits|length }}
</div>
{% else %}
<div class="wiki-edits-title unknown">
Contributi wiki
</div>
<div class="wiki-edits unknown">
---
</div>
{% endif %}
{% if record.fiorygi > 0 %}
<div class="fiorygi-title">
Fiorygi
</div>
<div class="game-score fiorygi">
<div class="fiorygi">
{{ record.fiorygi }}
</div>
{% else %}
<div class="fiorygi-title unknown">
Fiorygi
</div>
<div class="fiorygi unknown">
---
</div>
{% endif %}
</div>
</div>

View file

@ -0,0 +1,21 @@
<style>
@font-face {
font-family: "Andy";
src: url("{{ url_for('static', filename='terrariafont.ttf') }}");
font-style: normal;
font-weight: bold;
}
</style>
<div class="game-panel">
<div class="game-grid terraria13">
<div class="character">
{{ record.character_name }}
</div>
<div class="contribution-status">
<span class="contribution {% if record.contribution >= 8 %}orange{% elif record.contribution >= 5 %}green{% elif record.contribution >= 3 %}blue{% endif %}">
{{ record.contribution }}
</span>
fioryg{% if record.contribution != 1 %}i{% endif %} ottenut{% if record.contribution != 1 %}i{% else %}o{% endif %} per la partecipazione a Terraria 13!
</div>
</div>
</div>

View file

@ -23,7 +23,6 @@
<h1>
Profilo di {{ ryg.username }} {% if session.get('user_id', '') == ryg.id %}<a href="{{ url_for('page_editprofile') }}" id="edit-css">Modifica</a>{% endif %}
</h1>
<div class="omnicontainer">
<div class="profile">
{% if css.bio %}
<div class="box bio">
@ -36,50 +35,11 @@
</div>
{% endif %}
<div class="game-panels">
{% with record = ryg %}
{% include "minis/ryg.html" %}
{% for mini in mini_data %}
{% with record = mini["data"] %}
{% include "minis/" + mini["name"] + ".html" %}
{% endwith %}
{% if halloween %}
{% with record = halloween %}
{% include "minis/halloween2018.html" %}
{% endwith %}
{% endif %}
{% if tg %}
{% with record = tg %}
{% include "minis/tg.html" %}
{% endwith %}
{% endif %}
{% if discord %}
{% with record = discord %}
{% include "minis/discord.html" %}
{% endwith %}
{% endif %}
{% if steam %}
{% with record = steam %}
{% include "minis/steam.html" %}
{% endwith %}
{% endif %}
{% if dota %}
{% with record = dota %}
{% include "minis/dota.html" %}
{% endwith %}
{% endif %}
{% if osu %}
{% with record = osu %}
{% include "minis/osu.html" %}
{% endwith %}
{% endif %}
{% if lol %}
{% with record = lol %}
{% include "minis/lol.html" %}
{% endwith %}
{% endif %}
{% if ow %}
{% with record = ow %}
{% include "minis/ow.html" %}
{% endwith %}
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View file

@ -9,6 +9,7 @@
Modifica profilo
</h1>
<form action="{{ url_for('page_editprofile') }}" method="POST">
<input type="email" name="email" placeholder="La tua e-mail (Gravatar)" value="{{ royal.email }}"><br>
Puoi usare il <a href="{{ url_for('page_wiki', key='Meta') }}">Markdown</a> nella tua bio.
<textarea name="bio" placeholder="Scrivi la tua bio qui... (Markdown supportato)">{{ data.bio }}</textarea>
Se vuoi, puoi personalizzare il tuo profilo con un tuo <a href="https://www.w3schools.com/css/css_howto.asp">foglio di stile!</a>

View file

@ -1,14 +0,0 @@
{% extends 'base.html' %}
{% block pagetitle %}
Elenco delle Wiki
{% endblock %}
{% block body %}
<h1>
Royal Wiki
</h1>
<div class="wiki-home">
{% include "components/wikibox.html" %}
</div>
{% endblock %}

View file

@ -43,15 +43,37 @@
Ultima modifica di <span class="last-author"><a href="/profile/{{ wiki_log.editor.username }}">{{ wiki_log.editor.username }}</a></span> alle <span class="last-timestamp">{{ wiki_log.timestamp.strftime('%Y-%m-%d %H:%M:%S %Z') }}</span>{% if wiki_log.reason %}, motivo: <span class="last-reason">{{ wiki_log.reason }}</span>{% endif %}
</div>
{% endif %}
{% if session.get('user_id', '') %}
{% if wiki_page is none %}
<div class="wiki-edit">
<h4>Crea</h4>
<form action="{{ url_for('page_wiki_edit', key=key) }}" method="POST">
<textarea oninput="newEdit()" class="content" name="content" placeholder="Inserisci il Markdown per la pagina qui.">{% if wiki_page %}{{ wiki_page.content }}{% endif %}</textarea><br>
<input class="reason" name="reason" type="text" placeholder="Motivo per la creazione (facoltativo)"><br>
<input class="submit" type="submit" onmouseenter="onEnter()" onmouseleave="onExit()" value="Crea">
</form>
</div>
{% elif wiki_page.locked %}
<div class="wiki-locked">
<span>🔒 Pagina bloccata: non possono essere effettuate ulteriori modifiche.</span>
</div>
{% else %}
{% if g.user %}
<div class="wiki-edit">
<h4>Modifica</h4>
<form action="{{ url_for('page_wiki', key=key) }}" method="POST">
<textarea oninput="newEdit()" class="content" name="content" placeholder="Inserisci il Markdown per la pagina qui.">{% if wiki_page %}{{ wiki_page.content }}{% endif %}</textarea><br>
<input class="reason" name="reason" type="text" placeholder="Motivo per la modifica"><br>
<input class="submit" type="submit" onmouseenter="onEnter()" onmouseleave="onExit()" value="Invia">
<form action="{{ url_for('page_wiki_edit', key=key) }}" method="POST">
<textarea oninput="newEdit()" class="content" name="content" placeholder="Non eliminare pagine altrui. Steffo non la prenderà bene.">{% if wiki_page %}{{ wiki_page.content }}{% endif %}</textarea><br>
<input class="reason" name="reason" type="text" placeholder="Motivo per la modifica (facoltativo)"><br>
<input class="submit" type="submit" onmouseenter="onEnter()" onmouseleave="onExit()" value="Salva">
</form>
</div>
{% endif %}
{% endif %}
{% if wiki_page is not none %}
{% if g.user.role == "Admin" %}
<form action="{{ url_for('page_wiki_lock', key=key) }}" method="POST">
<input class="submit" type="submit" value="Blocca/Sblocca pagina">
</form>
{% endif %}
{% endif %}
</div>
{% endblock %}

View file

@ -2,13 +2,15 @@
# Requires SENTRY_AUTH_TOKEN and SENTRY_ORG set in .sentryclirc
old=$(git rev-list HEAD -n 1)
git stash
git pull
git lfs pull
new=$(git rev-list HEAD -n 1)
if [ ${old} = ${new} ]; then
if [[ ${old} = ${new} ]]; then
version=$(sentry-cli releases propose-version)
sentry-cli releases new -p royalnet ${version}
sentry-cli releases set-commits --auto ${version}
fi
sudo python3.6 -m pip install -r requirements.txt
sudo python3.6 -m pip install -r requirements.txt --upgrade
sudo service apache2 restart
python3.6 -OO bots.py

8
utils/__init__.py Normal file
View file

@ -0,0 +1,8 @@
from .dirty import Dirty, DirtyDelta
from .mmstatus import MatchmakingStatus
from .cast import Spell
from .stagismo import smecds
from .emojify import emojify
from .telegramstuff import reply_msg
__all__ = ["Dirty", "DirtyDelta", "MatchmakingStatus", "Spell", "smecds", "emojify", "reply_msg"]

230
utils/cast.py Normal file
View file

@ -0,0 +1,230 @@
import random
import math
import typing
import strings
import enum
s = strings.safely_format_string
class SpellType(enum.Flag):
DAMAGING = enum.auto()
HEALING = enum.auto()
STATS = enum.auto()
STATUS_EFFECT = enum.auto()
class DamageComponent:
dice_type_distribution = ([4] * 7) +\
([6] * 12) +\
([8] * 32) +\
([10] * 30) +\
([12] * 12) +\
([20] * 6) +\
([100] * 1)
all_damage_types = ["da fuoco", "da freddo", "elettrici", "sonici", "necrotici", "magici",
"da acido", "divini", "nucleari", "psichici", "fisici", "puri", "da taglio",
"da perforazione", "da impatto", "da caduta", "gelato", "onnipotenti", "oscuri",
"di luce", "da velocità", "da cactus", "dannosi", "da radiazione",
"tuamammici", "da maledizione", "pesanti", "leggeri", "immaginari", "da laser",
"da neutrini", "galattici", "cerebrali", "ritardati", "ritardanti", "morali", "materiali",
"energetici", "esplosivi", "energetici", "finanziari", "radianti", "sonori", "spaggiaritici",
"interiori", "endocrini", "invisibili", "inesistenti", "eccellenti", "bosonici",
"gellificanti", "terminali"]
repeat_distribution = ([1] * 8) +\
([2] * 1) +\
([3] * 1)
damage_types_distribution = ([1] * 6) + \
([2] * 3) + \
([3] * 1)
def __init__(self):
# ENSURE THE SEED IS ALREADY SET WHEN CREATING THIS COMPONENT!!!
self.dice_number = random.randrange(1, 21)
self.dice_type = random.sample(self.dice_type_distribution, 1)[0]
self.constant = random.randrange(math.floor(-self.dice_type / 4), math.ceil(self.dice_type / 4) + 1)
self.miss_chance = random.randrange(50, 101)
self.repeat = random.sample(self.repeat_distribution, 1)[0]
self.damage_types_qty = random.sample(self.damage_types_distribution, 1)[0]
self.damage_types = random.sample(self.all_damage_types, self.damage_types_qty)
@property
def avg(self):
return (self.dice_number * (self.dice_type + 1) / 2) + self.constant
def stringify(self) -> str:
string = ""
if self.constant > 0:
constant = "+" + str(self.constant)
elif self.constant == 0:
constant = ""
else:
constant = str(self.constant)
string += s(strings.SPELL.DAMAGE,
words={"number": str(self.dice_number),
"type": str(self.dice_type),
"constant": constant,
"avg": str(int(self.avg))})
for dmg_type in self.damage_types:
string += s(strings.SPELL.TYPE, words={"type": dmg_type})
string += s(strings.SPELL.ACCURACY, words={"accuracy": str(self.miss_chance)})
if self.repeat > 1:
string += s(strings.SPELL.REPEAT, words={"repeat": str(self.repeat)})
return string
class HealingComponent:
dice_type_distribution = ([4] * 12) +\
([6] * 38) +\
([8] * 30) +\
([10] * 12) +\
([12] * 6) +\
([20] * 1) +\
([100] * 1)
def __init__(self):
# ENSURE THE SEED IS ALREADY SET WHEN CREATING THIS COMPONENT!!!
self.dice_number = random.randrange(1, 11)
self.dice_type = random.sample(self.dice_type_distribution, 1)[0]
self.constant = random.randrange(math.floor(-self.dice_type / 4), math.ceil(self.dice_type / 4) + 1)
@property
def avg(self):
return (self.dice_number * (self.dice_type + 1) / 2) + self.constant
def stringify(self) -> str:
string = ""
if self.constant > 0:
constant = "+" + str(self.constant)
elif self.constant == 0:
constant = ""
else:
constant = str(self.constant)
string += s(strings.SPELL.HEALING,
words={"number": str(self.dice_number),
"type": str(self.dice_type),
"constant": constant,
"avg": str(int(self.avg))})
return string
class StatsComponent:
all_stats = ["Attacco", "Difesa", "Velocità", "Elusione", "Tenacia", "Rubavita",
"Vampirismo", "Forza", "Destrezza", "Costituzione", "Intelligenza",
"Saggezza", "Carisma", "Attacco Speciale", "Difesa Speciale",
"Eccellenza", "Immaginazione", "Cromosomi", "Timidezza", "Sonno",
"Elasticità", "Peso", "Sanità", "Appetito", "Fortuna", "Percezione",
"Determinazione"]
change_distribution = (["--"] * 1) +\
(["-"] * 2) +\
(["+"] * 2) +\
(["++"] * 1)
multistat_distribution = ([1] * 16) +\
([2] * 8) +\
([3] * 4) +\
([5] * 2) +\
([8] * 1)
def __init__(self):
# ENSURE THE SEED IS ALREADY SET WHEN CREATING THIS COMPONENT!!!
self.stat_changes = {}
self.stat_number = random.sample(self.multistat_distribution, 1)[0]
available_stats = self.all_stats.copy()
for _ in range(self.stat_number):
stat = random.sample(available_stats, 1)[0]
available_stats.remove(stat)
change = random.sample(self.change_distribution, 1)[0]
self.stat_changes[stat] = change
def stringify(self) -> str:
string = ""
for name in self.stat_changes:
string += s(strings.SPELL.STAT, words={
"name": name,
"change": self.stat_changes[name]
})
return string
class StatusEffectComponent:
all_status_effects = ["Bruciatura", "Sanguinamento", "Paralisi", "Veleno",
"Congelamento", "Iperveleno", "Sonno", "Stordimento",
"Rallentamento", "Radicamento", "Rigenerazione", "Morte",
"Affaticamento", "Glitch", "Accecamento", "Silenzio",
"Esilio", "Invisibilità", "Rapidità", "Splendore"]
def __init__(self):
# ENSURE THE SEED IS ALREADY SET WHEN CREATING THIS COMPONENT!!!
self.chance = random.randrange(1, 101)
self.effect = random.sample(self.all_status_effects, 1)[0]
def stringify(self) -> str:
return s(strings.SPELL.STATUS_EFFECT, words={
"chance": str(self.chance),
"effect": self.effect
})
class Spell:
version = "3.2"
damaging_spell_chance = 0.9
healing_spell_chance = 0.9 # If not a damaging spell
additional_stats_chance = 0.1 # In addition to the damage/healing
additional_status_effect_chance = 0.1 # In addition to the rest
def __init__(self, name: str):
seed = name.capitalize()
random.seed(seed)
# Spell name
self.name = seed
# Find the spell type
self.spell_type = SpellType(0)
if random.random() < self.damaging_spell_chance:
self.spell_type |= SpellType.DAMAGING
elif random.random() < self.healing_spell_chance:
self.spell_type |= SpellType.HEALING
if random.random() < self.additional_stats_chance:
self.spell_type |= SpellType.STATS
if random.random() < self.additional_status_effect_chance:
self.spell_type |= SpellType.STATUS_EFFECT
# Damaging spells
if self.spell_type & SpellType.DAMAGING:
self.damage_component = DamageComponent()
else:
self.damage_component = None
# Healing spells
if self.spell_type & SpellType.HEALING:
self.healing_component = HealingComponent()
else:
self.healing_component = None
# Stats spells
if self.spell_type & SpellType.STATS:
self.stats_component = StatsComponent()
else:
self.stats_component = None
# Status effect spells
if self.spell_type & SpellType.STATUS_EFFECT:
self.status_effect_component = StatusEffectComponent()
else:
self.status_effect_component = None
def stringify(self) -> str:
string = s(strings.SPELL.HEADER, words={"name": self.name, "version": self.version})
if self.spell_type & SpellType.DAMAGING:
string += self.damage_component.stringify()
if self.spell_type & SpellType.HEALING:
string += self.healing_component.stringify()
if self.spell_type & SpellType.STATS:
string += self.stats_component.stringify()
if self.spell_type & SpellType.STATUS_EFFECT:
string += self.status_effect_component.stringify()
if self.spell_type == SpellType(0):
string += s(strings.SPELL.NOTHING)
return string

37
utils/dirty.py Normal file
View file

@ -0,0 +1,37 @@
class Dirty:
def __init__(self, initial_value):
self.initial_value = initial_value
self.value = initial_value
def is_clean(self):
return self.initial_value == self.value
def is_dirty(self):
return not self.is_clean()
def __bool__(self):
return self.is_dirty()
class DirtyDelta(Dirty):
@property
def difference(self):
if self.initial_value is None:
initial_value = 0
else:
initial_value = self.initial_value
if self.value is None:
value = 0
else:
value = self.value
return value - initial_value
@property
def delta(self):
return abs(self.difference)
def difference_string(self):
if self.difference > 0:
return f"+{self.difference}"
return self.difference

59
utils/emojify.py Normal file
View file

@ -0,0 +1,59 @@
import random
emojis = {
"abcd": ["🔡", "🔠"],
"back": ["🔙"],
"cool": ["🆒"],
"free": ["🆓"],
"abc": ["🔤"],
"atm": ["🏧"],
"new": ["🆕"],
"sos": ["🆘"],
"top": ["🔝"],
"zzz": ["💤"],
"end": ["🔚"],
"ab": ["🆎"],
"cl": ["🆑"],
"id": ["🆔"],
"ng": ["🆖"],
"no": ["♑️"],
"ok": ["🆗"],
"on": ["🔛"],
"sy": ["💱"],
"tm": ["™️"],
"wc": ["🚾"],
"up": ["🆙"],
"a": ["🅰️"],
"b": ["🅱️"],
"c": ["☪️", "©", "🥐"],
"d": ["🇩"],
"e": ["📧", "💶"],
"f": ["🎏"],
"g": ["🇬"],
"h": ["🏨", "🏩", "🏋‍♀", "🏋‍♂"],
"i": ["", "♊️", "🕕"],
"j": ["⤴️"],
"k": ["🎋", "🦅", "💃"],
"l": ["🛴", "🕒"],
"m": ["♏️", "Ⓜ️", "〽️"],
"n": ["📈"],
"o": ["⭕️", "🅾️", "📯", "🌝", "🌚", "🌕", "🥯", "🙆‍♀", "🙆‍♂"],
"p": ["🅿️"],
"q": ["🔍", "🍀"],
"r": ["®"],
"s": ["💰", "💵", "💸", "💲"],
"t": ["✝️", "⬆️", "☦️"],
"u": ["", "⚓️", "🍉", "🌙", "🐋"],
"v": ["", "🔽", "☑️", "✔️"],
"w": ["🤷‍♀","🤷‍♂", "🤾‍♀", "🤾‍♂", "🤽‍♀", "🤽‍♂"],
"x": ["🙅‍♀", "🙅‍♂", "", ""],
"y": ["💴"],
"z": ["⚡️"]
}
def emojify(string: str):
new_string = string.lower()
for key in emojis:
selected_emoji = random.sample(emojis[key], 1)[0]
new_string = new_string.replace(key, selected_emoji)
return new_string

9
utils/mmstatus.py Normal file
View file

@ -0,0 +1,9 @@
import enum
class MatchmakingStatus(enum.IntEnum):
WAIT_FOR_ME = 1
READY = 2
MAYBE = 3
SOMEONE_ELSE = 4
IGNORED = -1

46
utils/stagismo.py Normal file
View file

@ -0,0 +1,46 @@
smecds = ["della secca", "del seccatore", "del secchiello", "del secchio", "del secchione", "del secondino",
"del sedano", "del sedativo", "della sedia", "del sedicente", "del sedile", "della sega", "del segale",
"della segatura", "della seggiola", "del seggiolino", "della seggiovia", "della segheria", "del seghetto",
"del segnalibro", "del segnaposto", "del segno", "del segretario", "della segreteria", "del seguace",
"del segugio", "della selce", "della sella", "della selz", "della selva", "della selvaggina", "del semaforo",
"del seme", "del semifreddo", "del seminario", "della seminarista", "della semola", "del semolino",
"del semplicione", "della senape", "del senatore", "del seno", "del sensore", "della sentenza",
"della sentinella", "del sentore", "della seppia", "del sequestratore", "della serenata", "del sergente",
"del sermone", "della serpe", "del serpente", "della serpentina", "della serra", "del serraglio",
"del serramanico", "della serranda", "della serratura", "del servitore", "della servitù", "del servizievole",
"del servo", "del set", "della seta", "della setola", "del sidecar", "del siderurgico", "del sidro",
"della siepe", "del sifone", "della sigaretta", "del sigaro", "del sigillo", "della signora",
"della signorina", "del silenziatore", "della silhouette", "del silicio", "del silicone", "del siluro",
"della sinagoga", "della sindacalista", "del sindacato", "del sindaco", "della sindrome", "della sinfonia",
"del sipario", "del sire", "della sirena", "della siringa", "del sismografo", "del sobborgo",
"del sobillatore", "del sobrio", "del soccorritore", "del socio", "del sociologo", "della soda", "del sofà",
"della soffitta", "del software", "dello sogghignare", "del soggiorno", "della sogliola", "del sognatore",
"della soia", "del solaio", "del solco", "del soldato", "del soldo", "del sole", "della soletta",
"della solista", "del solitario", "del sollazzare", "del sollazzo", "del sollecito", "del solleone",
"del solletico", "del sollevare", "del sollievo", "del solstizio", "del solubile", "del solvente",
"della soluzione", "del somaro", "del sombrero", "del sommergibile", "del sommo", "della sommossa",
"del sommozzatore", "del sonar", "della sonda", "del sondaggio", "del sondare", "del sonnacchioso",
"del sonnambulo", "del sonnellino", "del sonnifero", "del sonno", "della sonnolenza", "del sontuoso",
"del soppalco", "del soprabito", "del sopracciglio", "del sopraffare", "del sopraffino", "del sopraluogo",
"del sopramobile", "del soprannome", "del soprano", "del soprappensiero", "del soprassalto",
"del soprassedere", "del sopravvento", "del sopravvivere", "del soqquadro", "del sorbetto", "del sordido",
"della sordina", "del sordo", "della sorella", "della sorgente", "del sornione", "del sorpasso",
"della sorpresa", "del sorreggere", "del sorridere", "della sorsata", "del sorteggio", "del sortilegio",
"del sorvegliante", "del sorvolare", "del sosia", "del sospettoso", "del sospirare", "della sosta",
"della sostanza", "del sostegno", "del sostenitore", "del sostituto", "del sottaceto", "della sottana",
"del sotterfugio", "del sotterraneo", "del sottile", "del sottilizzare", "del sottintendere",
"del sottobanco", "del sottobosco", "del sottomarino", "del sottopassaggio", "del sottoposto",
"del sottoscala", "della sottoscrizione", "del sottostare", "del sottosuolo", "del sottotetto",
"del sottotitolo", "del sottovalutare", "del sottovaso", "della sottoveste", "del sottovuoto",
"del sottufficiale", "della soubrette", "del souvenir", "del soverchiare", "del sovrano", "del sovrapprezzo",
"della sovvenzione", "del sovversivo", "del sozzo", "dello suadente", "del sub", "del subalterno",
"del subbuglio", "del subdolo", "del sublime", "del suburbano", "del successore", "del succo",
"della succube", "del succulento", "della succursale", "del sudario", "della sudditanza", "del suddito",
"del sudicio", "del suffisso", "del suffragio", "del suffumigio", "del suggeritore", "del sughero",
"del sugo", "del suino", "della suite", "del sulfureo", "del sultano", "di Steffo", "di Spaggia",
"di Sabrina", "del sas", "del ses", "del sis", "del sos", "del sus", "della supremazia", "del Santissimo",
"della scatola", "del supercalifragilistichespiralidoso", "del sale", "del salame", "di (Town of) Salem",
"di Stronghold", "di SOMA", "dei Saints", "di S.T.A.L.K.E.R.", "di Sanctum", "dei Sims", "di Sid",
"delle Skullgirls", "di Sonic", "di Spiral (Knights)", "di Spore", "di Starbound", "di SimCity", "di Sensei",
"di Ssssssssssssss... Boom! E' esploso il dizionario", "della scala", "di Sakura", "di Suzie", "di Shinji",
"del senpai", "del support", "di Superman"]

9
utils/telegramstuff.py Normal file
View file

@ -0,0 +1,9 @@
import telegram
import strings
def reply_msg(bot: telegram.Bot, chat_id: int, string: str, ignore_escaping=False, disable_web_page_preview=True, **kwargs) -> telegram.Message:
string = strings.safely_format_string(string, ignore_escaping=ignore_escaping, words=kwargs)
return bot.send_message(chat_id, string,
parse_mode="HTML",
disable_web_page_preview=disable_web_page_preview)

View file

@ -1,4 +1,3 @@
import secrets
from flask import Flask, render_template, request, abort, redirect, url_for, Markup, escape, jsonify
from flask import session as fl_session
from flask import g as fl_g
@ -9,10 +8,13 @@ import markdown2
import datetime
# noinspection PyPackageRequirements
import telegram
import query_discord_music
import random
import sql_queries
import re
import functools
import strings
from sqlalchemy.orm.collections import InstrumentedList
from raven.contrib.flask import Sentry
from utils import reply_msg
app = Flask(__name__)
@ -29,9 +31,42 @@ telegram_bot = telegram.Bot(config["Telegram"]["bot_token"])
sentry = Sentry(app, dsn=config["Sentry"]["token"])
@app.template_filter()
def markdown(text):
"""Convert a string to markdown."""
converted_md = markdown2.markdown(text.replace("<", "&lt;"),
extras=["spoiler", "tables", "smarty-pants", "fenced-code-blocks"])
converted_md = re.sub(r"{https?://(?:www\.)?(?:youtube\.com/watch\?.*?&?v=|youtu.be/)([0-9A-Za-z-]+).*?}",
r'<div class="youtube-embed">'
r' <iframe src="https://www.youtube-nocookie.com/embed/\1?rel=0&amp;showinfo=0"'
r' frameborder="0"'
r' allow="autoplay; encrypted-media"'
r' allowfullscreen'
r' width="640px"'
r' height="320px">'
r' </iframe>'
r'</div>', converted_md)
converted_md = re.sub(r"{https?://clyp.it/([a-z0-9]+)}",
r'<div class="clyp-embed">'
r' <iframe width="100%" height="160" src="https://clyp.it/\1/widget" frameborder="0">'
r' </iframe>'
r'</div>', converted_md)
return Markup(converted_md)
def require_login(f):
@functools.wraps(f)
def func(*args, **kwargs):
if not fl_g.user:
abort(403)
return
return f(*args, **kwargs)
return func
@app.errorhandler(400)
def error_400(_=None):
return render_template("400.html", g=fl_g)
return render_template("400.html"), 400
@app.route("/400")
@ -41,7 +76,7 @@ def page_400():
@app.errorhandler(403)
def error_403(_=None):
return render_template("403.html", g=fl_g)
return render_template("403.html"), 403
@app.route("/403")
@ -51,7 +86,7 @@ def page_403():
@app.errorhandler(500)
def error_500(_=None):
return render_template("500.html", g=fl_g)
return render_template("500.html"), 500
@app.route("/500")
@ -61,64 +96,65 @@ def page_500():
@app.route("/")
def page_main():
if not fl_session.get("user_id"):
return redirect(url_for("page_login"))
db_session = db.Session()
royals = db_session.query(db.Royal).order_by(db.Royal.username).all()
wiki_pages = db_session.query(db.WikiEntry).order_by(db.WikiEntry.key).all()
random_diario = db_session.query(db.Diario).order_by(db.func.random()).first()
next_events = db_session.query(db.Event).filter(db.Event.time > datetime.datetime.now()).order_by(
royals = fl_g.session.query(db.Royal).order_by(db.Royal.fiorygi.desc()).all()
wiki_pages = fl_g.session.query(db.WikiEntry).order_by(db.WikiEntry.key).all()
random_diario = fl_g.session.query(db.Diario).order_by(db.func.random()).first()
next_events = fl_g.session.query(db.Event).filter(db.Event.time > datetime.datetime.now()).order_by(
db.Event.time).all()
halloween = db.Halloween.puzzle_status()[1]
db_session.close()
quests = fl_g.session.query(db.Quest).all()
return render_template("main.html", royals=royals, wiki_pages=wiki_pages, entry=random_diario,
next_events=next_events, g=fl_g, escape=escape, halloween=enumerate(halloween))
events=next_events, escape=escape, quests=quests)
@app.route("/profile/<name>")
def page_profile(name: str):
db_session = db.Session()
user = db_session.query(db.Royal).filter_by(username=name).one_or_none()
user = fl_g.session.query(db.Royal).filter_by(username=name).one_or_none()
if user is None:
db_session.close()
abort(404)
return
css = db_session.query(db.ProfileData).filter_by(royal=user).one_or_none()
steam = db_session.query(db.Steam).filter_by(royal=user).one_or_none()
osu = db_session.query(db.Osu).filter_by(royal=user).one_or_none()
dota = db_session.query(db.Dota).join(db.Steam).filter_by(royal=user).one_or_none()
lol = db_session.query(db.LeagueOfLegends).filter_by(royal=user).one_or_none()
ow = db_session.query(db.Overwatch).filter_by(royal=user).one_or_none()
tg = db_session.query(db.Telegram).filter_by(royal=user).one_or_none()
discord = db_session.execute(query_discord_music.one_query, {"royal": user.id}).fetchone()
gamelog = db_session.query(db.GameLog).filter_by(royal=user).one_or_none()
halloween = db_session.query(db.Halloween).filter_by(royal=user).one_or_none()
db_session.close()
css = fl_g.session.query(db.ProfileData).filter_by(royal=user).one_or_none()
mini_data = []
for game in db.mini_list:
# TODO: investigate on why instrumentedlists are returned, or minis are not found
try:
data = game.mini_get_single_from_royal(fl_g.session, user)
except Exception:
data = None
if data is None:
continue
elif isinstance(data, InstrumentedList):
if len(data) == 0:
continue
mini_data.append({
"name": game._mini_name,
"data": data[0]
})
continue
mini_data.append({
"name": game._mini_name,
"data": data
})
if css is not None:
converted_bio = Markup(markdown2.markdown(css.bio.replace("<", "&lt;"),
extras=["spoiler", "tables", "smarty-pants", "fenced-code-blocks"]))
else:
converted_bio = ""
return render_template("profile.html", ryg=user, css=css, osu=osu, dota=dota, lol=lol, steam=steam, ow=ow,
tg=tg, discord=discord, g=fl_g, bio=converted_bio, gamelog=gamelog,
halloween=halloween)
return render_template("profile.html", ryg=user, css=css, bio=converted_bio, mini_data=mini_data)
@app.route("/login")
def page_login():
return render_template("login.html", g=fl_g)
return render_template("login.html")
@app.route("/loggedin", methods=["POST"])
def page_loggedin():
username = request.form.get("username", "")
password = request.form.get("password", "")
db_session = db.Session()
user = db_session.query(db.Royal).filter_by(username=username).one_or_none()
db_session.close()
user = fl_g.session.query(db.Royal).filter_by(username=username).one_or_none()
fl_session.permanent = True
if user is None:
abort(403)
abort(400)
return
if user.password is None:
fl_session["user_id"] = user.id
@ -128,9 +164,7 @@ def page_loggedin():
fl_session["user_id"] = user.id
fl_session["username"] = username
return redirect(url_for("page_main"))
else:
abort(403)
return
return redirect(url_for("page_login"))
@app.route("/logout")
@ -142,39 +176,27 @@ def page_logout():
@app.route("/password", methods=["GET", "POST"])
@require_login
def page_password():
if not fl_session.get("user_id"):
return redirect(url_for("page_login"))
user_id = fl_session.get("user_id")
if request.method == "GET":
if user_id is None:
return redirect(url_for("page_login"))
return render_template("password.html", g=fl_g)
return render_template("password.html")
elif request.method == "POST":
new_password = request.form.get("new", "")
db_session = db.Session()
user = db_session.query(db.Royal).filter_by(id=user_id).one()
user = fl_g.session.query(db.Royal).filter_by(id=fl_g.user.id).one()
if user.password is None:
user.password = bcrypt.hashpw(bytes(new_password, encoding="utf8"), bcrypt.gensalt())
user.fiorygi += 1
db_session.commit()
db_session.close()
user.password = bcrypt.hashpw(bytes(new_password, encoding="utf8"), bcrypt.gensalt())
fl_g.session.commit()
return redirect(url_for("page_main"))
else:
db_session.close()
return redirect(url_for("page_login"))
@app.route("/editprofile", methods=["GET", "POST"])
@require_login
def page_editprofile():
user_id = fl_session.get("user_id")
if not user_id:
return redirect(url_for("page_login"))
db_session = db.Session()
profile_data = db_session.query(db.ProfileData).filter_by(royal_id=user_id).join(db.Royal).one_or_none()
royal = fl_g.session.query(db.Royal).filter_by(id=fl_g.user.id).one()
profile_data = fl_g.session.query(db.ProfileData).filter_by(royal=royal).one_or_none()
if request.method == "GET":
db_session.close()
return render_template("profileedit.html", data=profile_data, g=fl_g)
return render_template("profileedit.html", royal=royal, data=profile_data)
elif request.method == "POST":
css = request.form.get("css", "")
bio = request.form.get("bio", "")
@ -182,91 +204,35 @@ def page_editprofile():
abort(400)
return
if profile_data is None:
profile_data = db.ProfileData(royal_id=user_id, css=css, bio=bio)
db_session.add(profile_data)
db_session.flush()
profile_data.royal.fiorygi += 1
try:
telegram_bot.send_message(config["Telegram"]["main_group"],
f'⭐️ {profile_data.royal.username} ha'
f' <a href="http://ryg.steffo.eu/editprofile">configurato la sua bio</a>'
f' su Royalnet e ha ottenuto un fioryg!',
parse_mode="HTML", disable_web_page_preview=True, disable_notification=True)
except Exception:
pass
profile_data = db.ProfileData(royal=royal, css=css, bio=bio)
fl_g.session.add(profile_data)
fl_g.session.flush()
else:
profile_data.css = css
profile_data.bio = bio
db_session.commit()
royal = db_session.query(db.Royal).filter_by(id=user_id).one()
db_session.close()
fl_g.session.commit()
return redirect(url_for("page_profile", name=royal.username))
@app.route("/game/<name>")
def page_game(name: str):
db_session = db.Session()
if name == "rl":
game_name = "Rocket League"
query = db_session.query(db.RocketLeague).join(db.Steam).order_by(db.RocketLeague.solo_std_rank).all()
elif name == "dota":
game_name = "Dota 2"
query = db_session.query(db.Dota).join(db.Steam).order_by(db.Dota.rank_tier.desc().nullslast()).all()
elif name == "lol":
game_name = "League of Legends"
query = db_session.query(db.LeagueOfLegends).order_by(db.LeagueOfLegends.solo_division.desc().nullslast(),
db.LeagueOfLegends.solo_rank,
db.LeagueOfLegends.flex_division.desc().nullslast(),
db.LeagueOfLegends.flex_rank,
db.LeagueOfLegends.twtr_division.desc().nullslast(),
db.LeagueOfLegends.twtr_rank,
db.LeagueOfLegends.level).all()
elif name == "osu":
game_name = "osu!"
query = db_session.query(db.Osu).order_by(db.Osu.mania_pp.desc().nullslast()).all()
elif name == "ow":
game_name = "Overwatch"
query = db_session.query(db.Overwatch).order_by(db.Overwatch.rank.desc().nullslast()).all()
elif name == "steam":
game_name = "Steam"
query = db_session.query(db.Steam).order_by(db.Steam.persona_name).all()
elif name == "ryg":
game_name = "Royalnet"
query = db_session.query(db.Royal).order_by(db.Royal.username).all()
elif name == "tg":
game_name = "Telegram"
query = db_session.query(db.Telegram).order_by(db.Telegram.telegram_id).all()
elif name == "discord":
game_name = "Discord"
query = [dict(row) for row in db_session.execute(query_discord_music.all_query)]
elif name == "halloween2018":
game_name = "Rituale di Halloween"
query = db_session.query(db.Halloween).all()
for game in db.mini_list:
if game._mini_name == name:
query = game.mini_get_all(fl_g.session)
break
else:
abort(404)
return
db_session.close()
return render_template("game.html", minis=query, game_name=game_name, game_short_name=name, g=fl_g)
return render_template("game.html", mini_type=game, mini_data=query)
@app.route("/wiki")
def page_wikihome():
db_session = db.Session()
wiki_pages = db_session.query(db.WikiEntry).order_by(db.WikiEntry.key).all()
db_session.close()
return render_template("wikilist.html", wiki_pages=wiki_pages, g=fl_g)
@app.route("/wiki/<key>", methods=["GET", "POST"])
@app.route("/wiki/<key>")
def page_wiki(key: str):
db_session = db.Session()
wiki_page = db_session.query(db.WikiEntry).filter_by(key=key).one_or_none()
if request.method == "GET":
wiki_latest_edit = db_session.query(db.WikiLog).filter_by(edited_key=key) \
wiki_page = fl_g.session.query(db.WikiEntry).filter_by(key=key).one_or_none()
wiki_latest_edit = fl_g.session.query(db.WikiLog).filter_by(edited_key=key) \
.order_by(db.WikiLog.timestamp.desc()).first()
db_session.close()
if wiki_page is None:
return render_template("wikipage.html", key=key, g=fl_g)
return render_template("wikipage.html", key=key, wiki_page=None)
# Embed YouTube videos
converted_md = markdown2.markdown(wiki_page.content.replace("<", "&lt;"),
extras=["spoiler", "tables", "smarty-pants", "fenced-code-blocks"])
@ -286,129 +252,133 @@ def page_wiki(key: str):
r' </iframe>'
r'</div>', converted_md)
return render_template("wikipage.html", key=key, wiki_page=wiki_page, converted_md=Markup(converted_md),
wiki_log=wiki_latest_edit, g=fl_g)
elif request.method == "POST":
user_id = fl_session.get('user_id')
user = db_session.query(db.Royal).filter_by(id=user_id).one()
if user_id is None:
db_session.close()
return redirect(url_for("page_login"))
wiki_log=wiki_latest_edit)
@app.route("/wiki/<key>/edit", methods=["POST"])
@require_login
def page_wiki_edit(key: str):
wiki_page = fl_g.session.query(db.WikiEntry).filter_by(key=key).one_or_none()
if wiki_page is not None and wiki_page.locked:
abort(403)
return
new_content = request.form.get("content")
# Create new page
if wiki_page is None:
difference = len(new_content)
wiki_page = db.WikiEntry(key=key, content=new_content)
db_session.add(wiki_page)
db_session.flush()
fl_g.session.add(wiki_page)
fl_g.session.flush()
# Edit existing page
else:
difference = len(new_content) - len(wiki_page.content)
wiki_page.content = new_content
# Award fiorygi
if difference > 50:
fioryg_chance = -(5000/difference) + 100
fioryg_roll = random.randrange(0, 100)
if fioryg_roll > fioryg_chance:
user.fiorygi += 1
if difference > 500:
fiorygi = difference // 500
fiorygi_word = "fioryg" + ("i" if fiorygi != 1 else "")
fl_g.user.fiorygi += fiorygi
else:
fioryg_chance = -1
fioryg_roll = -2
fiorygi = 0
fiorygi_word = ""
edit_reason = request.form.get("reason")
new_log = db.WikiLog(editor=user, edited_key=key, timestamp=datetime.datetime.now(), reason=edit_reason)
db_session.add(new_log)
db_session.commit()
message = f' La pagina wiki <a href="https://ryg.steffo.eu/wiki/{key}">{key}</a> è stata' \
f' modificata da' \
f' <a href="https://ryg.steffo.eu/profile/{user.username}">{user.username}</a>' \
f' {"(" + edit_reason + ")" if edit_reason else ""}' \
f' [{"+" if difference > 0 else ""}{difference}]\n'
if fioryg_roll > fioryg_chance:
message += f"⭐️ {user.username} è stato premiato con 1 fioryg per la modifica!"
new_log = db.WikiLog(editor=fl_g.user, edited_key=key, timestamp=datetime.datetime.now(), reason=edit_reason)
fl_g.session.add(new_log)
fl_g.session.commit()
try:
telegram_bot.send_message(config["Telegram"]["main_group"], message,
parse_mode="HTML", disable_web_page_preview=True, disable_notification=True)
reply_msg(telegram_bot, config["Telegram"]["main_group"], strings.WIKI.PAGE_UPDATED,
key=key,
user=fl_g.user.telegram.mention(),
reason=edit_reason,
change=f"+{str(difference)}" if difference > 0 else str(difference))
if fiorygi > 0:
reply_msg(telegram_bot, config["Telegram"]["main_group"], strings.TELEGRAM.FIORYGI_AWARDED,
mention=fl_g.user.telegram.mention(),
number=fiorygi,
fiorygi=fiorygi_word,
reason="aver contribuito alla wiki Royal Games")
except Exception:
pass
return redirect(url_for("page_wiki", key=key))
@app.route("/wiki/<key>/lock", methods=["POST"])
@require_login
def page_wiki_lock(key: str):
wiki_page = fl_g.session.query(db.WikiEntry).filter_by(key=key).one_or_none()
if wiki_page is None:
abort(404)
return
if fl_g.user.role != "Admin":
abort(403)
return
wiki_page.locked = not wiki_page.locked
try:
if wiki_page.locked:
telegram_bot.send_message(config["Telegram"]["main_group"],
strings.safely_format_string(strings.WIKI.PAGE_LOCKED,
words={
"key": key,
"user": fl_g.user.username
}),
parse_mode="HTML",
disable_web_page_preview=True,
disable_notification=True)
else:
telegram_bot.send_message(config["Telegram"]["main_group"],
strings.safely_format_string(strings.WIKI.PAGE_UNLOCKED,
words={
"key": key,
"user": fl_g.user.username
}),
parse_mode="HTML",
disable_web_page_preview=True,
disable_notification=True)
except Exception:
pass
fl_g.session.commit()
return redirect(url_for("page_wiki", key=key))
@app.route("/diario")
@require_login
def page_diario():
user_id = fl_session.get("user_id")
if not user_id:
return redirect(url_for("page_login"))
db_session = db.Session()
diario_entries = db_session.query(db.Diario).order_by(db.Diario.timestamp.desc()).all()
db_session.close()
return render_template("diario.html", g=fl_g, entries=diario_entries)
diario_entries = fl_g.session.query(db.Diario).order_by(db.Diario.timestamp.desc()).all()
return render_template("diario.html", entries=diario_entries)
@app.route("/music")
def page_music():
db_session = db.Session()
songs = db_session.execute(query_discord_music.top_songs)
db_session.close()
songs = fl_g.session.execute(sql_queries.top_songs)
return render_template("topsongs.html", songs=songs)
@app.route("/music/<discord_id>")
def page_music_individual(discord_id: str):
db_session = db.Session()
discord = db_session.query(db.Discord).filter_by(discord_id=discord_id).one_or_none()
discord = fl_g.session.query(db.Discord).filter_by(discord_id=discord_id).one_or_none()
if discord is None:
db_session.close()
abort(404)
return
songs = db_session.execute(query_discord_music.single_top_songs, {"discordid": discord.discord_id})
db_session.close()
songs = fl_g.session.execute(sql_queries.single_top_songs, {"discordid": discord.discord_id})
return render_template("topsongs.html", songs=songs, discord=discord)
@app.route("/activity")
def page_activity():
db_session = db.Session()
reports = list(db_session.query(db.ActivityReport).order_by(db.ActivityReport.timestamp.desc()).limit(192).all())
db_session.close()
return render_template("activity.html", activityreports=list(reversed(reports)))
@app.route("/api/token")
def api_token():
username = request.form.get("username", "")
password = request.form.get("password", "")
db_session = db.Session()
user = db_session.query(db.Royal).filter_by(username=username).one_or_none()
if user is None:
db_session.close()
abort(403)
return
if user.password is None:
db_session.close()
abort(403)
if bcrypt.checkpw(bytes(password, encoding="utf8"), user.password):
new_token = db.LoginToken(royal=user, token=secrets.token_urlsafe())
db_session.add(new_token)
db_session.commit()
db_session.close()
return jsonify({
"id": user.id,
"username": user.username,
"token": new_token.token
})
else:
abort(403)
return
@app.route("/ses/identify")
def ses_identify():
response = jsonify({
"username": fl_session.get("username"),
"id": fl_session.get("user_id")
})
response.headers["Access-Control-Allow-Origin"] = "https://steffo.eu"
response.headers["Access-Control-Allow-Credentials"] = "true"
return response
reports = list(fl_g.session.query(db.ActivityReport).order_by(db.ActivityReport.timestamp.desc()).limit(192).all())
hourly_avg = list(fl_g.session.execute(sql_queries.activity_by_hour, {"current_month": datetime.datetime.now().month}))
previous_month = datetime.datetime.now().month - 1
if previous_month == 0:
previous_month = 12
hourly_comp = list(fl_g.session.execute(sql_queries.activity_by_hour, {"current_month": previous_month}))
even_before_month = previous_month - 1
if even_before_month == 0:
even_before_month = 12
hourly_before = list(fl_g.session.execute(sql_queries.activity_by_hour, {"current_month": even_before_month}))
return render_template("activity.html", activityreports=list(reversed(reports)),
hourly_avg=hourly_avg,
hourly_comp=hourly_comp,
hourly_before=hourly_before)
@app.route("/hooks/github", methods=["POST"])
@ -422,7 +392,13 @@ def hooks_github():
abort(400)
return
# TODO: add secret check
message = f"🐙 Nuovi aggiornamenti a Royalnet:\n"
if j["ref"] == "refs/heads/master":
message = f"🐙 Nuovi aggiornamenti a Royalnet <code>master</code>:\n"
elif j["ref"] == "refs/heads/unity":
message = f"🐙 Progresso di Royalnet <code>unity</code>:\n"
else:
return "Ignored."
if message:
for commit in j.get("commits", []):
if commit["distinct"]:
message += f'<a href="{commit["url"]}">{commit["message"]}</a>' \
@ -436,6 +412,17 @@ def hooks_github():
def pre_request():
fl_g.css = "nryg.less"
fl_g.rygconf = config
fl_g.session = db.Session()
try:
fl_g.user = fl_g.session.query(db.Royal).filter_by(id=fl_session["user_id"]).one_or_none()
except KeyError:
fl_g.user = None
@app.after_request
def after_request(response):
fl_g.session.close()
return response
if __name__ == "__main__":