mirror of https://github.com/stijndcl/didier
Add flake8 docstring plugin, formatting, create base class for embeds & models
parent
8d6dbe1c94
commit
b9c5c6ab10
25
.flake8
25
.flake8
|
@ -1,4 +1,5 @@
|
||||||
[flake8]
|
[flake8]
|
||||||
|
# Don't lint non-Python files
|
||||||
exclude =
|
exclude =
|
||||||
.git,
|
.git,
|
||||||
.github,
|
.github,
|
||||||
|
@ -9,10 +10,30 @@ exclude =
|
||||||
htmlcov,
|
htmlcov,
|
||||||
tests,
|
tests,
|
||||||
venv
|
venv
|
||||||
ignore=E203
|
# Disable rules that we don't care about (or conflict with others)
|
||||||
|
extend-ignore =
|
||||||
|
# Missing docstring in public module
|
||||||
|
D100, D104,
|
||||||
|
# Missing docstring in magic method
|
||||||
|
D105,
|
||||||
|
# Missing docstring in __init__
|
||||||
|
D107,
|
||||||
|
# First line of docstrings should end with a period
|
||||||
|
D400,
|
||||||
|
# First line of docstrings should be in imperative mood
|
||||||
|
D401,
|
||||||
|
# Whitespace before ":"
|
||||||
|
E203,
|
||||||
|
# Don't require docstrings when overriding a method,
|
||||||
|
# the base method should have a docstring but the rest not
|
||||||
|
ignore-decorator=overrides
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
|
# Disable some rules for entire files
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
# Missing __all__, main isn't supposed to be imported
|
# Missing __all__, main isn't supposed to be imported
|
||||||
main.py: DALL000,
|
main.py: DALL000,
|
||||||
# Missing __all__, Cogs aren't modules
|
# Missing __all__, Cogs aren't modules
|
||||||
./didier/cogs/*: DALL000
|
./didier/cogs/*: DALL000,
|
||||||
|
# All of the colours methods are just oneliners to create a colour,
|
||||||
|
# there's no point adding docstrings (function names are enough)
|
||||||
|
./didier/utils/discord/colours.py: D103
|
||||||
|
|
|
@ -28,6 +28,7 @@ async def create_new_announcement(
|
||||||
|
|
||||||
async def remove_old_announcements(session: AsyncSession):
|
async def remove_old_announcements(session: AsyncSession):
|
||||||
"""Delete all announcements that are > 8 days old
|
"""Delete all announcements that are > 8 days old
|
||||||
|
|
||||||
The RSS feed only goes back 7 days, so all of these old announcements never have to
|
The RSS feed only goes back 7 days, so all of these old announcements never have to
|
||||||
be checked again when checking if an announcement is fresh or not.
|
be checked again when checking if an announcement is fresh or not.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,7 +12,8 @@ __all__ = [
|
||||||
|
|
||||||
async def get_or_add(session: AsyncSession, user_id: int) -> User:
|
async def get_or_add(session: AsyncSession, user_id: int) -> User:
|
||||||
"""Get a user's profile
|
"""Get a user's profile
|
||||||
If it doesn't exist yet, create it (along with all linked datastructures).
|
|
||||||
|
If it doesn't exist yet, create it (along with all linked datastructures)
|
||||||
"""
|
"""
|
||||||
statement = select(User).where(User.user_id == user_id)
|
statement = select(User).where(User.user_id == user_id)
|
||||||
user: Optional[User] = (await session.execute(statement)).scalar_one_or_none()
|
user: Optional[User] = (await session.execute(statement)).scalar_one_or_none()
|
||||||
|
|
|
@ -2,13 +2,15 @@ from typing import List, Mapping, Optional
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
from overrides import overrides
|
||||||
|
|
||||||
from didier import Didier
|
from didier import Didier
|
||||||
|
|
||||||
|
|
||||||
class CustomHelpCommand(commands.MinimalHelpCommand):
|
class CustomHelpCommand(commands.MinimalHelpCommand):
|
||||||
"""Customised Help command to override the default implementation
|
"""Customised Help command to override the default implementation
|
||||||
The default is ugly as hell
|
|
||||||
|
The default is ugly as hell so we do some fiddling with it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _help_embed_base(self, title: str) -> discord.Embed:
|
def _help_embed_base(self, title: str) -> discord.Embed:
|
||||||
|
@ -30,6 +32,7 @@ class CustomHelpCommand(commands.MinimalHelpCommand):
|
||||||
|
|
||||||
return list(sorted(filtered_cogs, key=lambda cog: cog.qualified_name))
|
return list(sorted(filtered_cogs, key=lambda cog: cog.qualified_name))
|
||||||
|
|
||||||
|
@overrides
|
||||||
async def send_bot_help(self, mapping: Mapping[Optional[commands.Cog], List[commands.Command]], /):
|
async def send_bot_help(self, mapping: Mapping[Optional[commands.Cog], List[commands.Command]], /):
|
||||||
embed = self._help_embed_base("Categorieën")
|
embed = self._help_embed_base("Categorieën")
|
||||||
filtered_cogs = await self._filter_cogs(list(mapping.keys()))
|
filtered_cogs = await self._filter_cogs(list(mapping.keys()))
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from didier import Didier
|
||||||
|
from didier.data.apis import urban_dictionary
|
||||||
|
|
||||||
|
|
||||||
|
class Other(commands.Cog):
|
||||||
|
"""Cog for commands that don't really belong anywhere else"""
|
||||||
|
|
||||||
|
client: Didier
|
||||||
|
|
||||||
|
def __init__(self, client: Didier):
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
@commands.command(name="Define", aliases=["Ud", "Urban"], usage="[Woord]")
|
||||||
|
async def define(self, ctx: commands.Context, *, query: str):
|
||||||
|
"""Look up the definition of a word on the Urban Dictionary"""
|
||||||
|
definitions = urban_dictionary.lookup(self.client.http_session, query)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(client: Didier):
|
||||||
|
"""Load the cog"""
|
||||||
|
await client.add_cog(Other(client))
|
|
@ -25,8 +25,9 @@ class Owner(commands.Cog):
|
||||||
self.client = client
|
self.client = client
|
||||||
|
|
||||||
async def cog_check(self, ctx: commands.Context) -> bool:
|
async def cog_check(self, ctx: commands.Context) -> bool:
|
||||||
"""Global check for every command in this cog, so we don't have to add
|
"""Global check for every command in this cog
|
||||||
is_owner() to every single command separately
|
|
||||||
|
This means that we don't have to add is_owner() to every single command separately
|
||||||
"""
|
"""
|
||||||
# pylint: disable=W0236 # Pylint thinks this can't be async, but it can
|
# pylint: disable=W0236 # Pylint thinks this can't be async, but it can
|
||||||
return await self.client.is_owner(ctx.author)
|
return await self.client.is_owner(ctx.author)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
|
from didier.data.embeds.urban_dictionary import Definition
|
||||||
|
|
||||||
|
__all__ = ["lookup"]
|
||||||
|
|
||||||
|
|
||||||
|
async def lookup(http_session: ClientSession, query: str) -> list[Definition]:
|
||||||
|
"""Fetch the Urban Dictionary definitions for a given word"""
|
||||||
|
url = "https://api.urbandictionary.com/v0/define"
|
||||||
|
|
||||||
|
async with http_session.get(url, params={"term": query}) as response:
|
||||||
|
response_json = await response.json()
|
||||||
|
return list(map(Definition.parse_obj, response_json["list"]))
|
|
@ -0,0 +1,22 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"EmbedBaseModel",
|
||||||
|
"EmbedPydantic",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EmbedBaseModel(ABC):
|
||||||
|
"""Abstract base class for a model that can be turned into a Discord embed"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def to_embed(self) -> discord.Embed:
|
||||||
|
"""Turn this model into a Discord embed"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class EmbedPydantic(ABC, EmbedBaseModel, BaseModel):
|
||||||
|
"""Pydantic version of EmbedModel"""
|
|
@ -14,6 +14,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
import settings
|
import settings
|
||||||
from database.crud import ufora_announcements as crud
|
from database.crud import ufora_announcements as crud
|
||||||
from database.models import UforaCourse
|
from database.models import UforaCourse
|
||||||
|
from didier.data.embeds.base import EmbedBaseModel
|
||||||
from didier.utils.types.datetime import int_to_weekday
|
from didier.utils.types.datetime import int_to_weekday
|
||||||
from didier.utils.types.string import leading
|
from didier.utils.types.string import leading
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ __all__ = [
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UforaNotification:
|
class UforaNotification(EmbedBaseModel):
|
||||||
"""A single notification from Ufora"""
|
"""A single notification from Ufora"""
|
||||||
|
|
||||||
content: dict
|
content: dict
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from overrides import overrides
|
||||||
|
|
||||||
|
from didier.data.embeds.base import EmbedPydantic
|
||||||
|
|
||||||
|
__all__ = ["Definition"]
|
||||||
|
|
||||||
|
|
||||||
|
class Definition(EmbedPydantic):
|
||||||
|
"""A definition from the Urban Dictionary"""
|
||||||
|
|
||||||
|
word: str
|
||||||
|
definition: str
|
||||||
|
permalink: str
|
||||||
|
author: str
|
||||||
|
thumbs_up: int
|
||||||
|
thumbs_down: int
|
||||||
|
written_on: datetime
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def to_embed(self) -> discord.Embed:
|
||||||
|
embed = discord.Embed()
|
|
@ -2,6 +2,7 @@ import traceback
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
from overrides import overrides
|
||||||
|
|
||||||
from database.crud.custom_commands import create_command, edit_command
|
from database.crud.custom_commands import create_command, edit_command
|
||||||
from didier import Didier
|
from didier import Didier
|
||||||
|
@ -24,12 +25,14 @@ class CreateCustomCommand(discord.ui.Modal, title="Create Custom Command"):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.client = client
|
self.client = client
|
||||||
|
|
||||||
|
@overrides
|
||||||
async def on_submit(self, interaction: discord.Interaction):
|
async def on_submit(self, interaction: discord.Interaction):
|
||||||
async with self.client.db_session as session:
|
async with self.client.db_session as session:
|
||||||
command = await create_command(session, str(self.name.value), str(self.response.value))
|
command = await create_command(session, str(self.name.value), str(self.response.value))
|
||||||
|
|
||||||
await interaction.response.send_message(f"Successfully created ``{command.name}``.", ephemeral=True)
|
await interaction.response.send_message(f"Successfully created ``{command.name}``.", ephemeral=True)
|
||||||
|
|
||||||
|
@overrides
|
||||||
async def on_error(self, interaction: discord.Interaction, error: Exception): # type: ignore
|
async def on_error(self, interaction: discord.Interaction, error: Exception): # type: ignore
|
||||||
await interaction.response.send_message("Something went wrong.", ephemeral=True)
|
await interaction.response.send_message("Something went wrong.", ephemeral=True)
|
||||||
traceback.print_tb(error.__traceback__)
|
traceback.print_tb(error.__traceback__)
|
||||||
|
@ -37,7 +40,8 @@ class CreateCustomCommand(discord.ui.Modal, title="Create Custom Command"):
|
||||||
|
|
||||||
class EditCustomCommand(discord.ui.Modal, title="Edit Custom Command"):
|
class EditCustomCommand(discord.ui.Modal, title="Edit Custom Command"):
|
||||||
"""Modal to edit an existing custom command
|
"""Modal to edit an existing custom command
|
||||||
Fills in the current values as defaults
|
|
||||||
|
Fills in the current values as defaults for QOL
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: discord.ui.TextInput
|
name: discord.ui.TextInput
|
||||||
|
@ -59,6 +63,7 @@ class EditCustomCommand(discord.ui.Modal, title="Edit Custom Command"):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@overrides
|
||||||
async def on_submit(self, interaction: discord.Interaction):
|
async def on_submit(self, interaction: discord.Interaction):
|
||||||
name_field = typing.cast(discord.ui.TextInput, self.children[0])
|
name_field = typing.cast(discord.ui.TextInput, self.children[0])
|
||||||
response_field = typing.cast(discord.ui.TextInput, self.children[1])
|
response_field = typing.cast(discord.ui.TextInput, self.children[1])
|
||||||
|
@ -68,6 +73,7 @@ class EditCustomCommand(discord.ui.Modal, title="Edit Custom Command"):
|
||||||
|
|
||||||
await interaction.response.send_message(f"Successfully edited ``{self.original_name}``.", ephemeral=True)
|
await interaction.response.send_message(f"Successfully edited ``{self.original_name}``.", ephemeral=True)
|
||||||
|
|
||||||
|
@overrides
|
||||||
async def on_error(self, interaction: discord.Interaction, error: Exception): # type: ignore
|
async def on_error(self, interaction: discord.Interaction, error: Exception): # type: ignore
|
||||||
await interaction.response.send_message("Something went wrong.", ephemeral=True)
|
await interaction.response.send_message("Something went wrong.", ephemeral=True)
|
||||||
traceback.print_tb(error.__traceback__)
|
traceback.print_tb(error.__traceback__)
|
||||||
|
|
|
@ -42,7 +42,10 @@ class Didier(commands.Bot):
|
||||||
return DBSession()
|
return DBSession()
|
||||||
|
|
||||||
async def setup_hook(self) -> None:
|
async def setup_hook(self) -> None:
|
||||||
"""Hook called once the bot is initialised"""
|
"""Do some initial setup
|
||||||
|
|
||||||
|
This hook is called once the bot is initialised
|
||||||
|
"""
|
||||||
# Load extensions
|
# Load extensions
|
||||||
await self._load_initial_extensions()
|
await self._load_initial_extensions()
|
||||||
await self._load_directory_extensions("didier/cogs")
|
await self._load_directory_extensions("didier/cogs")
|
||||||
|
@ -113,7 +116,9 @@ class Didier(commands.Bot):
|
||||||
|
|
||||||
async def _try_invoke_custom_command(self, message: discord.Message) -> bool:
|
async def _try_invoke_custom_command(self, message: discord.Message) -> bool:
|
||||||
"""Check if the message tries to invoke a custom command
|
"""Check if the message tries to invoke a custom command
|
||||||
|
|
||||||
If it does, send the reply associated with it
|
If it does, send the reply associated with it
|
||||||
|
Returns a boolean indicating if a message invoked a command or not
|
||||||
"""
|
"""
|
||||||
# Doesn't start with the custom command prefix
|
# Doesn't start with the custom command prefix
|
||||||
if not message.content.startswith(settings.DISCORD_CUSTOM_COMMAND_PREFIX):
|
if not message.content.startswith(settings.DISCORD_CUSTOM_COMMAND_PREFIX):
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
__all__ = ["MissingEnvironmentVariable"]
|
||||||
|
|
||||||
|
|
||||||
|
class MissingEnvironmentVariable(RuntimeError):
|
||||||
|
"""Exception raised when an environment variable is missing
|
||||||
|
|
||||||
|
These are not necessarily checked on startup, because they may be unused
|
||||||
|
during a given test run, and random unrelated crashes would be annoying
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, variable: str):
|
||||||
|
super().__init__(f"Missing environment variable: {variable}")
|
|
@ -0,0 +1,7 @@
|
||||||
|
import discord
|
||||||
|
|
||||||
|
__all__ = ["urban_dictionary_green"]
|
||||||
|
|
||||||
|
|
||||||
|
def urban_dictionary_green() -> discord.Colour:
|
||||||
|
return discord.Colour.from_rgb(220, 255, 0)
|
|
@ -6,6 +6,7 @@ __all__ = ["abbreviated_number"]
|
||||||
|
|
||||||
def abbreviated_number(argument: str) -> Union[str, int]:
|
def abbreviated_number(argument: str) -> Union[str, int]:
|
||||||
"""Custom converter to allow numbers to be abbreviated
|
"""Custom converter to allow numbers to be abbreviated
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
515k
|
515k
|
||||||
4m
|
4m
|
||||||
|
|
|
@ -10,6 +10,7 @@ __all__ = ["get_prefix"]
|
||||||
|
|
||||||
def get_prefix(client: commands.Bot, message: Message) -> str:
|
def get_prefix(client: commands.Bot, message: Message) -> str:
|
||||||
"""Match a prefix against a message
|
"""Match a prefix against a message
|
||||||
|
|
||||||
This is done dynamically to allow variable amounts of whitespace,
|
This is done dynamically to allow variable amounts of whitespace,
|
||||||
and through regexes to allow case-insensitivity among other things.
|
and through regexes to allow case-insensitivity among other things.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,6 +6,7 @@ __all__ = ["leading", "pluralize"]
|
||||||
|
|
||||||
def leading(character: str, string: str, target_length: Optional[int] = 2) -> str:
|
def leading(character: str, string: str, target_length: Optional[int] = 2) -> str:
|
||||||
"""Add a leading [character] to [string] to make it length [target_length]
|
"""Add a leading [character] to [string] to make it length [target_length]
|
||||||
|
|
||||||
Pass None to target length to always do it (once), no matter the length
|
Pass None to target length to always do it (once), no matter the length
|
||||||
"""
|
"""
|
||||||
# Cast to string just in case
|
# Cast to string just in case
|
||||||
|
|
|
@ -22,6 +22,7 @@ profile = "black"
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
plugins = [
|
plugins = [
|
||||||
|
"pydantic.mypy",
|
||||||
"sqlalchemy.ext.mypy.plugin"
|
"sqlalchemy.ext.mypy.plugin"
|
||||||
]
|
]
|
||||||
[[tool.mypy.overrides]]
|
[[tool.mypy.overrides]]
|
||||||
|
|
|
@ -11,8 +11,8 @@ types-pytz==2021.3.8
|
||||||
# Flake8 + plugins
|
# Flake8 + plugins
|
||||||
flake8==4.0.1
|
flake8==4.0.1
|
||||||
flake8-bandit==3.0.0
|
flake8-bandit==3.0.0
|
||||||
flake8-black==0.3.3
|
|
||||||
flake8-bugbear==22.7.1
|
flake8-bugbear==22.7.1
|
||||||
|
flake8-docstrings==1.6.0
|
||||||
flake8-dunder-all==0.2.1
|
flake8-dunder-all==0.2.1
|
||||||
flake8-eradicate==1.2.1
|
flake8-eradicate==1.2.1
|
||||||
flake8-isort==4.1.1
|
flake8-isort==4.1.1
|
||||||
|
|
|
@ -6,5 +6,8 @@ git+https://github.com/Rapptz/discord.py
|
||||||
environs==9.5.0
|
environs==9.5.0
|
||||||
feedparser==6.0.10
|
feedparser==6.0.10
|
||||||
markdownify==0.11.2
|
markdownify==0.11.2
|
||||||
|
overrides==6.1.0
|
||||||
|
pydantic==1.9.1
|
||||||
|
python-dateutil==2.8.2
|
||||||
pytz==2022.1
|
pytz==2022.1
|
||||||
sqlalchemy[asyncio]==1.4.37
|
sqlalchemy[asyncio]==1.4.37
|
||||||
|
|
33
settings.py
33
settings.py
|
@ -6,6 +6,26 @@ from environs import Env
|
||||||
env = Env()
|
env = Env()
|
||||||
env.read_env()
|
env.read_env()
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"SANDBOX",
|
||||||
|
"LOGFILE",
|
||||||
|
"DB_NAME",
|
||||||
|
"DB_USERNAME",
|
||||||
|
"DB_PASSWORD",
|
||||||
|
"DB_HOST",
|
||||||
|
"DB_PORT",
|
||||||
|
"DISCORD_TOKEN",
|
||||||
|
"DISCORD_READY_MESSAGE",
|
||||||
|
"DISCORD_STATUS_MESSAGE",
|
||||||
|
"DISCORD_TEST_GUILDS",
|
||||||
|
"DISCORD_BOOS_REACT",
|
||||||
|
"DISCORD_CUSTOM_COMMAND_PREFIX",
|
||||||
|
"UFORA_ANNOUNCEMENTS_CHANNEL",
|
||||||
|
"UFORA_RSS_TOKEN",
|
||||||
|
"URBAN_DICTIONARY_TOKEN",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
"""General config"""
|
"""General config"""
|
||||||
SANDBOX: bool = env.bool("SANDBOX", True)
|
SANDBOX: bool = env.bool("SANDBOX", True)
|
||||||
LOGFILE: str = env.str("LOGFILE", "didier.log")
|
LOGFILE: str = env.str("LOGFILE", "didier.log")
|
||||||
|
@ -18,13 +38,14 @@ DB_HOST: str = env.str("DB_HOST", "localhost")
|
||||||
DB_PORT: int = env.int("DB_PORT", "5432")
|
DB_PORT: int = env.int("DB_PORT", "5432")
|
||||||
|
|
||||||
"""Discord"""
|
"""Discord"""
|
||||||
DISCORD_TOKEN: str = env.str("DISC_TOKEN")
|
DISCORD_TOKEN: str = env.str("DISCORD_TOKEN")
|
||||||
DISCORD_READY_MESSAGE: str = env.str("DISC_READY_MESSAGE", "I'M READY I'M READY I'M READY")
|
DISCORD_READY_MESSAGE: str = env.str("DISCORD_READY_MESSAGE", "I'M READY I'M READY I'M READY")
|
||||||
DISCORD_STATUS_MESSAGE: str = env.str("DISC_STATUS_MESSAGE", "with your Didier Dinks.")
|
DISCORD_STATUS_MESSAGE: str = env.str("DISCORD_STATUS_MESSAGE", "with your Didier Dinks.")
|
||||||
DISCORD_TEST_GUILDS: list[int] = env.list("DISC_TEST_GUILDS", [], subcast=int)
|
DISCORD_TEST_GUILDS: list[int] = env.list("DISCORD_TEST_GUILDS", [], subcast=int)
|
||||||
DISCORD_BOOS_REACT: str = env.str("DISC_BOOS_REACT", "<:boos:629603785840263179>")
|
DISCORD_BOOS_REACT: str = env.str("DISCORD_BOOS_REACT", "<:boos:629603785840263179>")
|
||||||
DISCORD_CUSTOM_COMMAND_PREFIX: str = env.str("DISC_CUSTOM_COMMAND_PREFIX", "?")
|
DISCORD_CUSTOM_COMMAND_PREFIX: str = env.str("DISCORD_CUSTOM_COMMAND_PREFIX", "?")
|
||||||
UFORA_ANNOUNCEMENTS_CHANNEL: Optional[int] = env.int("UFORA_ANNOUNCEMENTS_CHANNEL", None)
|
UFORA_ANNOUNCEMENTS_CHANNEL: Optional[int] = env.int("UFORA_ANNOUNCEMENTS_CHANNEL", None)
|
||||||
|
|
||||||
"""API Keys"""
|
"""API Keys"""
|
||||||
UFORA_RSS_TOKEN: Optional[str] = env.str("UFORA_RSS_TOKEN", None)
|
UFORA_RSS_TOKEN: Optional[str] = env.str("UFORA_RSS_TOKEN", None)
|
||||||
|
URBAN_DICTIONARY_TOKEN: Optional[str] = env.str("URBAN_DICTIONARY_TOKEN", None)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"definition": "When you fall [asleep] [tweeting] about [nonsensical] things",
|
||||||
|
"permalink": "http://cofveve.urbanup.com/11642742",
|
||||||
|
"thumbs_up": 170,
|
||||||
|
"sound_urls": [],
|
||||||
|
"author": "Bonafidé",
|
||||||
|
"word": "cofveve",
|
||||||
|
"defid": 11642742,
|
||||||
|
"current_vote": "",
|
||||||
|
"written_on": "2017-06-03T00:32:10.987Z",
|
||||||
|
"example": "[Despite] [the negative] [press] cofveve",
|
||||||
|
"thumbs_down": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"definition": "when you want to type [conference] and your hands are too small to reach [the keys] (someone else's [brill] def)",
|
||||||
|
"permalink": "http://cofveve.urbanup.com/11662158",
|
||||||
|
"thumbs_up": 69,
|
||||||
|
"sound_urls": [],
|
||||||
|
"author": "blacklist2017",
|
||||||
|
"word": "cofveve",
|
||||||
|
"defid": 11662158,
|
||||||
|
"current_vote": "",
|
||||||
|
"written_on": "2017-06-07T00:44:36.793Z",
|
||||||
|
"example": "\"[i want] to [shut down] this [press] cofveve...",
|
||||||
|
"thumbs_down": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"definition": "[Bullshit]",
|
||||||
|
"permalink": "http://cofveve.urbanup.com/12386593",
|
||||||
|
"thumbs_up": 5,
|
||||||
|
"sound_urls": [],
|
||||||
|
"author": "FreedomTodd",
|
||||||
|
"word": "Cofveve",
|
||||||
|
"defid": 12386593,
|
||||||
|
"current_vote": "",
|
||||||
|
"written_on": "2018-01-06T17:52:59.822Z",
|
||||||
|
"example": "[I’m] [full of] [cofveve]",
|
||||||
|
"thumbs_down": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"definition": "When you dip [an apple] in a chocolate [Starbucks] [frappe]",
|
||||||
|
"permalink": "http://razzle-cofveve.urbanup.com/12006856",
|
||||||
|
"thumbs_up": 0,
|
||||||
|
"sound_urls": [],
|
||||||
|
"author": "harry potter theme song si ",
|
||||||
|
"word": "razzle cofveve",
|
||||||
|
"defid": 12006856,
|
||||||
|
"current_vote": "",
|
||||||
|
"written_on": "2017-09-30T03:00:54.233Z",
|
||||||
|
"example": "[My friend] loves [apples] and [Starbucks], so the razzle cofveve was perfect!",
|
||||||
|
"thumbs_down": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue