mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-06 09:22:31 -05:00
Compare commits
26 Commits
3.0.0b17.p
...
3.0.0b19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a91cda4995 | ||
|
|
7959654dc8 | ||
|
|
dc9a85ca98 | ||
|
|
591ed50ac3 | ||
|
|
47350328e6 | ||
|
|
75ed749cb3 | ||
|
|
f44ea8b749 | ||
|
|
76c0071f57 | ||
|
|
2a396b4438 | ||
|
|
51a54863c5 | ||
|
|
06f986b92e | ||
|
|
652ceba845 | ||
|
|
16d0f54d8f | ||
|
|
872cce784a | ||
|
|
aec3ad382a | ||
|
|
9d4f9ef73c | ||
|
|
cf7cafc261 | ||
|
|
e3bff7e87c | ||
|
|
4b19421075 | ||
|
|
cf371e8093 | ||
|
|
5eeadc6399 | ||
|
|
f6823ea3d1 | ||
|
|
f24290c423 | ||
|
|
f8a36885fe | ||
|
|
a555eff2cc | ||
|
|
05c389623c |
@@ -1,5 +1,5 @@
|
|||||||
api_key_env: CROWDIN_API_KEY
|
api_key_env: CROWDIN_API_KEY
|
||||||
project_identifier_env: CROWDIN_PROJECT_ID
|
project_identifier_env: CROWDIN_PROJECT_ID
|
||||||
files:
|
files:
|
||||||
- source: /**/*.pot
|
- source: /redbot/**/*.pot
|
||||||
translation: /%original_path%/%locale%.po
|
translation: /%original_path%/%locale%.po
|
||||||
|
|||||||
@@ -190,6 +190,13 @@ texinfo_documents = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for linkcheck builder ----------------------------------------
|
||||||
|
|
||||||
|
# A list of regular expressions that match URIs that should not be
|
||||||
|
# checked when doing a linkcheck build.
|
||||||
|
linkcheck_ignore = [r"https://java.com*"]
|
||||||
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
"python": ("https://docs.python.org/3.6", None),
|
"python": ("https://docs.python.org/3.6", None),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Installing Red on Windows
|
|||||||
Needed Software
|
Needed Software
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
* `Python <https://python.org/downloads/>`_ - Red needs Python 3.6
|
* `Python <https://www.python.org/downloads/>`_ - Red needs Python 3.6
|
||||||
|
|
||||||
.. note:: Please make sure that the box to add Python to PATH is CHECKED, otherwise
|
.. note:: Please make sure that the box to add Python to PATH is CHECKED, otherwise
|
||||||
you may run into issues when trying to run Red
|
you may run into issues when trying to run Red
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ import logging.handlers
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# Let's not force this dependency, uvloop is much faster on cpython
|
||||||
|
if sys.implementation.name == "cpython":
|
||||||
|
try:
|
||||||
|
import uvloop
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Red - Discord Bot v3
|
# Red - Discord Bot v3
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class Admin:
|
|||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self._addrole(ctx, user, rolename)
|
await self._addrole(ctx, user, rolename)
|
||||||
else:
|
else:
|
||||||
await self.complain(ctx, USER_HIERARCHY_ISSUE, member=ctx.author)
|
await self.complain(ctx, USER_HIERARCHY_ISSUE, member=ctx.author, role=rolename)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ class Cleanup:
|
|||||||
- The message is less than 14 days old
|
- The message is less than 14 days old
|
||||||
- The message is not pinned
|
- The message is not pinned
|
||||||
"""
|
"""
|
||||||
|
if after is not None:
|
||||||
|
log.error(
|
||||||
|
"The `after` parameter for the `Cleanup.get_messages_for_deletion` method is "
|
||||||
|
"currently broken, see PRs #1980 and #2004 for details."
|
||||||
|
)
|
||||||
|
|
||||||
to_delete = []
|
to_delete = []
|
||||||
too_old = False
|
too_old = False
|
||||||
|
|
||||||
@@ -238,23 +244,35 @@ class Cleanup:
|
|||||||
await ctx.send(_("This command can only be used on bots with bot accounts."))
|
await ctx.send(_("This command can only be used on bots with bot accounts."))
|
||||||
return
|
return
|
||||||
|
|
||||||
after = await channel.get_message(message_id)
|
try:
|
||||||
|
message = await channel.get_message(message_id)
|
||||||
if not after:
|
except discord.NotFound:
|
||||||
await ctx.send(_("Message not found."))
|
await ctx.send(_("Message not found."))
|
||||||
return
|
return
|
||||||
|
|
||||||
to_delete = await self.get_messages_for_deletion(
|
if (ctx.message.created_at - message.created_at).days >= 14:
|
||||||
ctx, channel, 0, limit=None, after=after, delete_pinned=delete_pinned
|
await ctx.send("The specified message must be less than 14 days old.")
|
||||||
)
|
return
|
||||||
|
|
||||||
|
if not delete_pinned:
|
||||||
|
pinned_msgs = await channel.pins()
|
||||||
|
to_exclude = set(m for m in pinned_msgs if m.created_at > message.created_at)
|
||||||
|
else:
|
||||||
|
to_exclude = None
|
||||||
|
|
||||||
|
if to_exclude:
|
||||||
|
to_delete = await channel.history(limit=None, after=message).flatten()
|
||||||
|
to_delete = set(to_delete) - to_exclude
|
||||||
|
await channel.delete_messages(to_delete)
|
||||||
|
num_deleted = len(to_delete)
|
||||||
|
else:
|
||||||
|
num_deleted = len(await channel.purge(limit=None, after=message))
|
||||||
|
|
||||||
reason = "{}({}) deleted {} messages in channel {}.".format(
|
reason = "{}({}) deleted {} messages in channel {}.".format(
|
||||||
author.name, author.id, len(to_delete), channel.name
|
author.name, author.id, num_deleted, channel.name
|
||||||
)
|
)
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
|
|
||||||
await mass_purge(to_delete, channel)
|
|
||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import time
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from random import randint, choice
|
from random import randint, choice
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||||
from redbot.core.utils.chat_formatting import escape, italics, pagify
|
from redbot.core.utils.chat_formatting import escape, italics, pagify
|
||||||
|
|
||||||
_ = Translator("General", __file__)
|
_ = Translator("General", __file__)
|
||||||
@@ -164,7 +163,9 @@ class General:
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
async def lmgtfy(self, ctx, *, search_terms: str):
|
async def lmgtfy(self, ctx, *, search_terms: str):
|
||||||
"""Creates a lmgtfy link"""
|
"""Creates a lmgtfy link"""
|
||||||
search_terms = escape(search_terms.replace(" ", "+"), mass_mentions=True)
|
search_terms = escape(
|
||||||
|
search_terms.replace("+", "%2B").replace(" ", "+"), mass_mentions=True
|
||||||
|
)
|
||||||
await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))
|
await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
@commands.command(hidden=True)
|
||||||
@@ -224,49 +225,89 @@ class General:
|
|||||||
await ctx.send(_("I need the `Embed links` permission to send this."))
|
await ctx.send(_("I need the `Embed links` permission to send this."))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def urban(self, ctx, *, search_terms: str, definition_number: int = 1):
|
async def urban(self, ctx, *, word):
|
||||||
"""Urban Dictionary search
|
"""Searches urban dictionary entries using the unofficial api"""
|
||||||
|
|
||||||
Definition number must be between 1 and 10"""
|
|
||||||
|
|
||||||
def encode(s):
|
|
||||||
return quote_plus(s, encoding="utf-8", errors="replace")
|
|
||||||
|
|
||||||
# definition_number is just there to show up in the help
|
|
||||||
# all this mess is to avoid forcing double quotes on the user
|
|
||||||
|
|
||||||
search_terms = search_terms.split(" ")
|
|
||||||
try:
|
try:
|
||||||
if len(search_terms) > 1:
|
url = "https://api.urbandictionary.com/v0/define?term=" + str(word).lower()
|
||||||
pos = int(search_terms[-1]) - 1
|
|
||||||
search_terms = search_terms[:-1]
|
headers = {"content-type": "application/json"}
|
||||||
else:
|
|
||||||
pos = 0
|
|
||||||
if pos not in range(0, 11): # API only provides the
|
|
||||||
pos = 0 # top 10 definitions
|
|
||||||
except ValueError:
|
|
||||||
pos = 0
|
|
||||||
|
|
||||||
search_terms = {"term": "+".join([s for s in search_terms])}
|
|
||||||
url = "http://api.urbandictionary.com/v0/define"
|
|
||||||
try:
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url, params=search_terms) as r:
|
async with session.get(url, headers=headers) as response:
|
||||||
result = await r.json()
|
data = await response.json()
|
||||||
item_list = result["list"]
|
|
||||||
if item_list:
|
|
||||||
definition = item_list[pos]["definition"]
|
|
||||||
example = item_list[pos]["example"]
|
|
||||||
defs = len(item_list)
|
|
||||||
msg = "**Definition #{} out of {}:\n**{}\n\n**Example:\n**{}".format(
|
|
||||||
pos + 1, defs, definition, example
|
|
||||||
)
|
|
||||||
msg = pagify(msg, ["\n"])
|
|
||||||
for page in msg:
|
|
||||||
await ctx.send(page)
|
|
||||||
else:
|
|
||||||
await ctx.send(_("Your search terms gave no results."))
|
|
||||||
except IndexError:
|
|
||||||
await ctx.send(_("There is no definition #{}").format(pos + 1))
|
|
||||||
except:
|
except:
|
||||||
await ctx.send(_("Error."))
|
await ctx.send(
|
||||||
|
_(("No Urban dictionary entries were found or there was an error in the process"))
|
||||||
|
)
|
||||||
|
|
||||||
|
if data.get("error") != 404:
|
||||||
|
|
||||||
|
if await ctx.embed_requested():
|
||||||
|
# a list of embeds
|
||||||
|
embeds = []
|
||||||
|
for ud in data["list"]:
|
||||||
|
embed = discord.Embed()
|
||||||
|
embed.title = _("{} by {}".format(ud["word"].capitalize(), ud["author"]))
|
||||||
|
embed.url = ud["permalink"]
|
||||||
|
|
||||||
|
description = "{} \n \n **Example : ** {}".format(
|
||||||
|
ud["definition"], ud.get("example", "N/A")
|
||||||
|
)
|
||||||
|
if len(description) > 2048:
|
||||||
|
description = "{}...".format(description[:2045])
|
||||||
|
embed.description = _(description)
|
||||||
|
|
||||||
|
embed.set_footer(
|
||||||
|
text=_(
|
||||||
|
"{} Down / {} Up , Powered by urban dictionary".format(
|
||||||
|
ud["thumbs_down"], ud["thumbs_up"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
embeds.append(embed)
|
||||||
|
|
||||||
|
if embeds is not None and len(embeds) > 0:
|
||||||
|
await menu(
|
||||||
|
ctx,
|
||||||
|
pages=embeds,
|
||||||
|
controls=DEFAULT_CONTROLS,
|
||||||
|
message=None,
|
||||||
|
page=0,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages = []
|
||||||
|
for ud in data["list"]:
|
||||||
|
description = "{} \n \n **Example : ** {}".format(
|
||||||
|
ud["definition"], ud.get("example", "N/A")
|
||||||
|
)
|
||||||
|
if len(description) > 2048:
|
||||||
|
description = "{}...".format(description[:2045])
|
||||||
|
description = _(description)
|
||||||
|
|
||||||
|
message = "<{}> \n {} by {} \n \n {} \n \n {} Down / {} Up , Powered by urban dictionary".format(
|
||||||
|
ud["permalink"],
|
||||||
|
ud["word"].capitalize(),
|
||||||
|
ud["author"],
|
||||||
|
description,
|
||||||
|
ud["thumbs_down"],
|
||||||
|
ud["thumbs_up"],
|
||||||
|
)
|
||||||
|
messages.append(message)
|
||||||
|
|
||||||
|
if messages is not None and len(messages) > 0:
|
||||||
|
await menu(
|
||||||
|
ctx,
|
||||||
|
pages=messages,
|
||||||
|
controls=DEFAULT_CONTROLS,
|
||||||
|
message=None,
|
||||||
|
page=0,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await ctx.send(
|
||||||
|
_(("No Urban dictionary entries were found or there was an error in the process"))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|||||||
@@ -1266,7 +1266,14 @@ class Mod:
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def userinfo(self, ctx, *, user: discord.Member = None):
|
async def userinfo(self, ctx, *, user: discord.Member = None):
|
||||||
"""Shows users's informations"""
|
"""Shows information for a user.
|
||||||
|
|
||||||
|
This includes fields for status, discord join date, server
|
||||||
|
join date, voice state and previous names/nicknames.
|
||||||
|
|
||||||
|
If the user has none of roles, previous names or previous
|
||||||
|
nicknames, these fields will be omitted.
|
||||||
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
@@ -1306,12 +1313,13 @@ class Mod:
|
|||||||
if roles:
|
if roles:
|
||||||
roles = ", ".join([x.name for x in roles])
|
roles = ", ".join([x.name for x in roles])
|
||||||
else:
|
else:
|
||||||
roles = _("None")
|
roles = None
|
||||||
|
|
||||||
data = discord.Embed(description=activity, colour=user.colour)
|
data = discord.Embed(description=activity, colour=user.colour)
|
||||||
data.add_field(name=_("Joined Discord on"), value=created_on)
|
data.add_field(name=_("Joined Discord on"), value=created_on)
|
||||||
data.add_field(name=_("Joined this server on"), value=joined_on)
|
data.add_field(name=_("Joined this server on"), value=joined_on)
|
||||||
data.add_field(name=_("Roles"), value=roles, inline=False)
|
if roles is not None:
|
||||||
|
data.add_field(name=_("Roles"), value=roles, inline=False)
|
||||||
if names:
|
if names:
|
||||||
data.add_field(name=_("Previous Names"), value=", ".join(names), inline=False)
|
data.add_field(name=_("Previous Names"), value=", ".join(names), inline=False)
|
||||||
if nicks:
|
if nicks:
|
||||||
|
|||||||
@@ -95,19 +95,29 @@ class ModLog:
|
|||||||
await ctx.send(_("That case does not exist for that server"))
|
await ctx.send(_("That case does not exist for that server"))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
await ctx.send(embed=await case.get_case_msg_content())
|
if await ctx.embed_requested():
|
||||||
|
await ctx.send(embed=await case.message_content(embed=True))
|
||||||
|
else:
|
||||||
|
await ctx.send(await case.message_content(embed=False))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command(usage="[case] <reason>")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def reason(self, ctx: commands.Context, case: int, *, reason: str = ""):
|
async def reason(self, ctx: commands.Context, *, reason: str):
|
||||||
"""Lets you specify a reason for mod-log's cases
|
"""Lets you specify a reason for mod-log's cases
|
||||||
|
|
||||||
Please note that you can only edit cases you are
|
Please note that you can only edit cases you are
|
||||||
the owner of unless you are a mod/admin or the server owner"""
|
the owner of unless you are a mod/admin or the server owner.
|
||||||
|
|
||||||
|
If no number is specified, the latest case will be used."""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if not reason:
|
potential_case = reason.split()[0]
|
||||||
await ctx.send_help()
|
if potential_case.isdigit():
|
||||||
return
|
case = int(potential_case)
|
||||||
|
reason = reason.replace(potential_case, "")
|
||||||
|
else:
|
||||||
|
case = str(int(await modlog.get_next_case_number(guild)) - 1)
|
||||||
|
# latest case
|
||||||
try:
|
try:
|
||||||
case_before = await modlog.get_case(case, guild, self.bot)
|
case_before = await modlog.get_case(case, guild, self.bot)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ class Permissions:
|
|||||||
stil_valid = [
|
stil_valid = [
|
||||||
(k, v) for k, v in self.cache.items() if not any(obj in k for obj in to_invalidate)
|
(k, v) for k, v in self.cache.items() if not any(obj in k for obj in to_invalidate)
|
||||||
]
|
]
|
||||||
self.cache = LRUDict(*stil_valid, size=self.cache.size)
|
self.cache = LRUDict(stil_valid, size=self.cache.size)
|
||||||
|
|
||||||
def find_object_uniquely(self, info: str) -> int:
|
def find_object_uniquely(self, info: str) -> int:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ class Streams:
|
|||||||
|
|
||||||
@streamalert.command(name="list")
|
@streamalert.command(name="list")
|
||||||
async def streamalert_list(self, ctx: commands.Context):
|
async def streamalert_list(self, ctx: commands.Context):
|
||||||
|
"""List all active stream alerts in this server."""
|
||||||
streams_list = defaultdict(list)
|
streams_list = defaultdict(list)
|
||||||
guild_channels_ids = [c.id for c in ctx.guild.channels]
|
guild_channels_ids = [c.id for c in ctx.guild.channels]
|
||||||
msg = _("Active alerts:\n\n")
|
msg = _("Active alerts:\n\n")
|
||||||
@@ -608,16 +609,12 @@ class Streams:
|
|||||||
chn = self.bot.get_channel(raw_msg["channel"])
|
chn = self.bot.get_channel(raw_msg["channel"])
|
||||||
msg = await chn.get_message(raw_msg["message"])
|
msg = await chn.get_message(raw_msg["message"])
|
||||||
raw_stream["_messages_cache"].append(msg)
|
raw_stream["_messages_cache"].append(msg)
|
||||||
token = await self.db.tokens.get_raw(_class.__name__)
|
token = await self.db.tokens.get_raw(_class.__name__, default=None)
|
||||||
streams.append(_class(token=token, **raw_stream))
|
if token is not None:
|
||||||
|
raw_stream["token"] = token
|
||||||
|
streams.append(_class(**raw_stream))
|
||||||
|
|
||||||
# issue 1191 extended resolution: Remove this after suitable period
|
return streams
|
||||||
# Fast dedupe below
|
|
||||||
seen = set()
|
|
||||||
seen_add = seen.add
|
|
||||||
return [x for x in streams if not (x.name.lower() in seen or seen_add(x.name.lower()))]
|
|
||||||
|
|
||||||
# return streams
|
|
||||||
|
|
||||||
async def load_communities(self):
|
async def load_communities(self):
|
||||||
communities = []
|
communities = []
|
||||||
|
|||||||
@@ -36,5 +36,5 @@ class VersionInfo:
|
|||||||
return [self.major, self.minor, self.micro, self.releaselevel, self.serial]
|
return [self.major, self.minor, self.micro, self.releaselevel, self.serial]
|
||||||
|
|
||||||
|
|
||||||
__version__ = "3.0.0b17"
|
__version__ = "3.0.0b19"
|
||||||
version_info = VersionInfo(3, 0, 0, "beta", 17)
|
version_info = VersionInfo(3, 0, 0, "beta", 19)
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ async def set_balance(member: discord.Member, amount: int) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def _invalid_amount(amount: int) -> bool:
|
def _invalid_amount(amount: int) -> bool:
|
||||||
return amount <= 0
|
return amount < 0
|
||||||
|
|
||||||
|
|
||||||
async def withdraw_credits(member: discord.Member, amount: int) -> int:
|
async def withdraw_credits(member: discord.Member, amount: int) -> int:
|
||||||
@@ -214,10 +214,14 @@ async def withdraw_credits(member: discord.Member, amount: int) -> int:
|
|||||||
ValueError
|
ValueError
|
||||||
If the withdrawal amount is invalid or if the account has insufficient
|
If the withdrawal amount is invalid or if the account has insufficient
|
||||||
funds.
|
funds.
|
||||||
|
TypeError
|
||||||
|
If the withdrawal amount is not an `int`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(amount, int):
|
||||||
|
raise TypeError("Withdrawal amount must be of type int, not {}.".format(type(amount)))
|
||||||
if _invalid_amount(amount):
|
if _invalid_amount(amount):
|
||||||
raise ValueError("Invalid withdrawal amount {} <= 0".format(amount))
|
raise ValueError("Invalid withdrawal amount {} < 0".format(amount))
|
||||||
|
|
||||||
bal = await get_balance(member)
|
bal = await get_balance(member)
|
||||||
if amount > bal:
|
if amount > bal:
|
||||||
@@ -245,8 +249,12 @@ async def deposit_credits(member: discord.Member, amount: int) -> int:
|
|||||||
------
|
------
|
||||||
ValueError
|
ValueError
|
||||||
If the deposit amount is invalid.
|
If the deposit amount is invalid.
|
||||||
|
TypeError
|
||||||
|
If the deposit amount is not an `int`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(amount, int):
|
||||||
|
raise TypeError("Deposit amount must be of type int, not {}.".format(type(amount)))
|
||||||
if _invalid_amount(amount):
|
if _invalid_amount(amount):
|
||||||
raise ValueError("Invalid deposit amount {} <= 0".format(amount))
|
raise ValueError("Invalid deposit amount {} <= 0".format(amount))
|
||||||
|
|
||||||
@@ -269,14 +277,18 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
int
|
int
|
||||||
The new balance.
|
The new balance of the member gaining credits.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
ValueError
|
ValueError
|
||||||
If the amount is invalid or if ``from_`` has insufficient funds.
|
If the amount is invalid or if ``from_`` has insufficient funds.
|
||||||
|
TypeError
|
||||||
|
If the amount is not an `int`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(amount, int):
|
||||||
|
raise TypeError("Transfer amount must be of type int, not {}.".format(type(amount)))
|
||||||
if _invalid_amount(amount):
|
if _invalid_amount(amount):
|
||||||
raise ValueError("Invalid transfer amount {} <= 0".format(amount))
|
raise ValueError("Invalid transfer amount {} <= 0".format(amount))
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ from .help_formatter import Help, help as help_
|
|||||||
from .sentry import SentryManager
|
from .sentry import SentryManager
|
||||||
|
|
||||||
|
|
||||||
|
def _is_submodule(parent, child):
|
||||||
|
return parent == child or child.startswith(parent + ".")
|
||||||
|
|
||||||
|
|
||||||
class RedBase(BotBase, RPCMixin):
|
class RedBase(BotBase, RPCMixin):
|
||||||
"""Mixin for the main bot class.
|
"""Mixin for the main bot class.
|
||||||
|
|
||||||
@@ -211,12 +215,12 @@ class RedBase(BotBase, RPCMixin):
|
|||||||
async def load_extension(self, spec: ModuleSpec):
|
async def load_extension(self, spec: ModuleSpec):
|
||||||
name = spec.name.split(".")[-1]
|
name = spec.name.split(".")[-1]
|
||||||
if name in self.extensions:
|
if name in self.extensions:
|
||||||
return
|
raise discord.ClientException(f"there is already a package named {name} loaded")
|
||||||
|
|
||||||
lib = spec.loader.load_module()
|
lib = spec.loader.load_module()
|
||||||
if not hasattr(lib, "setup"):
|
if not hasattr(lib, "setup"):
|
||||||
del lib
|
del lib
|
||||||
raise discord.ClientException("extension does not have a setup function")
|
raise discord.ClientException(f"extension {name} does not have a setup function")
|
||||||
|
|
||||||
if asyncio.iscoroutinefunction(lib.setup):
|
if asyncio.iscoroutinefunction(lib.setup):
|
||||||
await lib.setup(self)
|
await lib.setup(self)
|
||||||
@@ -225,44 +229,41 @@ class RedBase(BotBase, RPCMixin):
|
|||||||
|
|
||||||
self.extensions[name] = lib
|
self.extensions[name] = lib
|
||||||
|
|
||||||
|
def remove_cog(self, cogname):
|
||||||
|
super().remove_cog(cogname)
|
||||||
|
|
||||||
|
for meth in self.rpc_handlers.pop(cogname.upper(), ()):
|
||||||
|
self.unregister_rpc_handler(meth)
|
||||||
|
|
||||||
def unload_extension(self, name):
|
def unload_extension(self, name):
|
||||||
lib = self.extensions.get(name)
|
lib = self.extensions.get(name)
|
||||||
|
|
||||||
if lib is None:
|
if lib is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
lib_name = lib.__name__ # Thank you
|
lib_name = lib.__name__ # Thank you
|
||||||
|
|
||||||
# find all references to the module
|
# find all references to the module
|
||||||
cog_names = []
|
|
||||||
|
|
||||||
# remove the cogs registered from the module
|
# remove the cogs registered from the module
|
||||||
for cogname, cog in self.cogs.copy().items():
|
for cogname, cog in self.cogs.copy().items():
|
||||||
if cog.__module__.startswith(lib_name):
|
if cog.__module__ and _is_submodule(lib_name, cog.__module__):
|
||||||
self.remove_cog(cogname)
|
self.remove_cog(cogname)
|
||||||
|
|
||||||
cog_names.append(cogname)
|
|
||||||
|
|
||||||
# remove all rpc handlers
|
|
||||||
for cogname in cog_names:
|
|
||||||
if cogname.upper() in self.rpc_handlers:
|
|
||||||
methods = self.rpc_handlers[cogname]
|
|
||||||
for meth in methods:
|
|
||||||
self.unregister_rpc_handler(meth)
|
|
||||||
|
|
||||||
del self.rpc_handlers[cogname]
|
|
||||||
|
|
||||||
# first remove all the commands from the module
|
# first remove all the commands from the module
|
||||||
for cmd in self.all_commands.copy().values():
|
for cmd in self.all_commands.copy().values():
|
||||||
if cmd.module.startswith(lib_name):
|
if cmd.module and _is_submodule(lib_name, cmd.module):
|
||||||
if isinstance(cmd, GroupMixin):
|
if isinstance(cmd, GroupMixin):
|
||||||
cmd.recursively_remove_all_commands()
|
cmd.recursively_remove_all_commands()
|
||||||
|
|
||||||
self.remove_command(cmd.name)
|
self.remove_command(cmd.name)
|
||||||
|
|
||||||
# then remove all the listeners from the module
|
# then remove all the listeners from the module
|
||||||
for event_list in self.extra_events.copy().values():
|
for event_list in self.extra_events.copy().values():
|
||||||
remove = []
|
remove = []
|
||||||
|
|
||||||
for index, event in enumerate(event_list):
|
for index, event in enumerate(event_list):
|
||||||
if event.__module__.startswith(lib_name):
|
if event.__module__ and _is_submodule(lib_name, event.__module__):
|
||||||
remove.append(index)
|
remove.append(index)
|
||||||
|
|
||||||
for index in reversed(remove):
|
for index in reversed(remove):
|
||||||
@@ -282,11 +283,12 @@ class RedBase(BotBase, RPCMixin):
|
|||||||
pkg_name = lib.__package__
|
pkg_name = lib.__package__
|
||||||
del lib
|
del lib
|
||||||
del self.extensions[name]
|
del self.extensions[name]
|
||||||
for m, _ in sys.modules.copy().items():
|
|
||||||
if m.startswith(pkg_name):
|
|
||||||
del sys.modules[m]
|
|
||||||
|
|
||||||
if pkg_name.startswith("redbot.cogs"):
|
for module in list(sys.modules):
|
||||||
|
if _is_submodule(lib_name, module):
|
||||||
|
del sys.modules[module]
|
||||||
|
|
||||||
|
if pkg_name.startswith("redbot.cogs."):
|
||||||
del sys.modules["redbot.cogs"].__dict__[name]
|
del sys.modules["redbot.cogs"].__dict__[name]
|
||||||
|
|
||||||
|
|
||||||
@@ -295,6 +297,13 @@ class Red(RedBase, discord.AutoShardedClient):
|
|||||||
You're welcome Caleb.
|
You're welcome Caleb.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
async def logout(self):
|
||||||
|
"""Logs out of Discord and closes all connections."""
|
||||||
|
if self._sentry_mgr:
|
||||||
|
await self._sentry_mgr.close()
|
||||||
|
|
||||||
|
await super().logout()
|
||||||
|
|
||||||
async def shutdown(self, *, restart: bool = False):
|
async def shutdown(self, *, restart: bool = False):
|
||||||
"""Gracefully quit Red.
|
"""Gracefully quit Red.
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,13 @@ class CogManager:
|
|||||||
bot directory.
|
bot directory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
CORE_PATH = Path(redbot.cogs.__path__[0])
|
||||||
|
|
||||||
def __init__(self, paths: Tuple[str] = ()):
|
def __init__(self, paths: Tuple[str] = ()):
|
||||||
self.conf = Config.get_conf(self, 2938473984732, True)
|
self.conf = Config.get_conf(self, 2938473984732, True)
|
||||||
tmp_cog_install_path = cog_data_path(self) / "cogs"
|
tmp_cog_install_path = cog_data_path(self) / "cogs"
|
||||||
tmp_cog_install_path.mkdir(parents=True, exist_ok=True)
|
tmp_cog_install_path.mkdir(parents=True, exist_ok=True)
|
||||||
self.conf.register_global(paths=(), install_path=str(tmp_cog_install_path))
|
self.conf.register_global(paths=[], install_path=str(tmp_cog_install_path))
|
||||||
|
|
||||||
self._paths = [Path(p) for p in paths]
|
self._paths = [Path(p) for p in paths]
|
||||||
|
|
||||||
async def paths(self) -> Tuple[Path, ...]:
|
async def paths(self) -> Tuple[Path, ...]:
|
||||||
@@ -54,18 +55,13 @@ class CogManager:
|
|||||||
"""
|
"""
|
||||||
conf_paths = [Path(p) for p in await self.conf.paths()]
|
conf_paths = [Path(p) for p in await self.conf.paths()]
|
||||||
other_paths = self._paths
|
other_paths = self._paths
|
||||||
core_paths = await self.core_paths()
|
|
||||||
|
|
||||||
all_paths = _deduplicate(list(conf_paths) + list(other_paths) + core_paths)
|
all_paths = _deduplicate(list(conf_paths) + list(other_paths) + [self.CORE_PATH])
|
||||||
|
|
||||||
if self.install_path not in all_paths:
|
if self.install_path not in all_paths:
|
||||||
all_paths.insert(0, await self.install_path())
|
all_paths.insert(0, await self.install_path())
|
||||||
return tuple(p.resolve() for p in all_paths if p.is_dir())
|
return tuple(p.resolve() for p in all_paths if p.is_dir())
|
||||||
|
|
||||||
async def core_paths(self) -> List[Path]:
|
|
||||||
core_paths = [Path(p) for p in redbot.cogs.__path__]
|
|
||||||
return core_paths
|
|
||||||
|
|
||||||
async def install_path(self) -> Path:
|
async def install_path(self) -> Path:
|
||||||
"""Get the install path for 3rd party cogs.
|
"""Get the install path for 3rd party cogs.
|
||||||
|
|
||||||
@@ -155,10 +151,12 @@ class CogManager:
|
|||||||
|
|
||||||
if path == await self.install_path():
|
if path == await self.install_path():
|
||||||
raise ValueError("Cannot add the install path as an additional path.")
|
raise ValueError("Cannot add the install path as an additional path.")
|
||||||
|
if path == self.CORE_PATH:
|
||||||
|
raise ValueError("Cannot add the core path as an additional path.")
|
||||||
|
|
||||||
all_paths = _deduplicate(await self.paths() + (path,))
|
async with self.conf.paths() as paths:
|
||||||
# noinspection PyTypeChecker
|
if not any(Path(p) == path for p in paths):
|
||||||
await self.set_paths(all_paths)
|
paths.append(str(path))
|
||||||
|
|
||||||
async def remove_path(self, path: Union[Path, str]) -> Tuple[Path, ...]:
|
async def remove_path(self, path: Union[Path, str]) -> Tuple[Path, ...]:
|
||||||
"""Remove a path from the current paths list.
|
"""Remove a path from the current paths list.
|
||||||
@@ -174,12 +172,14 @@ class CogManager:
|
|||||||
Tuple of new valid paths.
|
Tuple of new valid paths.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
path = self._ensure_path_obj(path)
|
path = self._ensure_path_obj(path).resolve()
|
||||||
all_paths = list(await self.paths())
|
|
||||||
if path in all_paths:
|
paths = [Path(p) for p in await self.conf.paths()]
|
||||||
all_paths.remove(path) # Modifies in place
|
if path in paths:
|
||||||
await self.set_paths(all_paths)
|
paths.remove(path)
|
||||||
return tuple(all_paths)
|
await self.set_paths(paths)
|
||||||
|
|
||||||
|
return tuple(paths)
|
||||||
|
|
||||||
async def set_paths(self, paths_: List[Path]):
|
async def set_paths(self, paths_: List[Path]):
|
||||||
"""Set the current paths list.
|
"""Set the current paths list.
|
||||||
@@ -213,9 +213,8 @@ class CogManager:
|
|||||||
When no matching spec can be found.
|
When no matching spec can be found.
|
||||||
"""
|
"""
|
||||||
resolved_paths = _deduplicate(await self.paths())
|
resolved_paths = _deduplicate(await self.paths())
|
||||||
core_paths = _deduplicate(await self.core_paths())
|
|
||||||
|
|
||||||
real_paths = [str(p) for p in resolved_paths if p not in core_paths]
|
real_paths = [str(p) for p in resolved_paths if p != self.CORE_PATH]
|
||||||
|
|
||||||
for finder, module_name, _ in pkgutil.iter_modules(real_paths):
|
for finder, module_name, _ in pkgutil.iter_modules(real_paths):
|
||||||
if name == module_name:
|
if name == module_name:
|
||||||
@@ -228,7 +227,8 @@ class CogManager:
|
|||||||
" in any available path.".format(name)
|
" in any available path.".format(name)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _find_core_cog(self, name: str) -> ModuleSpec:
|
@staticmethod
|
||||||
|
async def _find_core_cog(name: str) -> ModuleSpec:
|
||||||
"""
|
"""
|
||||||
Attempts to find a spec for a core cog.
|
Attempts to find a spec for a core cog.
|
||||||
|
|
||||||
@@ -310,7 +310,8 @@ _ = Translator("CogManagerUI", __file__)
|
|||||||
class CogManagerUI:
|
class CogManagerUI:
|
||||||
"""Commands to interface with Red's cog manager."""
|
"""Commands to interface with Red's cog manager."""
|
||||||
|
|
||||||
async def visible_paths(self, ctx):
|
@staticmethod
|
||||||
|
async def visible_paths(ctx):
|
||||||
install_path = await ctx.bot.cog_mgr.install_path()
|
install_path = await ctx.bot.cog_mgr.install_path()
|
||||||
cog_paths = await ctx.bot.cog_mgr.paths()
|
cog_paths = await ctx.bot.cog_mgr.paths()
|
||||||
cog_paths = [p for p in cog_paths if p != install_path]
|
cog_paths = [p for p in cog_paths if p != install_path]
|
||||||
@@ -322,11 +323,15 @@ class CogManagerUI:
|
|||||||
"""
|
"""
|
||||||
Lists current cog paths in order of priority.
|
Lists current cog paths in order of priority.
|
||||||
"""
|
"""
|
||||||
install_path = await ctx.bot.cog_mgr.install_path()
|
cog_mgr = ctx.bot.cog_mgr
|
||||||
cog_paths = await ctx.bot.cog_mgr.paths()
|
install_path = await cog_mgr.install_path()
|
||||||
cog_paths = [p for p in cog_paths if p != install_path]
|
core_path = cog_mgr.CORE_PATH
|
||||||
|
cog_paths = await cog_mgr.paths()
|
||||||
|
cog_paths = [p for p in cog_paths if p not in (install_path, core_path)]
|
||||||
|
|
||||||
msg = _("Install Path: {}\n\n").format(install_path)
|
msg = _("Install Path: {install_path}\nCore Path: {core_path}\n\n").format(
|
||||||
|
install_path=install_path, core_path=core_path
|
||||||
|
)
|
||||||
|
|
||||||
partial = []
|
partial = []
|
||||||
for i, p in enumerate(cog_paths, start=1):
|
for i, p in enumerate(cog_paths, start=1):
|
||||||
@@ -428,16 +433,16 @@ class CogManagerUI:
|
|||||||
"""
|
"""
|
||||||
loaded = set(ctx.bot.extensions.keys())
|
loaded = set(ctx.bot.extensions.keys())
|
||||||
|
|
||||||
all = set(await ctx.bot.cog_mgr.available_modules())
|
all_cogs = set(await ctx.bot.cog_mgr.available_modules())
|
||||||
|
|
||||||
unloaded = all - loaded
|
unloaded = all_cogs - loaded
|
||||||
|
|
||||||
loaded = sorted(list(loaded), key=str.lower)
|
loaded = sorted(list(loaded), key=str.lower)
|
||||||
unloaded = sorted(list(unloaded), key=str.lower)
|
unloaded = sorted(list(unloaded), key=str.lower)
|
||||||
|
|
||||||
if await ctx.embed_requested():
|
if await ctx.embed_requested():
|
||||||
loaded = ("**{} loaded:**\n").format(len(loaded)) + ", ".join(loaded)
|
loaded = _("**{} loaded:**\n").format(len(loaded)) + ", ".join(loaded)
|
||||||
unloaded = ("**{} unloaded:**\n").format(len(unloaded)) + ", ".join(unloaded)
|
unloaded = _("**{} unloaded:**\n").format(len(unloaded)) + ", ".join(unloaded)
|
||||||
|
|
||||||
for page in pagify(loaded, delims=[", ", "\n"], page_length=1800):
|
for page in pagify(loaded, delims=[", ", "\n"], page_length=1800):
|
||||||
e = discord.Embed(description=page, colour=discord.Colour.dark_green())
|
e = discord.Embed(description=page, colour=discord.Colour.dark_green())
|
||||||
@@ -447,9 +452,9 @@ class CogManagerUI:
|
|||||||
e = discord.Embed(description=page, colour=discord.Colour.dark_red())
|
e = discord.Embed(description=page, colour=discord.Colour.dark_red())
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
else:
|
else:
|
||||||
loaded_count = "**{} loaded:**\n".format(len(loaded))
|
loaded_count = _("**{} loaded:**\n").format(len(loaded))
|
||||||
loaded = ", ".join(loaded)
|
loaded = ", ".join(loaded)
|
||||||
unloaded_count = "**{} unloaded:**\n".format(len(unloaded))
|
unloaded_count = _("**{} unloaded:**\n").format(len(unloaded))
|
||||||
unloaded = ", ".join(unloaded)
|
unloaded = ", ".join(unloaded)
|
||||||
loaded_count_sent = False
|
loaded_count_sent = False
|
||||||
unloaded_count_sent = False
|
unloaded_count_sent = False
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class Dev:
|
|||||||
author - command author's member object
|
author - command author's member object
|
||||||
message - the command's message object
|
message - the command's message object
|
||||||
discord - discord.py library
|
discord - discord.py library
|
||||||
commands - discord.py commands extension
|
commands - redbot.core.commands
|
||||||
_ - The result of the last dev command.
|
_ - The result of the last dev command.
|
||||||
"""
|
"""
|
||||||
env = {
|
env = {
|
||||||
@@ -138,7 +138,7 @@ class Dev:
|
|||||||
author - command author's member object
|
author - command author's member object
|
||||||
message - the command's message object
|
message - the command's message object
|
||||||
discord - discord.py library
|
discord - discord.py library
|
||||||
commands - discord.py commands extension
|
commands - redbot.core.commands
|
||||||
_ - The result of the last dev command.
|
_ - The result of the last dev command.
|
||||||
"""
|
"""
|
||||||
env = {
|
env = {
|
||||||
|
|||||||
@@ -407,20 +407,24 @@ async def help(ctx, *cmds: str):
|
|||||||
max_pages_in_guild = await ctx.bot.db.help.max_pages_in_guild()
|
max_pages_in_guild = await ctx.bot.db.help.max_pages_in_guild()
|
||||||
if len(embeds) > max_pages_in_guild:
|
if len(embeds) > max_pages_in_guild:
|
||||||
destination = ctx.author
|
destination = ctx.author
|
||||||
|
try:
|
||||||
for embed in embeds:
|
for embed in embeds:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
try:
|
try:
|
||||||
await destination.send(embed=embed)
|
await destination.send(embed=embed)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
destination = ctx.author
|
destination = ctx.author
|
||||||
await destination.send(embed=embed)
|
await destination.send(embed=embed)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
await destination.send(embed)
|
await destination.send(embed)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
destination = ctx.author
|
destination = ctx.author
|
||||||
await destination.send(embed)
|
await destination.send(embed)
|
||||||
|
except discord.Forbidden:
|
||||||
|
await ctx.channel.send(
|
||||||
|
"I couldn't send the help message to you in DM. Either you blocked me or you disabled DMs in this server."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@help.error
|
@help.error
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
log = logging.getLogger("red")
|
log = logging.getLogger("red")
|
||||||
|
|
||||||
PRETTY = {"indent": 4, "sort_keys": True, "separators": (",", " : ")}
|
PRETTY = {"indent": 4, "sort_keys": False, "separators": (",", " : ")}
|
||||||
MINIFIED = {"sort_keys": True, "separators": (",", ":")}
|
MINIFIED = {"sort_keys": False, "separators": (",", ":")}
|
||||||
|
|
||||||
|
|
||||||
class JsonIO:
|
class JsonIO:
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class RPCMixin:
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.rpc = RPC()
|
self.rpc = RPC()
|
||||||
|
|
||||||
self.rpc_handlers = {} # Lowered cog name to method
|
self.rpc_handlers = {} # Uppercase cog name to method
|
||||||
|
|
||||||
def register_rpc_handler(self, method):
|
def register_rpc_handler(self, method):
|
||||||
"""
|
"""
|
||||||
@@ -132,6 +132,7 @@ class RPCMixin:
|
|||||||
self.rpc.add_method(method)
|
self.rpc.add_method(method)
|
||||||
|
|
||||||
cog_name = method.__self__.__class__.__name__.upper()
|
cog_name = method.__self__.__class__.__name__.upper()
|
||||||
|
|
||||||
if cog_name not in self.rpc_handlers:
|
if cog_name not in self.rpc_handlers:
|
||||||
self.rpc_handlers[cog_name] = []
|
self.rpc_handlers[cog_name] = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from raven import Client
|
from raven import Client
|
||||||
from raven.handlers.logging import SentryHandler
|
from raven.handlers.logging import SentryHandler
|
||||||
|
from raven_aiohttp import AioHttpTransport
|
||||||
|
|
||||||
from redbot.core import __version__
|
from redbot.core import __version__
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ class SentryManager:
|
|||||||
release=__version__,
|
release=__version__,
|
||||||
include_paths=["redbot"],
|
include_paths=["redbot"],
|
||||||
enable_breadcrumbs=False,
|
enable_breadcrumbs=False,
|
||||||
|
transport=AioHttpTransport,
|
||||||
)
|
)
|
||||||
self.handler = SentryHandler(self.client)
|
self.handler = SentryHandler(self.client)
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
@@ -30,3 +33,9 @@ class SentryManager:
|
|||||||
def disable(self):
|
def disable(self):
|
||||||
"""Disable error reporting for Sentry."""
|
"""Disable error reporting for Sentry."""
|
||||||
self.logger.removeHandler(self.handler)
|
self.logger.removeHandler(self.handler)
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.create_task(self.close())
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
"""Wait for the Sentry client to send pending messages and shut down."""
|
||||||
|
await self.client.remote.get_transport().close()
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ pyyaml==3.12
|
|||||||
fuzzywuzzy[speedup]<=0.16.0
|
fuzzywuzzy[speedup]<=0.16.0
|
||||||
Red-Trivia>=1.1.1
|
Red-Trivia>=1.1.1
|
||||||
async-timeout<3.0.0
|
async-timeout<3.0.0
|
||||||
|
raven-aiohttp==0.7.0
|
||||||
2
setup.py
2
setup.py
@@ -135,7 +135,7 @@ setup(
|
|||||||
"test": ["pytest>3", "pytest-asyncio"],
|
"test": ["pytest>3", "pytest-asyncio"],
|
||||||
"mongo": ["motor"],
|
"mongo": ["motor"],
|
||||||
"docs": ["sphinx>=1.7", "sphinxcontrib-asyncio", "sphinx_rtd_theme"],
|
"docs": ["sphinx>=1.7", "sphinxcontrib-asyncio", "sphinx_rtd_theme"],
|
||||||
"voice": ["red-lavalink>=0.0.4"],
|
"voice": ["red-lavalink==0.1.0"],
|
||||||
"style": ["black==18.5b1"],
|
"style": ["black==18.5b1"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -69,3 +69,15 @@ async def test_set_default_balance(bank, guild_factory):
|
|||||||
await bank.set_default_balance(500, guild)
|
await bank.set_default_balance(500, guild)
|
||||||
default_bal = await bank.get_default_balance(guild)
|
default_bal = await bank.get_default_balance(guild)
|
||||||
assert default_bal == 500
|
assert default_bal == 500
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_nonint_transaction_amount(bank, member_factory):
|
||||||
|
mbr1 = member_factory.get()
|
||||||
|
mbr2 = member_factory.get()
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
await bank.deposit_credits(mbr1, 1.0)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
await bank.withdraw_credits(mbr1, 1.0)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
await bank.transfer_credits(mbr1, mbr2, 1.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user