Browse Source

ajout de la base

master
Geekcat 1 year 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)
async def _bank(self, ctx):
"""Bank operations"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)

@_bank.command(pass_context=True, no_pm=True)
async def register(self, ctx):
"""Registers an account at the Twentysix bank"""
settings = self.settings[ctx.message.server.id]
author = ctx.message.author
credits = 0
if ctx.message.server.id in self.settings:
credits = settings.get("REGISTER_CREDITS", 0)
try:
account = self.bank.create_account(author, initial_balance=credits)
await self.bot.say("{} Account opened. Current balance: {}"
"".format(author.mention, account.balance))
except AccountAlreadyExists:
await self.bot.say("{} You already have an account at the"
" KiTTYs bank.".format(author.mention))

@_bank.command(pass_context=True)
async def balance(self, ctx, user: discord.Member=None):
"""Shows balance of user.

Defaults to yours."""
if not user:
user = ctx.message.author
try:
await self.bot.say("{} Your balance is: {}".format(
user.mention, self.bank.get_balance(user)))
except NoAccount:
await self.bot.say("{} You don't have an account at the"
" KiTTYs bank. Type `{}bank register`"
" to open one.".format(user.mention,
ctx.prefix))
else:
try:
await self.bot.say("{}'s balance is {}".format(
user.name, self.bank.get_balance(user)))
except NoAccount:
await self.bot.say("That user has no bank account.")

@_bank.command(pass_context=True)
async def transfer(self, ctx, user: discord.Member, sum: int):
"""Transfer credits to other users"""
author = ctx.message.author
try:
self.bank.transfer_credits(author, user, sum)
logger.info("{}({}) transferred {} credits to {}({})".format(
author.name, author.id, sum, user.name, user.id))
await self.bot.say("{} credits have been transferred to {}'s"
" account.".format(sum, user.name))
except NegativeValue:
await self.bot.say("You need to transfer at least 1 credit.")
except SameSenderAndReceiver:
await self.bot.say("You can't transfer credits to yourself.")
except InsufficientBalance:
await self.bot.say("You don't have that sum in your bank account.")
except NoAccount:
await self.bot.say("That user has no bank account.")

@_bank.command(name="set", pass_context=True)
@checks.admin_or_permissions(manage_server=True)
async def _set(self, ctx, user: discord.Member, credits: SetParser):
"""Sets credits of user's bank account. See help for more operations

Passing positive and negative values will add/remove credits instead

Examples:
bank set @Twentysix 26 - Sets 26 credits
bank set @Twentysix +2 - Adds 2 credits
bank set @Twentysix -6 - Removes 6 credits"""
author = ctx.message.author
try:
if credits.operation == "deposit":
self.bank.deposit_credits(user, credits.sum)
logger.info("{}({}) added {} credits to {} ({})".format(
author.name, author.id, credits.sum, user.name, user.id))
await self.bot.say("{} credits have been added to {}"
"".format(credits.sum, user.name))
elif credits.operation == "withdraw":
self.bank.withdraw_credits(user, credits.sum)
logger.info("{}({}) removed {} credits to {} ({})".format(
author.name, author.id, credits.sum, user.name, user.id))
await self.bot.say("{} credits have been withdrawn from {}"
"".format(credits.sum, user.name))
elif credits.operation == "set":
self.bank.set_credits(user, credits.sum)
logger.info("{}({}) set {} credits to {} ({})"
"".format(author.name, author.id, credits.sum,
user.name, user.id))
await self.bot.say("{}'s credits have been set to {}".format(
user.name, credits.sum))
except InsufficientBalance:
await self.bot.say("User doesn't have enough credits.")
except NoAccount:
await self.bot.say("User has no bank account.")

@_bank.command(pass_context=True, no_pm=True)
@checks.serverowner_or_permissions(administrator=True)
async def reset(self, ctx, confirmation: bool=False):
"""Deletes all server's bank accounts"""
if confirmation is False:
await self.bot.say("This will delete all bank accounts on "
"this server.\nIf you're sure, type "
"{}bank reset yes".format(ctx.prefix))
else:
self.bank.wipe_bank(ctx.message.server)
await self.bot.say("All bank accounts of this server have been "
"deleted.")

@commands.command(pass_context=True, no_pm=True)
async def daily(self, ctx): # TODO
"""Get some free credits"""
author = ctx.message.author
server = author.server
id = author.id
if self.bank.account_exists(author):
if id in self.payday_register[server.id]:
seconds = abs(self.payday_register[server.id][
id] - int(time.perf_counter()))
if seconds >= self.settings[server.id]["PAYDAY_TIME"]:
self.bank.deposit_credits(author, self.settings[
server.id]["PAYDAY_CREDITS"])
self.payday_register[server.id][
id] = int(time.perf_counter())
await self.bot.say(
"{} Here, take some credits. Enjoy! (+{}"
" credits!)".format(
author.mention,
str(self.settings[server.id]["PAYDAY_CREDITS"])))
else:
dtime = self.display_time(
self.settings[server.id]["PAYDAY_TIME"] - seconds)
await self.bot.say(
"{} Too soon. For your next payday you have to"
" wait {}.".format(author.mention, dtime))
else:
self.payday_register[server.id][id] = int(time.perf_counter())
self.bank.deposit_credits(author, self.settings[
server.id]["PAYDAY_CREDITS"])
await self.bot.say(
"{} Here, take some credits. Enjoy! (+{} credits!)".format(
author.mention,
str(self.settings[server.id]["PAYDAY_CREDITS"])))
else:
await self.bot.say("{} You need an account to receive credits."
" Type `{}bank register` to open one.".format(
author.mention, ctx.prefix))

@commands.group(pass_context=True)
async def leaderboard(self, ctx):
"""Server / global leaderboard

Defaults to \"server\" if not issued in DM"""
if ctx.invoked_subcommand is None:
if ctx.message.server:
await ctx.invoke(self._server_leaderboard)
else:
await ctx.invoke(self._global_leaderboard)

@leaderboard.command(name="server", pass_context=True, no_pm=True)
async def _server_leaderboard(self, ctx, top: int=10):
"""Prints out the server's leaderboard

Defaults to top 10"""
# Originally coded by Airenkun - edited by irdumb
server = ctx.message.server
if top < 1:
top = 10
bank_sorted = sorted(self.bank.get_server_accounts(server),
key=lambda x: x.balance, reverse=True)
bank_sorted = [a for a in bank_sorted if a.member] # exclude users who left
if len(bank_sorted) < top:
top = len(bank_sorted)
topten = bank_sorted[:top]
highscore = ""
place = 1
for acc in topten:
highscore += str(place).ljust(len(str(top)) + 1)
highscore += (str(acc.member.display_name) + " ").ljust(23 - len(str(acc.balance)))
highscore += str(acc.balance) + "\n"
place += 1
if highscore != "":
for page in pagify(highscore, shorten_by=12):
await self.bot.say(box(page, lang="py"))
else:
await self.bot.say("There are no accounts in the bank. Be the first with `{}bank register`")

@leaderboard.command(name="global")
async def _global_leaderboard(self, top: int=10):
"""Prints out the global leaderboard

Defaults to top 10"""
if top < 1:
top = 10
bank_sorted = sorted(self.bank.get_all_accounts(),
key=lambda x: x.balance, reverse=True)
bank_sorted = [a for a in bank_sorted if a.member] # exclude users who left
unique_accounts = []
for acc in bank_sorted:
if not self.already_in_list(unique_accounts, acc):
unique_accounts.append(acc)
if len(unique_accounts) < top:
top = len(unique_accounts)
topten = unique_accounts[:top]
highscore = ""
place = 1
for acc in topten:
highscore += str(place).ljust(len(str(top)) + 1)
highscore += ("{} |{}| ".format(acc.member, acc.server)
).ljust(23 - len(str(acc.balance)))
highscore += str(acc.balance) + "\n"
place += 1
if highscore != "":
for page in pagify(highscore, shorten_by=12):
await self.bot.say(box(page, lang="py"))
else:
await self.bot.say("There are no accounts in the bank. Be the first with `{}bank register`")

def already_in_list(self, accounts, user):
for acc in accounts:
if user.id == acc.id:
return True
return False

@commands.command()
async def payouts(self):
"""Shows slot machine payouts"""
await self.bot.whisper(SLOT_PAYOUTS_MSG)

@commands.command(pass_context=True, no_pm=True)
async def slot(self, ctx, bid: int):
"""Play the slot machine"""
author = ctx.message.author
server = author.server
settings = self.settings[server.id]
valid_bid = settings["SLOT_MIN"] <= bid and bid <= settings["SLOT_MAX"]
slot_time = settings["SLOT_TIME"]
last_slot = self.slot_register.get(author.id)
now = datetime.utcnow()
try:
if last_slot:
if (now - last_slot).seconds < slot_time:
raise OnCooldown()
if not valid_bid:
raise InvalidBid()
if not self.bank.can_spend(author, bid):
raise InsufficientBalance
await self.slot_machine(author, bid)
except NoAccount:
await self.bot.say("{} You need an account to use the slot "
"machine. Type `{}bank register` to open one."
"".format(author.mention, ctx.prefix))
except InsufficientBalance:
await self.bot.say("{} You need an account with enough funds to "
"play with the slot machine.".format(author.mention))
except OnCooldown:
await self.bot.say("Slot machine is still cooling off! Wait {} "
"seconds between each pull !".format(slot_time))
except InvalidBid:
await self.bot.say("Bid must be between {} and {}."
"".format(settings["SLOT_MIN"],
settings["SLOT_MAX"]))

async def slot_machine(self, author, bid):
default_reel = deque(SMReel)
reels = []
self.slot_register[author.id] = datetime.utcnow()
for i in range(3):
default_reel.rotate(random.randint(-999, 999)) # weeeeee
new_reel = deque(default_reel, maxlen=3) # we need only 3 symbols
reels.append(new_reel) # for each reel
rows = ((reels[0][0], reels[1][0], reels[2][0]),
(reels[0][1], reels[1][1], reels[2][1]),
(reels[0][2], reels[1][2], reels[2][2]))

slot = "~~\n~~" # Mobile friendly
for i, row in enumerate(rows): # Let's build the slot to show
sign = " "
if i == 1:
sign = ">"
slot += "{}{} {} {}\n".format(sign, *[c.value for c in row])

payout = PAYOUTS.get(rows[1])
if not payout:
# Checks for two-consecutive-symbols special rewards
payout = PAYOUTS.get((rows[1][0], rows[1][1]),
PAYOUTS.get((rows[1][1], rows[1][2]))
)
if not payout:
# Still nothing. Let's check for 3 generic same symbols
# or 2 consecutive symbols
has_three = rows[1][0] == rows[1][1] == rows[1][2]
has_two = (rows[1][0] == rows[1][1]) or (rows[1][1] == rows[1][2])
if has_three:
payout = PAYOUTS["3 symbols"]
elif has_two:
payout = PAYOUTS["2 symbols"]

if payout:
then = self.bank.get_balance(author)
pay = payout["payout"](bid)
now = then - bid + pay
self.bank.set_credits(author, now)
await self.bot.say("{}\n{} {}\n\nYour bid: {}\n{} → {}!"
"".format(slot, author.mention,
payout["phrase"], bid, then, now))
else:
then = self.bank.get_balance(author)
self.bank.withdraw_credits(author, bid)
now = then - bid
await self.bot.say("{}\n{} Nothing!\nYour bid: {}\n{} → {}!"
"".format(slot, author.mention, bid, then, now))

@commands.group(pass_context=True, no_pm=True)
@checks.admin_or_permissions(manage_server=True)
async def economyset(self, ctx):
"""Changes economy module settings"""
server = ctx.message.server
settings = self.settings[server.id]
if ctx.invoked_subcommand is None:
msg = "```"
for k, v in settings.items():
msg += "{}: {}\n".format(k, v)
msg += "```"
await send_cmd_help(ctx)
await self.bot.say(msg)

@economyset.command(pass_context=True)
async def slotmin(self, ctx, bid: int):
"""Minimum slot machine bid"""
server = ctx.message.server
self.settings[server.id]["SLOT_MIN"] = bid
await self.bot.say("Minimum bid is now {} credits.".format(bid))
dataIO.save_json(self.file_path, self.settings)

@economyset.command(pass_context=True)
async def slotmax(self, ctx, bid: int):
"""Maximum slot machine bid"""
server = ctx.message.server
self.settings[server.id]["SLOT_MAX"] = bid
await self.bot.say("Maximum bid is now {} credits.".format(bid))
dataIO.save_json(self.file_path, self.settings)

@economyset.command(pass_context=True)
async def slottime(self, ctx, seconds: int):
"""Seconds between each slots use"""
server = ctx.message.server
self.settings[server.id]["SLOT_TIME"] = seconds
await self.bot.say("Cooldown is now {} seconds.".format(seconds))
dataIO.save_json(self.file_path, self.settings)

@economyset.command(pass_context=True)
async def paydaytime(self, ctx, seconds: int):
"""Seconds between each payday"""
server = ctx.message.server
self.settings[server.id]["PAYDAY_TIME"] = seconds
await self.bot.say("Value modified. At least {} seconds must pass "
"between each payday.".format(seconds))
dataIO.save_json(self.file_path, self.settings)

@economyset.command(pass_context=True)
async def paydaycredits(self, ctx, credits: int):
"""Credits earned each payday"""
server = ctx.message.server
self.settings[server.id]["PAYDAY_CREDITS"] = credits
await self.bot.say("Every payday will now give {} credits."
"".format(credits))
dataIO.save_json(self.file_path, self.settings)

@economyset.command(pass_context=True)
async def registercredits(self, ctx, credits: int):
"""Credits given on registering an account"""
server = ctx.message.server
if credits < 0:
credits = 0
self.settings[server.id]["REGISTER_CREDITS"] = credits
await self.bot.say("Registering an account will now give {} credits."
"".format(credits))
dataIO.save_json(self.file_path, self.settings)

# What would I ever do without stackoverflow?
def display_time(self, seconds, granularity=2):
intervals = ( # Source: http://stackoverflow.com/a/24542445
('weeks', 604800), # 60 * 60 * 24 * 7
('days', 86400), # 60 * 60 * 24
('hours', 3600), # 60 * 60
('minutes', 60),
('seconds', 1),
)

result = []

for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
if value == 1:
name = name.rstrip('s')
result.append("{} {}".format(value, name))
return ', '.join(result[:granularity])


def check_folders():
if not os.path.exists("data/economy"):
print("Creating data/economy folder...")
os.makedirs("data/economy")


def check_files():

f = "data/economy/settings.json"
if not dataIO.is_valid_json(f):
print("Creating default economy's settings.json...")
dataIO.save_json(f, {})

f = "data/economy/bank.json"
if not dataIO.is_valid_json(f):
print("Creating empty bank.json...")
dataIO.save_json(f, {})


def setup(bot):
global logger
check_folders()
check_files()
logger = logging.getLogger("red.economy")
if logger.level == 0:
# Prevents the logger from being loaded again in case of module reload
logger.setLevel(logging.INFO)
handler = logging.FileHandler(
filename='data/economy/economy.log', encoding='utf-8', mode='a')
handler.setFormatter(logging.Formatter(
'%(asctime)s %(message)s', datefmt="[%d/%m/%Y %H:%M]"))
logger.addHandler(handler)
bot.add_cog(Economy(bot))

+ 58
- 0
cogs/funny.py View File

@@ -0,0 +1,58 @@
import discord
import pyfiglet
import requests
import random
from discord.ext import commands
from cogs.utils import checks
from random import randint
from random import choice
from cogs.utils.chat_formatting import box
from bs4 import BeautifulSoup

class Fun:
"""docstring for fun"""
def __init__(self, bot):
self.bot = bot
self.nsword = 0

@commands.command(pass_context=True)
async def swordbattle(self, ctx, *, user: discord.Member):
"""Sword Duel!"""
author = ctx.message.author
self.nsword += 1
n = self.nsword
if user.id == self.bot.user.id:
await self.bot.say("I'm a cat ! Not a fighting guy !")
else:
await self.bot.say(author.mention + " and " + user.mention + " dueled for " + str(randint(1, 120)) +
" hours! It was a long, heated battle, but " +
choice([author.mention, user.mention]) + " is victorious !")

@commands.command(pass_context=True)
async def cookie(self, ctx, *, user: discord.Member):
"""Give some cookie to a people!"""
author = ctx.message.author
if user.id == self.bot.user.id:
await self.bot.say("Thank you :3 !")
else:
await self.bot.say(author.mention + " gave a :cookie: to " + user.mention + " :cat:")

@commands.command(pass_context=True)
async def heart(self, ctx, *, user: discord.Member):
"""<3"""
author = ctx.message.author
user.id == self.bot.user.id
await self.bot.say(":heart: " + user.mention)

@commands.command(pass_context=True)
async def arduino(self):
"""We're loving arduino"""
source = requests.Session().get("https://www.hackster.io/arduino/projects", headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"}).content
soup = BeautifulSoup(source, "html.parser")
lst = soup.find_all("div", class_="project_card__body__S0LrW cards__body__3yUOQ")
print(lst)
arduino = "https://www.hackster.io/" + random.choice(lst).string
await self.bot.say(arduino)

def setup(bot):
bot.add_cog(Fun(bot))

+ 433
- 0
cogs/general.py View File

@@ -0,0 +1,433 @@
import discord
from discord.ext import commands
from .utils.chat_formatting import escape_mass_mentions, italics, pagify
from random import randint
from random import choice
from enum import Enum
from urllib.parse import quote_plus
import datetime
import time
import aiohttp
import asyncio

settings = {"POLL_DURATION" : 60}


class RPS(Enum):
rock = "\N{MOYAI}"
paper = "\N{PAGE FACING UP}"
scissors = "\N{BLACK SCISSORS}"


class RPSParser:
def __init__(self, argument):
argument = argument.lower()
if argument == "rock":
self.choice = RPS.rock
elif argument == "paper":
self.choice = RPS.paper
elif argument == "scissors":
self.choice = RPS.scissors
else:
raise


class General:
"""General commands."""

def __init__(self, bot):
self.bot = bot
self.stopwatches = {}
self.ball = ["As I see it, yes", "It is certain", "It is decidedly so", "Most likely", "Outlook good",
"Signs point to yes", "Without a doubt", "Yes", "Yes – definitely", "You may rely on it", "Reply hazy, try again",
"Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again",
"Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful", "My cat is agree"]
self.poll_sessions = []

@commands.command(hidden=True)
async def ping(self):
"""Pong."""
await self.bot.say(":cat: Meow ?")