Browse Source

ajout de la base

master
Geekcat 2 years ago
parent
commit
cada8819f6
66 changed files with 11725 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. BIN
      cogs/__pycache__/Calc.cpython-35.pyc
  3. BIN
      cogs/__pycache__/alias.cpython-35.pyc
  4. BIN
      cogs/__pycache__/ascii.cpython-35.pyc
  5. BIN
      cogs/__pycache__/audio.cpython-35.pyc
  6. BIN
      cogs/__pycache__/cat.cpython-35.pyc
  7. BIN
      cogs/__pycache__/customcom.cpython-35.pyc
  8. BIN
      cogs/__pycache__/downloader.cpython-35.pyc
  9. BIN
      cogs/__pycache__/economy.cpython-35.pyc
  10. BIN
      cogs/__pycache__/funny.cpython-35.pyc
  11. BIN
      cogs/__pycache__/general.cpython-35.pyc
  12. BIN
      cogs/__pycache__/hentai.cpython-35.pyc
  13. BIN
      cogs/__pycache__/image.cpython-35.pyc
  14. BIN
      cogs/__pycache__/mod.cpython-35.pyc
  15. BIN
      cogs/__pycache__/nsfw.cpython-35.pyc
  16. BIN
      cogs/__pycache__/oboobs.cpython-35.pyc
  17. BIN
      cogs/__pycache__/owner.cpython-35.pyc
  18. BIN
      cogs/__pycache__/reddit.cpython-35.pyc
  19. BIN
      cogs/__pycache__/remindme.cpython-35.pyc
  20. BIN
      cogs/__pycache__/steam.cpython-35.pyc
  21. BIN
      cogs/__pycache__/streams.cpython-35.pyc
  22. BIN
      cogs/__pycache__/text.cpython-35.pyc
  23. BIN
      cogs/__pycache__/torrent.cpython-35.pyc
  24. BIN
      cogs/__pycache__/trivia.cpython-35.pyc
  25. BIN
      cogs/__pycache__/wiiutils.cpython-35.pyc
  26. BIN
      cogs/__pycache__/wikipedia.cpython-35.pyc
  27. BIN
      cogs/__pycache__/yandere.cpython-35.pyc
  28. BIN
      cogs/__pycache__/youtube.cpython-35.pyc
  29. +191
    -0
      cogs/alias.py
  30. +32
    -0
      cogs/ascii.py
  31. +2554
    -0
      cogs/audio.py
  32. +33
    -0
      cogs/cat.py
  33. +197
    -0
      cogs/customcom.py
  34. +709
    -0
      cogs/downloader.py
  35. +739
    -0
      cogs/economy.py
  36. +58
    -0
      cogs/funny.py
  37. +433
    -0
      cogs/general.py
  38. +183
    -0
      cogs/image.py
  39. +1728
    -0
      cogs/mod.py
  40. +262
    -0
      cogs/nsfw.py
  41. +271
    -0
      cogs/oboobs.py
  42. +1091
    -0
      cogs/owner.py
  43. +106
    -0
      cogs/remindme.py
  44. +167
    -0
      cogs/steam.py
  45. +692
    -0
      cogs/streams.py
  46. +78
    -0
      cogs/text.py
  47. +332
    -0
      cogs/trivia.py
  48. +0
    -0
      cogs/utils/__init__.py
  49. BIN
      cogs/utils/__pycache__/__init__.cpython-35.pyc
  50. BIN
      cogs/utils/__pycache__/chat_formatting.cpython-35.pyc
  51. BIN
      cogs/utils/__pycache__/checks.cpython-35.pyc
  52. BIN
      cogs/utils/__pycache__/converters.cpython-35.pyc
  53. BIN
      cogs/utils/__pycache__/dataIO.cpython-35.pyc
  54. BIN
      cogs/utils/__pycache__/settings.cpython-35.pyc
  55. +80
    -0
      cogs/utils/chat_formatting.py
  56. +89
    -0
      cogs/utils/checks.py
  57. +46
    -0
      cogs/utils/converters.py
  58. +79
    -0
      cogs/utils/dataIO.py
  59. +299
    -0
      cogs/utils/settings.py
  60. +46
    -0
      cogs/wiiutils.py
  61. +66
    -0
      cogs/wikipedia.py
  62. +37
    -0
      cogs/youtube.py
  63. +622
    -0
      kitty.py
  64. +495
    -0
      launcher.py
  65. +7
    -0
      requirements.txt
  66. +2
    -0
      start_launcher.sh

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
data/*

BIN
cogs/__pycache__/Calc.cpython-35.pyc View File


BIN
cogs/__pycache__/alias.cpython-35.pyc View File


BIN
cogs/__pycache__/ascii.cpython-35.pyc View File


BIN
cogs/__pycache__/audio.cpython-35.pyc View File


BIN
cogs/__pycache__/cat.cpython-35.pyc View File


BIN
cogs/__pycache__/customcom.cpython-35.pyc View File


BIN
cogs/__pycache__/downloader.cpython-35.pyc View File


BIN
cogs/__pycache__/economy.cpython-35.pyc View File


BIN
cogs/__pycache__/funny.cpython-35.pyc View File


BIN
cogs/__pycache__/general.cpython-35.pyc View File


BIN
cogs/__pycache__/hentai.cpython-35.pyc View File


BIN
cogs/__pycache__/image.cpython-35.pyc View File


BIN
cogs/__pycache__/mod.cpython-35.pyc View File


BIN
cogs/__pycache__/nsfw.cpython-35.pyc View File


BIN
cogs/__pycache__/oboobs.cpython-35.pyc View File


BIN
cogs/__pycache__/owner.cpython-35.pyc View File


BIN
cogs/__pycache__/reddit.cpython-35.pyc View File


BIN
cogs/__pycache__/remindme.cpython-35.pyc View File


BIN
cogs/__pycache__/steam.cpython-35.pyc View File


BIN
cogs/__pycache__/streams.cpython-35.pyc View File


BIN
cogs/__pycache__/text.cpython-35.pyc View File


BIN
cogs/__pycache__/torrent.cpython-35.pyc View File


BIN
cogs/__pycache__/trivia.cpython-35.pyc View File


BIN
cogs/__pycache__/wiiutils.cpython-35.pyc View File


BIN
cogs/__pycache__/wikipedia.cpython-35.pyc View File


BIN
cogs/__pycache__/yandere.cpython-35.pyc View File


BIN
cogs/__pycache__/youtube.cpython-35.pyc View File


+ 191
- 0
cogs/alias.py View File

@ -0,0 +1,191 @@
from discord.ext import commands
from .utils.chat_formatting import box
from .utils.dataIO import dataIO
from .utils import checks
from __main__ import user_allowed, send_cmd_help
from copy import copy
import os
import discord
class Alias:
def __init__(self, bot):
self.bot = bot
self.file_path = "data/alias/aliases.json"
self.aliases = dataIO.load_json(self.file_path)
self.remove_old()
@commands.group(pass_context=True, no_pm=True)
async def alias(self, ctx):
"""Manage per-server aliases for commands"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@alias.command(name="add", pass_context=True, no_pm=True)
@checks.mod_or_permissions(manage_server=True)
async def _add_alias(self, ctx, command, *, to_execute):
"""Add an alias for a command
Example: !alias add test flip @Twentysix"""
server = ctx.message.server
command = command.lower()
if len(command.split(" ")) != 1:
await self.bot.say("I can't safely do multi-word aliases because"
" of the fact that I allow arguments to"
" aliases. It sucks, I know, deal with it.")
return
if self.part_of_existing_command(command, server.id):
await self.bot.say('I can\'t safely add an alias that starts with '
'an existing command or alias. Sry <3')
return
prefix = self.get_prefix(server, to_execute)
if prefix is not None:
to_execute = to_execute[len(prefix):]
if server.id not in self.aliases:
self.aliases[server.id] = {}
if command not in self.bot.commands:
self.aliases[server.id][command] = to_execute
dataIO.save_json(self.file_path, self.aliases)
await self.bot.say("Alias '{}' added.".format(command))
else:
await self.bot.say("Cannot add '{}' because it's a real bot "
"command.".format(command))
@alias.command(name="help", pass_context=True, no_pm=True)
async def _help_alias(self, ctx, command):
"""Tries to execute help for the base command of the alias"""
server = ctx.message.server
if server.id in self.aliases:
server_aliases = self.aliases[server.id]
if command in server_aliases:
help_cmd = server_aliases[command].split(" ")[0]
new_content = self.bot.settings.get_prefixes(server)[0]
new_content += "help "
new_content += help_cmd[len(self.get_prefix(server,
help_cmd)):]
message = ctx.message
message.content = new_content
await self.bot.process_commands(message)
else:
await self.bot.say("That alias doesn't exist.")
@alias.command(name="show", pass_context=True, no_pm=True)
async def _show_alias(self, ctx, command):
"""Shows what command the alias executes."""
server = ctx.message.server
if server.id in self.aliases:
server_aliases = self.aliases[server.id]
if command in server_aliases:
await self.bot.say(box(server_aliases[command]))
else:
await self.bot.say("That alias doesn't exist.")
@alias.command(name="del", pass_context=True, no_pm=True)
@checks.mod_or_permissions(manage_server=True)
async def _del_alias(self, ctx, command):
"""Deletes an alias"""
command = command.lower()
server = ctx.message.server
if server.id in self.aliases:
self.aliases[server.id].pop(command, None)
dataIO.save_json(self.file_path, self.aliases)
await self.bot.say("Alias '{}' deleted.".format(command))
@alias.command(name="list", pass_context=True, no_pm=True)
async def _alias_list(self, ctx):
"""Lists aliases available on this server
Responds in DM"""
server = ctx.message.server
if server.id in self.aliases:
message = "```Alias list:\n"
for alias in sorted(self.aliases[server.id]):
if len(message) + len(alias) + 3 > 2000:
await self.bot.whisper(message)
message = "```\n"
message += "\t{}\n".format(alias)
if message != "```Alias list:\n":
message += "```"
await self.bot.whisper(message)
else:
await self.bot.say("There are no aliases on this server.")
async def on_message(self, message):
if len(message.content) < 2 or message.channel.is_private:
return
msg = message.content
server = message.server
prefix = self.get_prefix(server, msg)
if not prefix:
return
if server.id in self.aliases and user_allowed(message):
alias = self.first_word(msg[len(prefix):]).lower()
if alias in self.aliases[server.id]:
new_command = self.aliases[server.id][alias]
args = message.content[len(prefix + alias):]
new_message = copy(message)
new_message.content = prefix + new_command + args
await self.bot.process_commands(new_message)
def part_of_existing_command(self, alias, server):
'''Command or alias'''
for command in self.bot.commands:
if alias.lower() == command.lower():
return True
return False
def remove_old(self):
for sid in self.aliases:
to_delete = []
to_add = []
for aliasname, alias in self.aliases[sid].items():
lower = aliasname.lower()
if aliasname != lower:
to_delete.append(aliasname)
to_add.append((lower, alias))
if aliasname != self.first_word(aliasname):
to_delete.append(aliasname)
continue
server = discord.Object(id=sid)
prefix = self.get_prefix(server, alias)
if prefix is not None:
self.aliases[sid][aliasname] = alias[len(prefix):]
for alias in to_delete: # Fixes caps and bad prefixes
del self.aliases[sid][alias]
for alias, command in to_add: # For fixing caps
self.aliases[sid][alias] = command
dataIO.save_json(self.file_path, self.aliases)
def first_word(self, msg):
return msg.split(" ")[0]
def get_prefix(self, server, msg):
prefixes = self.bot.settings.get_prefixes(server)
for p in prefixes:
if msg.startswith(p):
return p
return None
def check_folder():
if not os.path.exists("data/alias"):
print("Creating data/alias folder...")
os.makedirs("data/alias")
def check_file():
aliases = {}
f = "data/alias/aliases.json"
if not dataIO.is_valid_json(f):
print("Creating default alias's aliases.json...")
dataIO.save_json(f, aliases)
def setup(bot):
check_folder()
check_file()
bot.add_cog(Alias(bot))

+ 32
- 0
cogs/ascii.py View File

@ -0,0 +1,32 @@
from discord.ext import commands
from cogs.utils.chat_formatting import box
try:
from pyfiglet import figlet_format
except:
figlet_format = None
class Ascii(object):
def __init__(self, bot):
self.bot = bot
@commands.command(name="ascii")
async def _ascii(self, *, text):
try:
msg = str(figlet_format(text, font='cybermedium'))
if msg[0] == " ":
msg = "." + msg[1:]
error = figlet_format("Too long !",
font='cybermedium')
if len(msg) > 2000:
await self.bot.say(box(error))
else:
await self.bot.say(box(msg))
except Exception as e:
await self.bot.say("Don't use emojis :cat:, and/or accents")
def setup(bot):
n = Ascii(bot)
bot.add_cog(n)

+ 2554
- 0
cogs/audio.py
File diff suppressed because it is too large
View File


+ 33
- 0
cogs/cat.py View File

@ -0,0 +1,33 @@
import asyncio
import nekos
import discord
from discord.ext import commands
import aiohttp
class cat:
def __init__(self, bot):
self.bot = bot
@commands.command()
async def cat(self):
"""Send a random cat picture on the channel"""
cats = nekos.cat()
await self.bot.say(cats)
@commands.command()
async def why(self):
"""Why the xbox live is better than psn ?"""
why = nekos.why()
await self.bot.say(why)
@commands.command()
async def fact(self):
"""The biggest cat in the world is Maine Coon, this cat weight 25kg"""
facts = nekos.fact()
embed=discord.Embed(color=0x717171)
embed.add_field(name="KiTTY/Deo", value=facts, inline=False)
await self.bot.say(embed=embed)
def setup(bot):
bot.add_cog(cat(bot))

+ 197
- 0
cogs/customcom.py View File

@ -0,0 +1,197 @@
from discord.ext import commands
from .utils.dataIO import dataIO
from .utils import checks
from .utils.chat_formatting import pagify, box
import os
import re
class CustomCommands:
"""Custom commands
Creates commands used to display text"""
def __init__(self, bot):
self.bot = bot
self.file_path = "data/customcom/commands.json"
self.c_commands = dataIO.load_json(self.file_path)
@commands.group(aliases=["cc"], pass_context=True, no_pm=True)
async def customcom(self, ctx):
"""Custom commands management"""
if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx)
@customcom.command(name="add", pass_context=True)
@checks.mod_or_permissions(administrator=True)
async def cc_add(self, ctx, command : str, *, text):
"""Adds a custom command
Example:
[p]customcom add yourcommand Text you want
"""
server = ctx.message.server
command = command.lower()
if command in self.bot.commands:
await self.bot.say("That command is already a standard command.")
return
if server.id not in self.c_commands:
self.c_commands[server.id] = {}
cmdlist = self.c_commands[server.id]
if command not in cmdlist:
cmdlist[command] = text
self.c_commands[server.id] = cmdlist
dataIO.save_json(self.file_path, self.c_commands)
await self.bot.say("Custom command successfully added.")
else:
await self.bot.say("This command already exists. Use "
"`{}customcom edit` to edit it."
"".format(ctx.prefix))
@customcom.command(name="edit", pass_context=True)
@checks.mod_or_permissions(administrator=True)
async def cc_edit(self, ctx, command : str, *, text):
"""Edits a custom command
Example:
[p]customcom edit yourcommand Text you want
"""
server = ctx.message.server
command = command.lower()
if server.id in self.c_commands:
cmdlist = self.c_commands[server.id]
if command in cmdlist:
cmdlist[command] = text
self.c_commands[server.id] = cmdlist
dataIO.save_json(self.file_path, self.c_commands)
await self.bot.say("Custom command successfully edited.")
else:
await self.bot.say("That command doesn't exist. Use "
"`{}customcom add` to add it."
"".format(ctx.prefix))
else:
await self.bot.say("There are no custom commands in this server."
" Use `{}customcom add` to start adding some."
"".format(ctx.prefix))
@customcom.command(name="delete", pass_context=True)
@checks.mod_or_permissions(administrator=True)
async def cc_delete(self, ctx, command : str):
"""Deletes a custom command
Example:
[p]customcom delete yourcommand"""
server = ctx.message.server
command = command.lower()
if server.id in self.c_commands:
cmdlist = self.c_commands[server.id]
if command in cmdlist:
cmdlist.pop(command, None)
self.c_commands[server.id] = cmdlist
dataIO.save_json(self.file_path, self.c_commands)
await self.bot.say("Custom command successfully deleted.")
else:
await self.bot.say("That command doesn't exist.")
else:
await self.bot.say("There are no custom commands in this server."
" Use `{}customcom add` to start adding some."
"".format(ctx.prefix))
@customcom.command(name="list", pass_context=True)
async def cc_list(self, ctx):
"""Shows custom commands list"""
server = ctx.message.server
commands = self.c_commands.get(server.id, {})
if not commands:
await self.bot.say("There are no custom commands in this server."
" Use `{}customcom add` to start adding some."
"".format(ctx.prefix))
return
commands = ", ".join([ctx.prefix + c for c in sorted(commands)])
commands = "Custom commands:\n\n" + commands
if len(commands) < 1500:
await self.bot.say(box(commands))
else:
for page in pagify(commands, delims=[" ", "\n"]):
await self.bot.whisper(box(page))
async def on_message(self, message):
if len(message.content) < 2 or message.channel.is_private:
return
server = message.server
prefix = self.get_prefix(message)
if not prefix:
return
if server.id in self.c_commands and self.bot.user_allowed(message):
cmdlist = self.c_commands[server.id]
cmd = message.content[len(prefix):]
if cmd in cmdlist:
cmd = cmdlist[cmd]
cmd = self.format_cc(cmd, message)
await self.bot.send_message(message.channel, cmd)
elif cmd.lower() in cmdlist:
cmd = cmdlist[cmd.lower()]
cmd = self.format_cc(cmd, message)
await self.bot.send_message(message.channel, cmd)
def get_prefix(self, message):
for p in self.bot.settings.get_prefixes(message.server):
if message.content.startswith(p):
return p
return False
def format_cc(self, command, message):
results = re.findall("\{([^}]+)\}", command)
for result in results:
param = self.transform_parameter(result, message)
command = command.replace("{" + result + "}", param)
return command
def transform_parameter(self, result, message):
"""
For security reasons only specific objects are allowed
Internals are ignored
"""
raw_result = "{" + result + "}"
objects = {
"message" : message,
"author" : message.author,
"channel" : message.channel,
"server" : message.server
}
if result in objects:
return str(objects[result])
try:
first, second = result.split(".")
except ValueError:
return raw_result
if first in objects and not second.startswith("_"):
first = objects[first]
else:
return raw_result
return str(getattr(first, second, raw_result))
def check_folders():
if not os.path.exists("data/customcom"):
print("Creating data/customcom folder...")
os.makedirs("data/customcom")
def check_files():
f = "data/customcom/commands.json"
if not dataIO.is_valid_json(f):
print("Creating empty commands.json...")
dataIO.save_json(f, {})
def setup(bot):
check_folders()
check_files()
bot.add_cog(CustomCommands(bot))

+ 709
- 0
cogs/downloader.py View File

@ -0,0 +1,709 @@
from discord.ext import commands
from cogs.utils.dataIO import dataIO
from cogs.utils import checks
from cogs.utils.chat_formatting import pagify, box
from __main__ import send_cmd_help, set_cog
import os
from subprocess import run as sp_run, PIPE
import shutil
from asyncio import as_completed
from setuptools import distutils
import discord
from functools import partial
from concurrent.futures import ThreadPoolExecutor
from time import time
from importlib.util import find_spec
from copy import deepcopy
NUM_THREADS = 4
REPO_NONEX = 0x1
REPO_CLONE = 0x2
REPO_SAME = 0x4
REPOS_LIST = "https://twentysix26.github.io/Red-Docs/red_cog_approved_repos/"
WINDOWS_OS = os.name == 'nt'
DISCLAIMER = ("You're about to add a 3rd party repository. The creator of Red, KiTTY,"
" and its community have no responsibility for any potential "
"damage that the content of 3rd party repositories might cause."
"\nBy typing 'I agree' you declare to have read and understand "
"the above message. This message won't be shown again until the"
" next reboot.")
class UpdateError(Exception):
pass
class CloningError(UpdateError):
pass
class RequirementFail(UpdateError):
pass
class Downloader:
"""Cog downloader/installer."""
def __init__(self, bot):
self.bot = bot
self.disclaimer_accepted = False
self.path = os.path.join("data", "downloader")
self.file_path = os.path.join(self.path, "repos.json")
# {name:{url,cog1:{installed},cog1:{installed}}}
self.repos = dataIO.load_json(self.file_path)
self.executor = ThreadPoolExecutor(NUM_THREADS)
self._do_first_run()
def save_repos(self):
dataIO.save_json(self.file_path, self.repos)
@commands.group(pass_context=True)
@checks.is_owner()
async def cog(self, ctx):
"""Additional cogs management"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@cog.group(pass_context=True)
async def repo(self, ctx):
"""Repo management commands"""
if ctx.invoked_subcommand is None or \
isinstance(ctx.invoked_subcommand, commands.Group):
await send_cmd_help(ctx)
return
@repo.command(name="add", pass_context=True)
async def _repo_add(self, ctx, repo_name: str, repo_url: str):
"""Adds repo to available repo lists
Warning: Adding 3RD Party Repositories is at your own
Risk."""
if not self.disclaimer_accepted:
await self.bot.say(DISCLAIMER)
answer = await self.bot.wait_for_message(timeout=30,
author=ctx.message.author)
if answer is None:
await self.bot.say('Not adding repo.')
return
elif "i agree" not in answer.content.lower():
await self.bot.say('Not adding repo.')
return
else:
self.disclaimer_accepted = True
self.repos[repo_name] = {}
self.repos[repo_name]['url'] = repo_url
try:
self.update_repo(repo_name)
except CloningError:
await self.bot.say("That repository link doesn't seem to be "
"valid.")
del self.repos[repo_name]
return
except FileNotFoundError:
error_message = ("I couldn't find git. The downloader needs it "
"for it to properly work.")
if WINDOWS_OS:
error_message += ("\nIf you just installed it you may need "
"a reboot for it to be seen into the PATH "
"environment variable.")
await self.bot.say(error_message)
return
self.populate_list(repo_name)
self.save_repos()
data = self.get_info_data(repo_name)
if data:
msg = data.get("INSTALL_MSG")
if msg:
await self.bot.say(msg[:2000])
await self.bot.say("Repo '{}' added.".format(repo_name))
@repo.command(name="remove")
async def _repo_del(self, repo_name: str):
"""Removes repo from repo list. COGS ARE NOT REMOVED."""
def remove_readonly(func, path, excinfo):
os.chmod(path, 0o755)
func(path)
if repo_name not in self.repos:
await self.bot.say("That repo doesn't exist.")
return
del self.repos[repo_name]
try:
shutil.rmtree(os.path.join(self.path, repo_name), onerror=remove_readonly)
except FileNotFoundError:
pass
self.save_repos()
await self.bot.say("Repo '{}' removed.".format(repo_name))
@cog.command(name="list")
async def _send_list(self, repo_name=None):
"""Lists installable cogs
Repositories list:
https://twentysix26.github.io/Red-Docs/red_cog_approved_repos/"""
retlist = []
if repo_name and repo_name in self.repos:
msg = "Available cogs:\n"
for cog in sorted(self.repos[repo_name].keys()):
if 'url' == cog:
continue
data = self.get_info_data(repo_name, cog)
if data and data.get("HIDDEN") is True:
continue
if data:
retlist.append([cog, data.get("SHORT", "")])
else:
retlist.append([cog, ''])
else:
if self.repos:
msg = "Available repos:\n"
for repo_name in sorted(self.repos.keys()):
data = self.get_info_data(repo_name)
if data:
retlist.append([repo_name, data.get("SHORT", "")])
else:
retlist.append([repo_name, ""])
else:
await self.bot.say("You haven't added a repository yet.\n"
"Start now! {}".format(REPOS_LIST))
return
col_width = max(len(row[0]) for row in retlist) + 2
for row in retlist:
msg += "\t" + "".join(word.ljust(col_width) for word in row) + "\n"
msg += "\nRepositories list: {}".format(REPOS_LIST)
for page in pagify(msg, delims=['\n'], shorten_by=8):
await self.bot.say(box(page))
@cog.command()
async def info(self, repo_name: str, cog: str=None):
"""Shows info about the specified cog"""
if cog is not None:
cogs = self.list_cogs(repo_name)
if cog in cogs:
data = self.get_info_data(repo_name, cog)
if data:
msg = "{} by {}\n\n".format(cog, data["AUTHOR"])
msg += data["NAME"] + "\n\n" + data["DESCRIPTION"]
await self.bot.say(box(msg))
else:
await self.bot.say("The specified cog has no info file.")
else:
await self.bot.say("That cog doesn't exist."
" Use cog list to see the full list.")
else:
data = self.get_info_data(repo_name)
if data is None:
await self.bot.say("That repo does not exist or the"
" information file is missing for that repo"
".")
return
name = data.get("NAME", None)
name = repo_name if name is None else name
author = data.get("AUTHOR", "Unknown")
desc = data.get("DESCRIPTION", "")
msg = ("```{} by {}```\n\n{}".format(name, author, desc))
await self.bot.say(msg)
@cog.command(hidden=True)
async def search(self, *terms: str):
"""Search installable cogs"""
pass # TO DO
@cog.command(pass_context=True)
async def update(self, ctx):
"""Updates cogs"""
tasknum = 0
num_repos = len(self.repos)
min_dt = 0.5
burst_inc = 0.1/(NUM_THREADS)
touch_n = tasknum
touch_t = time()
def regulate(touch_t, touch_n):
dt = time() - touch_t
if dt + burst_inc*(touch_n) > min_dt:
touch_n = 0
touch_t = time()
return True, touch_t, touch_n
return False, touch_t, touch_n + 1
tasks = []
for r in self.repos:
task = partial(self.update_repo, r)
task = self.bot.loop.run_in_executor(self.executor, task)
tasks.append(task)
base_msg = "Downloading updated cogs, please wait... "
status = ' %d/%d repos updated' % (tasknum, num_repos)
msg = await self.bot.say(base_msg + status)
updated_cogs = []
new_cogs = []
deleted_cogs = []
failed_cogs = []
error_repos = {}
installed_updated_cogs = []
for f in as_completed(tasks):
tasknum += 1
try:
name, updates, oldhash = await f
if updates:
if type(updates) is dict:
for k, l in updates.items():
tl = [(name, c, oldhash) for c in l]
if k == 'A':
new_cogs.extend(tl)
elif k == 'D':
deleted_cogs.extend(tl)
elif k == 'M':
updated_cogs.extend(tl)
except UpdateError as e:
name, what = e.args
error_repos[name] = what
edit, touch_t, touch_n = regulate(touch_t, touch_n)
if edit:
status = ' %d/%d repos updated' % (tasknum, num_repos)
msg = await self._robust_edit(msg, base_msg + status)
status = 'done. '
for t in updated_cogs:
repo, cog, _ = t
if self.repos[repo][cog]['INSTALLED']:
try:
await self.install(repo, cog,
no_install_on_reqs_fail=False)
except RequirementFail:
failed_cogs.append(t)
else:
installed_updated_cogs.append(t)
for t in updated_cogs.copy():
if t in failed_cogs:
updated_cogs.remove(t)
if not any(self.repos[repo][cog]['INSTALLED'] for
repo, cog, _ in updated_cogs):
status += ' No updates to apply. '
if new_cogs:
status += '\nNew cogs: ' \
+ ', '.join('%s/%s' % c[:2] for c in new_cogs) + '.'
if deleted_cogs:
status += '\nDeleted cogs: ' \
+ ', '.join('%s/%s' % c[:2] for c in deleted_cogs) + '.'
if updated_cogs:
status += '\nUpdated cogs: ' \
+ ', '.join('%s/%s' % c[:2] for c in updated_cogs) + '.'
if failed_cogs:
status += '\nCogs that got new requirements which have ' + \
'failed to install: ' + \
', '.join('%s/%s' % c[:2] for c in failed_cogs) + '.'
if error_repos:
status += '\nThe following repos failed to update: '
for n, what in error_repos.items():
status += '\n%s: %s' % (n, what)
msg = await self._robust_edit(msg, base_msg + status)
if not installed_updated_cogs:
return
patchnote_lang = 'Prolog'
shorten_by = 8 + len(patchnote_lang)
for note in self.patch_notes_handler(installed_updated_cogs):
if note is None:
continue
for page in pagify(note, delims=['\n'], shorten_by=shorten_by):
await self.bot.say(box(page, patchnote_lang))
await self.bot.say("Cogs updated. Reload updated cogs? (yes/no)")
answer = await self.bot.wait_for_message(timeout=15,
author=ctx.message.author)
if answer is None:
await self.bot.say("Ok then, you can reload cogs with"
" `{}reload <cog_name>`".format(ctx.prefix))
elif answer.content.lower().strip() == "yes":
registry = dataIO.load_json(os.path.join("data", "red", "cogs.json"))
update_list = []
fail_list = []
for repo, cog, _ in installed_updated_cogs:
if not registry.get('cogs.' + cog, False):
continue
try:
self.bot.unload_extension("cogs." + cog)
self.bot.load_extension("cogs." + cog)
update_list.append(cog)
except:
fail_list.append(cog)
msg = 'Done.'
if update_list:
msg += " The following cogs were reloaded: "\
+ ', '.join(update_list) + "\n"
if fail_list:
msg += " The following cogs failed to reload: "\
+ ', '.join(fail_list)
await self.bot.say(msg)
else:
await self.bot.say("Ok then, you can reload cogs with"
" `{}reload <cog_name>`".format(ctx.prefix))
def patch_notes_handler(self, repo_cog_hash_pairs):
for repo, cog, oldhash in repo_cog_hash_pairs:
repo_path = os.path.join('data', 'downloader', repo)
cogfile = os.path.join(cog, cog + ".py")
cmd = ["git", "-C", repo_path, "log", "--relative-date",
"--reverse", oldhash + '..', cogfile
]
try:
log = sp_run(cmd, stdout=PIPE).stdout.decode().strip()
yield self.format_patch(repo, cog, log)
except:
pass
@cog.command(pass_context=True)
async def uninstall(self, ctx, repo_name, cog):
"""Uninstalls a cog"""
if repo_name not in self.repos:
await self.bot.say("That repo doesn't exist.")
return
if cog not in self.repos[repo_name]:
await self.bot.say("That cog isn't available from that repo.")
return
set_cog("cogs." + cog, False)
self.repos[repo_name][cog]['INSTALLED'] = False
self.save_repos()
os.remove(os.path.join("cogs", cog + ".py"))
owner = self.bot.get_cog('Owner')
await owner.unload.callback(owner, cog_name=cog)
await self.bot.say("Cog successfully uninstalled.")
@cog.command(name="install", pass_context=True)
async def _install(self, ctx, repo_name: str, cog: str):
"""Installs specified cog"""
if repo_name not in self.repos:
await self.bot.say("That repo doesn't exist.")
return
if cog not in self.repos[repo_name]:
await self.bot.say("That cog isn't available from that repo.")
return
data = self.get_info_data(repo_name, cog)
try:
install_cog = await self.install(repo_name, cog, notify_reqs=True)
except RequirementFail:
await self.bot.say("That cog has requirements that I could not "
"install. Check the console for more "
"informations.")
return
if data is not None:
install_msg = data.get("INSTALL_MSG", None)
if install_msg:
await self.bot.say(install_msg[:2000])
if install_cog:
await self.bot.say("Installation completed. Load it now? (yes/no)")
answer = await self.bot.wait_for_message(timeout=15,
author=ctx.message.author)
if answer is None:
await self.bot.say("Ok then, you can load it with"
" `{}load {}`".format(ctx.prefix, cog))
elif answer.content.lower().strip() == "yes":
set_cog("cogs." + cog, True)
owner = self.bot.get_cog('Owner')
await owner.load.callback(owner, cog_name=cog)
else:
await self.bot.say("Ok then, you can load it with"
" `{}load {}`".format(ctx.prefix, cog))
elif install_cog is False:
await self.bot.say("Invalid cog. Installation aborted.")
else:
await self.bot.say("That cog doesn't exist. Use cog list to see"
" the full list.")
async def install(self, repo_name, cog, *, notify_reqs=False,
no_install_on_reqs_fail=True):
# 'no_install_on_reqs_fail' will make the cog get installed anyway
# on requirements installation fail. This is necessary because due to
# how 'cog update' works right now, the user would have no way to
# reupdate the cog if the update fails, since 'cog update' only
# updates the cogs that get a new commit.
# This is not a great way to deal with the problem and a cog update
# rework would probably be the best course of action.
reqs_failed = False
if cog.endswith('.py'):
cog = cog[:-3]
path = self.repos[repo_name][cog]['file']
cog_folder_path = self.repos[repo_name][cog]['folder']
cog_data_path = os.path.join(cog_folder_path, 'data')
data = self.get_info_data(repo_name, cog)
if data is not None:
requirements = data.get("REQUIREMENTS", [])
requirements = [r for r in requirements
if not self.is_lib_installed(r)]
if requirements and notify_reqs:
await self.bot.say("Installing cog's requirements...")
for requirement in requirements:
if not self.is_lib_installed(requirement):
success = await self.bot.pip_install(requirement)
if not success:
if no_install_on_reqs_fail:
raise RequirementFail()
else:
reqs_failed = True
to_path = os.path.join("cogs", cog + ".py")
print("Copying {}...".format(cog))
shutil.copy(path, to_path)
if os.path.exists(cog_data_path):
print("Copying {}'s data folder...".format(cog))
distutils.dir_util.copy_tree(cog_data_path,
os.path.join('data', cog))
self.repos[repo_name][cog]['INSTALLED'] = True
self.save_repos()
if not reqs_failed:
return True
else:
raise RequirementFail()
def get_info_data(self, repo_name, cog=None):
if cog is not None:
cogs = self.list_cogs(repo_name)
if cog in cogs:
info_file = os.path.join(cogs[cog].get('folder'), "info.json")
if os.path.isfile(info_file):
try:
data = dataIO.load_json(info_file)
except:
return None
return data
else:
repo_info = os.path.join(self.path, repo_name, 'info.json')
if os.path.isfile(repo_info):
try:
data = dataIO.load_json(repo_info)
return data
except:
return None
return None
def list_cogs(self, repo_name):
valid_cogs = {}
repo_path = os.path.join(self.path, repo_name)
folders = [f for f in os.listdir(repo_path)
if os.path.isdir(os.path.join(repo_path, f))]
legacy_path = os.path.join(repo_path, "cogs")
legacy_folders = []
if os.path.exists(legacy_path):
for f in os.listdir(legacy_path):
if os.path.isdir(os.path.join(legacy_path, f)):
legacy_folders.append(os.path.join("cogs", f))
folders = folders + legacy_folders
for f in folders:
cog_folder_path = os.path.join(self.path, repo_name, f)
cog_folder = os.path.basename(cog_folder_path)
for cog in os.listdir(cog_folder_path):
cog_path = os.path.join(cog_folder_path, cog)
if os.path.isfile(cog_path) and cog_folder == cog[:-3]:
valid_cogs[cog[:-3]] = {'folder': cog_folder_path,
'file': cog_path}
return valid_cogs
def get_dir_name(self, url):
splitted = url.split("/")
git_name = splitted[-1]
return git_name[:-4]
def is_lib_installed(self, name):
return bool(find_spec(name))
def _do_first_run(self):
save = False
repos_copy = deepcopy(self.repos)
# Issue 725
for repo in repos_copy:
for cog in repos_copy[repo]:
cog_data = repos_copy[repo][cog]
if isinstance(cog_data, str): # ... url field
continue
for k, v in cog_data.items():
if k in ("file", "folder"):
repos_copy[repo][cog][k] = os.path.normpath(cog_data[k])
if self.repos != repos_copy:
self.repos = repos_copy
save = True
invalid = []
for repo in self.repos:
broken = 'url' in self.repos[repo] and len(self.repos[repo]) == 1
if broken:
save = True
try:
self.update_repo(repo)
self.populate_list(repo)
except CloningError:
invalid.append(repo)
continue
except Exception as e:
print(e) # TODO: Proper logging
continue
for repo in invalid:
del self.repos[repo]
if save:
self.save_repos()
def populate_list(self, name):
valid_cogs = self.list_cogs(name)
new = set(valid_cogs.keys())
old = set(self.repos[name].keys())
for cog in new - old:
self.repos[name][cog] = valid_cogs.get(cog, {})
self.repos[name][cog]['INSTALLED'] = False
for cog in new & old:
self.repos[name][cog].update(valid_cogs[cog])
for cog in old - new:
if cog != 'url':
del self.repos[name][cog]
def update_repo(self, name):
def run(*args, **kwargs):
env = os.environ.copy()
env['GIT_TERMINAL_PROMPT'] = '0'
kwargs['env'] = env
return sp_run(*args, **kwargs)
try:
dd = self.path
if name not in self.repos:
raise UpdateError("Repo does not exist in data, wtf")
folder = os.path.join(dd, name)
# Make sure we don't git reset the Red folder on accident
if not os.path.exists(os.path.join(folder, '.git')):
#if os.path.exists(folder):
#shutil.rmtree(folder)
url = self.repos[name].get('url')
if not url:
raise UpdateError("Need to clone but no URL set")
branch = None
if "@" in url: # Specific branch
url, branch = url.rsplit("@", maxsplit=1)
if branch is None:
p = run(["git", "clone", url, folder])
else:
p = run(["git", "clone", "-b", branch, url, folder])
if p.returncode != 0:
raise CloningError()
self.populate_list(name)
return name, REPO_CLONE, None
else:
rpbcmd = ["git", "-C", folder, "rev-parse", "--abbrev-ref", "HEAD"]
p = run(rpbcmd, stdout=PIPE)
branch = p.stdout.decode().strip()
rpcmd = ["git", "-C", folder, "rev-parse", branch]
p = run(["git", "-C", folder, "reset", "--hard",
"origin/%s" % branch, "-q"])
if p.returncode != 0:
raise UpdateError("Error resetting to origin/%s" % branch)
p = run(rpcmd, stdout=PIPE)
if p.returncode != 0:
raise UpdateError("Unable to determine old commit hash")
oldhash = p.stdout.decode().strip()
p = run(["git", "-C", folder, "pull", "-q", "--ff-only"])
if p.returncode != 0:
raise UpdateError("Error pulling updates")
p = run(rpcmd, stdout=PIPE)
if p.returncode != 0:
raise UpdateError("Unable to determine new commit hash")
newhash = p.stdout.decode().strip()
if oldhash == newhash:
return name, REPO_SAME, None
else:
self.populate_list(name)
self.save_repos()
ret = {}
cmd = ['git', '-C', folder, 'diff', '--no-commit-id',
'--name-status', oldhash, newhash]
p = run(cmd, stdout=PIPE)
if p.returncode != 0:
raise UpdateError("Error in git diff")
changed = p.stdout.strip().decode().split('\n')
for f in changed:
if not f.endswith('.py'):
continue
status, _, cogpath = f.partition('\t')
split = os.path.split(cogpath)
cogdir, cogname = split[-2:]
cogname = cogname[:-3] # strip .py
if len(split) != 2 or cogdir != cogname:
continue
if status not in ret:
ret[status] = []
ret[status].append(cogname)
return name, ret, oldhash
except CloningError as e:
raise CloningError(name, *e.args) from None
except UpdateError as e:
raise UpdateError(name, *e.args) from None
async def _robust_edit(self, msg, text):
try:
msg = await self.bot.edit_message(msg, text)
except discord.errors.NotFound:
msg = await self.bot.send_message(msg.channel, text)
except:
raise
return msg
@staticmethod
def format_patch(repo, cog, log):
header = "Patch Notes for %s/%s" % (repo, cog)
line = "=" * len(header)
if log:
return '\n'.join((header, line, log))
def check_folders():
if not os.path.exists(os.path.join("data", "downloader")):
print('Making repo downloads folder...')
os.mkdir(os.path.join("data", "downloader"))
def check_files():
f = os.path.join("data", "downloader", "repos.json")
if not dataIO.is_valid_json(f):
print("Creating default data/downloader/repos.json")
dataIO.save_json(f, {})
def setup(bot):
check_folders()
check_files()
n = Downloader(bot)
bot.add_cog(n)

+ 739
- 0
cogs/economy.py View File

@ -0,0 +1,739 @@
import discord
from discord.ext import commands
from cogs.utils.dataIO import dataIO
from collections import namedtuple, defaultdict, deque
from datetime import datetime
from copy import deepcopy
from .utils import checks
from cogs.utils.chat_formatting import pagify, box
from enum import Enum
from __main__ import send_cmd_help
import os
import time
import logging
import random
default_settings = {"PAYDAY_TIME": 300, "PAYDAY_CREDITS": 120,
"SLOT_MIN": 5, "SLOT_MAX": 100, "SLOT_TIME": 0,
"REGISTER_CREDITS": 0}
class EconomyError(Exception):
pass
class OnCooldown(EconomyError):
pass
class InvalidBid(EconomyError):
pass
class BankError(Exception):
pass
class AccountAlreadyExists(BankError):
pass
class NoAccount(BankError):
pass
class InsufficientBalance(BankError):
pass
class NegativeValue(BankError):
pass
class SameSenderAndReceiver(BankError):
pass
NUM_ENC = "\N{COMBINING ENCLOSING KEYCAP}"
class SMReel(Enum):
cherries = "\N{CHERRIES}"
cookie = "\N{COOKIE}"
two = "\N{DIGIT TWO}" + NUM_ENC
flc = "\N{FOUR LEAF CLOVER}"
cyclone = "\N{CYCLONE}"
sunflower = "\N{SUNFLOWER}"
six = "\N{DIGIT SIX}" + NUM_ENC
mushroom = "\N{MUSHROOM}"
heart = "\N{HEAVY BLACK HEART}"
snowflake = "\N{SNOWFLAKE}"
PAYOUTS = {
(SMReel.two, SMReel.two, SMReel.six) : {
"payout" : lambda x: x * 2500 + x,
"phrase" : "JACKPOT! 226! Your bid has been multiplied * 2500! :cat:"
},
(SMReel.flc, SMReel.flc, SMReel.flc) : {
"payout" : lambda x: x + 1000,
"phrase" : "4LC! +1000!"
},
(SMReel.cherries, SMReel.cherries, SMReel.cherries) : {
"payout" : lambda x: x + 800,
"phrase" : "Three cherries! +800!"
},
(SMReel.two, SMReel.six) : {
"payout" : lambda x: x * 4 + x,
"phrase" : "2 6! Your bid has been multiplied * 4! :scream_cat:"
},
(SMReel.cherries, SMReel.cherries) : {
"payout" : lambda x: x * 3 + x,
"phrase" : "Two cherries! Your bid has been multiplied * 3!"
},
"3 symbols" : {
"payout" : lambda x: x + 500,
"phrase" : "Three symbols! +500!"
},
"2 symbols" : {
"payout" : lambda x: x * 2 + x,
"phrase" : "Two consecutive symbols! Your bid has been multiplied * 2!"
},
}
SLOT_PAYOUTS_MSG = ("Slot machine payouts:\n"
"{two.value} {two.value} {six.value} Bet * 2500\n"
"{flc.value} {flc.value} {flc.value} +1000\n"
"{cherries.value} {cherries.value} {cherries.value} +800\n"
"{two.value} {six.value} Bet * 4\n"
"{cherries.value} {cherries.value} Bet * 3\n\n"
"Three symbols: +500\n"
"Two symbols: Bet * 2".format(**SMReel.__dict__))
class Bank:
def __init__(self, bot, file_path):
self.accounts = dataIO.load_json(file_path)
self.bot = bot
def create_account(self, user, *, initial_balance=0):
server = user.server
if not self.account_exists(user):
if server.id not in self.accounts:
self.accounts[server.id] = {}
if user.id in self.accounts: # Legacy account
balance = self.accounts[user.id]["balance"]
else:
balance = initial_balance
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
account = {"name": user.name,
"balance": balance,
"created_at": timestamp
}
self.accounts[server.id][user.id] = account
self._save_bank()
return self.get_account(user)
else:
raise AccountAlreadyExists()
def account_exists(self, user):
try:
self._get_account(user)
except NoAccount:
return False
return True
def withdraw_credits(self, user, amount):
server = user.server
if amount < 0:
raise NegativeValue()
account = self._get_account(user)
if account["balance"] >= amount:
account["balance"] -= amount
self.accounts[server.id][user.id] = account
self._save_bank()
else:
raise InsufficientBalance()
def deposit_credits(self, user, amount):
server = user.server
if amount < 0:
raise NegativeValue()
account = self._get_account(user)
account["balance"] += amount
self.accounts[server.id][user.id] = account
self._save_bank()
def set_credits(self, user, amount):
server = user.server
if amount < 0:
raise NegativeValue()
account = self._get_account(user)
account["balance"] = amount
self.accounts[server.id][user.id] = account
self._save_bank()
def transfer_credits(self, sender, receiver, amount):
if amount < 0:
raise NegativeValue()
if sender is receiver:
raise SameSenderAndReceiver()
if self.account_exists(sender) and self.account_exists(receiver):
sender_acc = self._get_account(sender)
if sender_acc["balance"] < amount:
raise InsufficientBalance()
self.withdraw_credits(sender, amount)
self.deposit_credits(receiver, amount)
else:
raise NoAccount()
def can_spend(self, user, amount):
account = self._get_account(user)
if account["balance"] >= amount:
return True
else:
return False
def wipe_bank(self, server):
self.accounts[server.id] = {}
self._save_bank()
def get_server_accounts(self, server):
if server.id in self.accounts:
raw_server_accounts = deepcopy(self.accounts[server.id])
accounts = []
for k, v in raw_server_accounts.items():
v["id"] = k
v["server"] = server
acc = self._create_account_obj(v)
accounts.append(acc)
return accounts
else:
return []
def get_all_accounts(self):
accounts = []
for server_id, v in self.accounts.items():
server = self.bot.get_server(server_id)
if server is None:
# Servers that have since been left will be ignored
# Same for users_id from the old bank format
continue
raw_server_accounts = deepcopy(self.accounts[server.id])
for k, v in raw_server_accounts.items():
v["id"] = k
v["server"] = server
acc = self._create_account_obj(v)
accounts.append(acc)
return accounts
def get_balance(self, user):
account = self._get_account(user)
return account["balance"]
def get_account(self, user):
acc = self._get_account(user)
acc["id"] = user.id
acc["server"] = user.server
return self._create_account_obj(acc)
def _create_account_obj(self, account):
account["member"] = account["server"].get_member(account["id"])
account["created_at"] = datetime.strptime(account["created_at"],
"%Y-%m-%d %H:%M:%S")
Account = namedtuple("Account", "id name balance "
"created_at server member")
return Account(**account)
def _save_bank(self):
dataIO.save_json("data/economy/bank.json", self.accounts)
def _get_account(self, user):
server = user.server
try:
return deepcopy(self.accounts[server.id][user.id])
except KeyError:
raise NoAccount()
class SetParser:
def __init__(self, argument):
allowed = ("+", "-")
if argument and argument[0] in allowed:
try:
self.sum = int(argument)
except:
raise
if self.sum < 0:
self.operation = "withdraw"
elif self.sum > 0:
self.operation = "deposit"
else:
raise
self.sum = abs(self.sum)
elif argument.isdigit():
self.sum = int(argument)
self.operation = "set"
else:
raise
class Economy:
"""Economy
Get rich and have fun with imaginary currency!"""
def __init__(self, bot):
global default_settings
self.bot = bot
self.bank = Bank(bot, "data/economy/bank.json")
self.file_path = "data/economy/settings.json"
self.settings = dataIO.load_json(self.file_path)
if "PAYDAY_TIME" in self.settings: # old format
default_settings = self.settings
self.settings = {}
self.settings = defaultdict(default_settings.copy, self.settings)
self.payday_register = defaultdict(dict)
self.slot_register = defaultdict(dict)
@commands.group(name="bank", pass_context=True