Merge pull request #31 from stijndcl/disable_translate

Small reworks
pull/35/head
Stijn De Clercq 2021-01-26 22:28:16 +01:00 committed by GitHub
commit 2bfd507bce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 395 additions and 118 deletions

View File

@ -1,11 +1,8 @@
from bs4 import BeautifulSoup
import datetime
from decorators import help from decorators import help
from discord.ext import commands from discord.ext import commands
from enums.help_categories import Category from enums.help_categories import Category
from functions import checks, config from functions import checks, config
import requests from functions.football import getMatches, getTable
import tabulate
class Football(commands.Cog): class Football(commands.Cog):
@ -14,7 +11,7 @@ class Football(commands.Cog):
# Don't allow any commands to work when locked # Don't allow any commands to work when locked
def cog_check(self, ctx): def cog_check(self, ctx):
return checks.isMe(ctx) and not self.client.locked return not self.client.locked
@commands.group(name="Jpl", case_insensitive=True, invoke_without_command=True) @commands.group(name="Jpl", case_insensitive=True, invoke_without_command=True)
@commands.check(checks.allowedChannels) @commands.check(checks.allowedChannels)
@ -25,64 +22,19 @@ class Football(commands.Cog):
@jpl.command(name="Matches", aliases=["M"], usage="[Week]*") @jpl.command(name="Matches", aliases=["M"], usage="[Week]*")
async def matches(self, ctx, *args): async def matches(self, ctx, *args):
args = list(args) args = list(args)
# Default is current day
if not args: if not args:
args = [str(config.get("jpl_day"))] args = [str(config.get("jpl_day"))]
if all(letter.isdigit() for letter in args[0]): if all(letter.isdigit() for letter in args[0]):
current_day = requests.get("https://api.sporza.be/web/soccer/matchdays/161733/{}".format(args[0])).json() await ctx.send(getMatches(int(args[0])))
current_day = current_day["groupedMatches"][0]["matches"]
# Create dictionaries for every match
matches_formatted = {}
for i, match in enumerate(current_day):
matchDic = {"home": match["homeTeam"]["name"], "away": match["awayTeam"]["name"]}
# Add date
matchDate = datetime.datetime.strptime(match["startDateTime"].split("+")[0], "%Y-%m-%dT%H:%M:%S.%f")
matchDic["date"] = matchDate.strftime("%d/%m")
matchDic["day"] = self.get_weekday(matchDate.weekday())
# TODO check back when there's active games (to find the key in the dict) & add the current time if not over
# Add scores
if match["status"] == "END": # Status != [not_yet_started] whatever it is
matchDic["score"] = "{} - {}".format(match["homeScore"], match["awayScore"])
else:
# If there's no score, show when the match starts
matchDic["score"] = "{}:{}".format(
("" if len(str(matchDate.hour)) == 2 else "0") + str(matchDate.hour), # Leading Zero
("" if len(str(matchDate.minute)) == 2 else "0") + str(matchDate.minute)) # Leading Zero
matches_formatted[i] = matchDic
# Put every formatted version of the matches in a list
matchList = list([self.format_match(matches_formatted[match]) for match in matches_formatted])
await ctx.send("```Jupiler Pro League - Speeldag {}\n\n{}```".format(args[0], tabulate.tabulate(matchList, headers=["Dag", "Datum", "Thuis", "Stand", "Uit", "Tijd"])))
else: else:
return await ctx.send("Dit is geen geldige speeldag.") return await ctx.send("Dit is geen geldige speeldag.")
# TODO check back when there's active games & add the timestamp instead of EINDE
def format_match(self, match):
return [match["day"], match["date"], match["home"], match["score"], match["away"], "Einde"]
def get_weekday(self, day: int):
days = ["Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"]
return days[day]
@jpl.command(name="Table", aliases=["Ranking", "Rankings", "Ranks", "T"]) @jpl.command(name="Table", aliases=["Ranking", "Rankings", "Ranks", "T"])
async def table(self, ctx, *args): async def table(self, ctx, *args):
page_html = requests.get("https://sporza.be/nl/categorie/voetbal/jupiler-pro-league/").text await ctx.send(getTable())
bs_parsed = BeautifulSoup(page_html, "html.parser")
rows = bs_parsed.find(summary="algemeen klassement").find_all("tr")[1:]
rowsFormatted = []
for row in rows:
rowsFormatted.append(self.createRowList(row))
await ctx.send("```Jupiler Pro League Klassement\n\n{}```".format(tabulate.tabulate(rowsFormatted, headers=["#", "Ploeg", "Punten", "M", "M+", "M-", "M="])))
# Formats the row into an list that can be passed to Tabulate
def createRowList(self, row):
scoresArray = list([td.renderContents().decode("utf-8") for td in row.find_all("td")])[:6]
# Insert the team name into the list
scoresArray.insert(1, row.find_all("a")[0].renderContents().decode("utf-8").split("<!--")[0])
return scoresArray
def setup(client): def setup(client):

View File

@ -3,10 +3,10 @@ from decorators import help
import discord import discord
from discord.ext import commands from discord.ext import commands
from enums.help_categories import Category from enums.help_categories import Category
from functions import checks, mock, stringFormatters from functions import checks, stringFormatters
from functions.database import memes, trump, dadjoke from functions.database import memes, trump, dadjoke
from functions.memes import generate
import json import json
import os
import random import random
import requests import requests
@ -91,63 +91,23 @@ class Fun(commands.Cog):
return await ctx.send("Controleer je argumenten.") return await ctx.send("Controleer je argumenten.")
# Get the meme info that corresponds to this name # Get the meme info that corresponds to this name
result = memes.getMeme(name) result: memes.Meme = memes.getMeme(name)
# No meme found # No meme found
if not result[0]: if result is None:
return await ctx.send(result[1]) return await ctx.send("Deze meme staat niet in de database.")
# Convert to list to support item assignment # Convert to list to support item assignment
fields = list(fields) fields = list(fields)
# If there's only one field, the user isn't required to use quotes generated = generate(result, fields)
if result[1][2] == 1:
fields = [" ".join(fields)]
# Apply mock to mocking spongebob memes # If the request was successful, remove the message calling it
if result[1][1] == "mocking spongebob": if generated["success"]:
fields = list(map(mock.mock, fields)) await self.utilsCog.removeMessage(ctx.message)
# X, X everywhere only takes X as an argument # Send the meme's url or the error message
if result[1][1] == "x, x everywhere": await ctx.send(generated["message"])
fields[0] = " ".join(fields)
fields.append(fields[0] + " everywhere")
# List of fields to send to the API
boxes = [{"text": ""}, {"text": ""}, {"text": ""}, {"text": ""}]
# Add all fields required & ignore the excess ones
for i in range(len(fields)):
if i > 3:
break
boxes[i]["text"] = fields[i]
# Check server status
req = requests.get('https://api.imgflip.com/get_memes').json()
if req["success"]:
caption = {
"template_id": result[1][0],
"username": os.getenv("IMGFLIPNAME"),
"password": os.getenv("IMGFLIPPASSWORD"),
"boxes[0][text]": boxes[0]["text"],
"boxes[1][text]": boxes[1]["text"],
"boxes[2][text]": boxes[2]["text"],
"boxes[3][text]": boxes[3]["text"]
}
# Send the POST to the API
memeReply = requests.post('https://api.imgflip.com/caption_image', caption).json()
if memeReply['success']:
await ctx.send(str(memeReply['data']['url']))
await self.utilsCog.removeMessage(ctx.message)
else:
await ctx.send(
"Error! Controleer of je de juiste syntax hebt gebruikt. Gebruik het commando "
"\"memes\" voor een lijst aan geaccepteerde meme-namen.")
else:
await ctx.send("Er is een fout opgetreden.")
@commands.command(name="Memes") @commands.command(name="Memes")
@commands.check(checks.allowedChannels) @commands.check(checks.allowedChannels)

View File

@ -130,7 +130,7 @@ class ModCommands(commands.Cog):
# Adds a meme into the database # Adds a meme into the database
@add.command(name="Meme", aliases=["Mem"], usage="[Id] [Name] [Aantal Velden]") @add.command(name="Meme", aliases=["Mem"], usage="[Id] [Name] [Aantal Velden]")
async def meme(self, ctx, memeid, meme, fields): async def meme(self, ctx, memeid, meme, fields):
await ctx.send(memes.insert(memeid, meme, fields)[1]) await ctx.send(memes.insert(memeid, meme, fields))
# Adds a person's GitHub into the database # Adds a person's GitHub into the database
@add.command(name="GitHub", aliases=["Gh", "Git"], usage="[Id] [Link]") @add.command(name="GitHub", aliases=["Gh", "Git"], usage="[Id] [Link]")

View File

@ -102,7 +102,7 @@ class Stats(commands.Cog):
embed.set_author(name="Channel Activity - {}".format(ctx.guild)) embed.set_author(name="Channel Activity - {}".format(ctx.guild))
description = "" description = ""
for c in sorted(res, key=lambda x: int(x[1]), reverse=True): for c in sorted(res, key=lambda x: float(x[1]), reverse=True):
if not any(tc.id == int(c[0]) for tc in ctx.guild.text_channels): if not any(tc.id == int(c[0]) for tc in ctx.guild.text_channels):
continue continue

View File

@ -3,7 +3,9 @@ from data.remind import Reminders
from discord.ext import commands, tasks from discord.ext import commands, tasks
from enums.numbers import Numbers from enums.numbers import Numbers
from functions import timeFormatters from functions import timeFormatters
from functions.config import config
from functions.database import currency, poke, prison, birthdays, stats from functions.database import currency, poke, prison, birthdays, stats
from functions.scraping import getMatchweek
import json import json
import random import random
import requests import requests
@ -21,6 +23,7 @@ class Tasks(commands.Cog):
self.checkBirthdays.start() self.checkBirthdays.start()
self.updateMessageCounts.start() self.updateMessageCounts.start()
self.sendReminders.start() self.sendReminders.start()
self.updateMatchweek.start()
@tasks.loop(hours=1.0) @tasks.loop(hours=1.0)
async def bankInterest(self): async def bankInterest(self):
@ -218,6 +221,23 @@ class Tasks(commands.Cog):
async def beforeSendReminders(self): async def beforeSendReminders(self):
await self.client.wait_until_ready() await self.client.wait_until_ready()
@tasks.loop(hours=2.0)
async def updateMatchweek(self):
"""
Task that checks the current JPL matchweek & changes the dict value
"""
matchweek = getMatchweek()
if matchweek is None:
return
# Change the setting in the config
config("jpl_day", int(matchweek))
@updateMatchweek.before_loop
async def beforeUpdateMatchweek(self):
await self.client.wait_until_ready()
def getCurrentHour(self): def getCurrentHour(self):
return timeFormatters.dateTimeNow().hour return timeFormatters.dateTimeNow().hour

View File

@ -15,8 +15,8 @@ class Translate(commands.Cog):
def cog_check(self, ctx): def cog_check(self, ctx):
return not self.client.locked return not self.client.locked
@commands.command(name="Translate", aliases=["Tl", "Trans"], usage="[Tekst] [Van]* [Naar]*") # @commands.command(name="Translate", aliases=["Tl", "Trans"], usage="[Tekst] [Van]* [Naar]*")
@help.Category(Category.Words) # @help.Category(Category.Words)
async def translate(self, ctx, query=None, to="nl", fr="auto"): async def translate(self, ctx, query=None, to="nl", fr="auto"):
if query is None: if query is None:
return await ctx.send("Controleer je argumenten.") return await ctx.send("Controleer je argumenten.")
@ -57,8 +57,8 @@ class Translate(commands.Cog):
raise e raise e
@commands.command(name="Detect", aliases=["Ld"], usage="[Tekst]") # @commands.command(name="Detect", aliases=["Ld"], usage="[Tekst]")
@help.Category(Category.Words) # @help.Category(Category.Words)
async def detect(self, ctx, query=None): async def detect(self, ctx, query=None):
if query is None: if query is None:
return await ctx.send("Controleer je argumenten.") return await ctx.send("Controleer je argumenten.")

View File

@ -1 +1 @@
{"semester": "1", "year": "2", "years": 2, "jpl": 161733, "jpl_day": 4} {"semester": "1", "year": "2", "years": 2, "jpl": 161733, "jpl_day": 22}

View File

@ -1,14 +1,18 @@
from attr import dataclass
from functions.database import utils from functions.database import utils
def insert(id, name, fields): def insert(id, name, fields):
if getMeme(name)[0]: if getMeme(name) is not None:
return [False, "Deze meme staat al in de database."] return "Deze meme staat al in de database."
connection = utils.connect() connection = utils.connect()
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("INSERT INTO memes(id, name, fields) VALUES (%s, %s, %s)", [int(id), name.lower(), int(fields)]) cursor.execute("INSERT INTO memes(id, name, fields) VALUES (%s, %s, %s)", [int(id), name.lower(), int(fields)])
connection.commit() connection.commit()
return [True, "{} is toegevoegd aan de database.".format(name[0].upper() + name[1:].lower())]
return "{} is toegevoegd aan de database.".format(name[0].upper() + name[1:].lower())
def getMeme(name): def getMeme(name):
@ -16,9 +20,23 @@ def getMeme(name):
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("SELECT * FROM memes WHERE name like %s", ["%" + name.lower() + "%"]) cursor.execute("SELECT * FROM memes WHERE name like %s", ["%" + name.lower() + "%"])
result = cursor.fetchall() result = cursor.fetchall()
if len(result) == 0: if len(result) == 0:
return [False, "Deze meme staat niet in de database."] return None
return [True, result[0]]
meme = Meme(result[0][0], result[0][1], result[0][2])
return meme
@dataclass
class Meme:
"""
Dataclass to represent a meme in order to avoid having to use [] on tuples all the time
"""
meme_id: int
name: str
fields: int
def getAllMemes(): def getAllMemes():

View File

@ -0,0 +1,164 @@
from enum import Enum
from attr import dataclass, field
from functions.timeFormatters import fromString
from functions.scraping import getJPLMatches, getJPLTable
from functions.stringFormatters import leadingZero
from datetime import datetime
import tabulate
class Status(Enum):
AfterToday = "--:--"
NotStarted = "--:--"
Over = "Einde"
HalfTime = "Rust"
@dataclass
class Match:
"""
Class representing a football match between two teams
"""
matchDict: dict
home: str = field(init=False)
homeScore: int = 0
away: str = field(init=False)
awayScore: int = 0
start: datetime = field(init=False)
date: str = field(init=False)
weekDay: str = field(init=False)
status: Status = field(init=False)
def __attrs_post_init__(self):
"""
Parse class attributes out of a dictionary returned from an API request
"""
# The API isn't public, so every single game state is differently formatted
self.status = self._getStatus(self.matchDict[Navigation.Status.value])
self.home = self.matchDict[Navigation.HomeTeam.value][Navigation.Name.value]
self.away = self.matchDict[Navigation.AwayTeam.value][Navigation.Name.value]
if self._hasStarted():
self.homeScore = self.matchDict[Navigation.HomeScore.value]
self.awayScore = self.matchDict[Navigation.AwayScore.value]
self.start = fromString(self.matchDict["startDateTime"], formatString="%Y-%m-%dT%H:%M:%S.%f%z")
self.date = self.start.strftime("%d/%m")
self.weekDay = self._getWeekday()
def _getStatus(self, status: str):
"""
Gets the string representation for the status of this match
"""
# LiveTime only exists if the status is live
# Avoids KeyErrors
if status.lower() == "live":
# Half time
if Navigation.LiveMatchPhase.value in self.matchDict and \
Navigation.LiveMatchPhase.value == Navigation.HalfTime.value:
return Status.HalfTime.value
# Current time
return self.matchDict[Navigation.LiveTime.value]
# If no special status, pull it out of this dict
statusses: dict = {
"after_today": Status.AfterToday.value,
"not_started": Status.NotStarted.value,
"end": Status.Over.value
}
return statusses[status.lower()]
def _getWeekday(self):
"""
Gets the day of the week this match is played on
"""
day = self.start.weekday()
days = ["Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"]
return days[day]
def getInfo(self):
"""
Returns a list of all the info of this class in order to create a table
"""
return [self.weekDay, self.date, self.home, self._getScore(), self.away, self.status]
def _getScore(self):
"""
Returns a string representing the scoreboard
"""
# No score to show yet, show time when the match starts
if not self._hasStarted():
return "{}:{}".format(leadingZero(str(self.start.hour)), leadingZero(str(self.start.minute)))
return "{} - {}".format(self.homeScore, self.awayScore)
def _hasStarted(self):
return self.status not in [Status.AfterToday.value, Status.NotStarted.value]
class Navigation(Enum):
"""
Enum to navigate through the matchdict,
seeing as the API is private the keys of the dict could change every now and then
so this makes sure a key only has to be changed once.
"""
AwayTeam = "awayTeam"
HomeTeam = "homeTeam"
AwayScore = "awayScore"
HomeScore = "homeScore"
LiveTime = "liveTime"
LiveMatchPhase = "liveMatchPhase"
HalfTime = "HALF_TIME"
Status = "status"
Name = "name"
def getMatches(matchweek: int):
"""
Function that constructs the list of matches for a given matchweek
"""
current_day = getJPLMatches(matchweek)
# API request failed
if current_day is None:
return "Er ging iets fout. Probeer het later opnieuw."
matches = list(map(Match, current_day))
matches = list(map(lambda x: x.getInfo(), matches))
header = "Jupiler Pro League - Speeldag {}".format(matchweek)
table = tabulate.tabulate(matches, headers=["Dag", "Datum", "Thuis", "Stand", "Uit", "Tijd"])
return "```{}\n\n{}```".format(header, table)
def getTable():
"""
Function that constructs the current table of the JPL
"""
rows = getJPLTable()
if rows is None:
return "Er ging iets fout. Probeer het later opnieuw."
# Format every row to work for Tabulate
formatted = [_formatRow(row) for row in rows]
header = "Jupiler Pro League Klassement"
table = tabulate.tabulate(formatted, headers=["#", "Ploeg", "Punten", "M", "M+", "M-", "M="])
return "```{}\n\n{}```".format(header, table)
def _formatRow(row):
"""
Function that formats a row into a list for Tabulate to use
"""
scoresArray = list([td.renderContents().decode("utf-8") for td in row.find_all("td")])[:6]
# Insert the team name into the list
scoresArray.insert(1, row.find_all("a")[0].renderContents().decode("utf-8").split("<!--")[0])
return scoresArray

102
functions/memes.py 100644
View File

@ -0,0 +1,102 @@
import os
import requests
from functions.database.memes import Meme
from functions.mock import mock
def generate(meme: Meme, fields):
"""
Main function that takes a Meme as input & generates an image.
"""
# If there's only one field, the user isn't required to use quotes
if meme.fields == 1:
fields = [" ".join(fields)]
fields = _applyMeme(meme, fields)
# List of fields to send to the API
boxes = [{"text": ""}, {"text": ""}, {"text": ""}, {"text": ""}]
# Add all fields required & ignore the excess ones
for i in range(len(fields)):
if i > 3:
break
boxes[i]["text"] = fields[i]
# Check server status
req = requests.get('https://api.imgflip.com/get_memes').json()
# Server is down
if not req["success"]:
return {"success": False, "message": "Er is een fout opgetreden."}
# Post meme
reply = _postMeme(meme, boxes)
# Adding a message parameter makes the code in the cog a lot cleaner
if not reply["success"]:
reply["message"] = "Error! Controleer of je de juiste syntax hebt gebruikt. Gebruik het commando " \
"\"memes\" voor een lijst aan geaccepteerde meme-namen."
else:
reply["message"] = reply["data"]["url"]
return reply
def _postMeme(meme: Meme, boxes):
"""
Performs API request to generate the meme
"""
caption = {
"template_id": meme.meme_id,
"username": os.getenv("IMGFLIPNAME"),
"password": os.getenv("IMGFLIPPASSWORD"),
"boxes[0][text]": boxes[0]["text"],
"boxes[1][text]": boxes[1]["text"],
"boxes[2][text]": boxes[2]["text"],
"boxes[3][text]": boxes[3]["text"]
}
# Send the POST to the API
memeReply = requests.post('https://api.imgflip.com/caption_image', caption).json()
return memeReply
def _applyMeme(meme: Meme, fields):
"""
Some memes are in a special format that only requires
a few words to be added, or needs the input to be changed.
This function handles all that.
Links certain meme id's to functions that need to be applied first.
"""
memeDict = {
102156234: _mockingSpongebob,
91538330: _xXEverywhere,
252600902: _alwaysHasBeen
}
# Meme needs no special treatment
if meme.meme_id not in memeDict:
return fields
return memeDict[meme.meme_id](fields)
def _mockingSpongebob(fields):
return list(map(mock, fields))
def _xXEverywhere(fields):
word = fields[0]
return ["{}".format(word), "{} everywhere".format(word)]
def _alwaysHasBeen(fields):
word = fields[0]
return ["Wait, it's all {}?".format(word), "Always has been"]

View File

@ -1,3 +1,5 @@
import re
from requests import get from requests import get
from urllib.parse import urlencode from urllib.parse import urlencode
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@ -43,3 +45,62 @@ def google_search(query):
divs = bs.find_all("div", attrs={"class": "g"}) divs = bs.find_all("div", attrs={"class": "g"})
return list(getContent(d) for d in divs), 200 return list(getContent(d) for d in divs), 200
def getMatchweek():
"""
Parses the current JPL matchweek out of Sporza's site
"""
resp = get("https://sporza.be/nl/categorie/voetbal/jupiler-pro-league/")
if resp.status_code != 200:
return None
bs = BeautifulSoup(resp.text, "html.parser")
matchdays = bs.find_all("section", attrs={"class": "sc-matchdays"})
if len(matchdays) < 2:
return None
# Table header
header = matchdays[1]
# Regex to find current matchday
r = re.compile(r"speeldag\s*\d+", flags=re.I)
match = r.search(str(header))
# Something went wrong, just ignore
if match is None:
return None
# "Speeldag DD" -> split on space & take second
return match[0].split(" ")[1]
def getJPLMatches(week: int):
"""
JPL matches for a given matchweek
"""
current_day = get("https://api.sporza.be/web/soccer/matchdays/161733/{}".format(week))
# Something went wrong
if current_day.status_code != 200:
return None
return current_day.json()["groupedMatches"][0]["matches"]
def getJPLTable():
"""
JPL table
"""
page_html = get("https://sporza.be/nl/categorie/voetbal/jupiler-pro-league/")
# Something went wrong
if page_html.status_code != 200:
return None
bs_parsed = BeautifulSoup(page_html.text, "html.parser")
rows = bs_parsed.find(summary="algemeen klassement").find_all("tr")[1:]
return rows