diff --git a/discord/__main__.py b/discord/__main__.py new file mode 100644 index 000000000..54b244f36 --- /dev/null +++ b/discord/__main__.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- + +""" +The MIT License (MIT) + +Copyright (c) 2015-2017 Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +import discord +import argparse +import sys +from pathlib import Path +import os +import re + +def core(parser, args): + pass + +bot_template = """#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from discord.ext import commands +import discord +import config + +class Bot(commands.{base}): + def __init__(self, **kwargs): + super().__init__(command_prefix=commands.when_mentioned_or('{prefix}'), **kwargs) + for cog in config.cogs: + try: + self.load_extension(cog) + except Exception as e: + print('Could not load extension {{0}} due to {{1.__class__.__name__}}: {{1}}'.format(cog, e)) + + async def on_ready(self): + print('Logged on as {{0}} (ID: {{0.id}})'.format(self)) + + +bot = Bot() + +# write general commands here + +bot.run(config.token) +""" + +gitignore_template = """# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Our configuration files +config.py +""" + +cog_template = '''# -*- coding: utf-8 -*- + +from discord.ext import commands +import discord + +class {name}: + """The description for {name} goes here.""" + + def __init__(self, bot): + self.bot = bot +{extra} +def setup(bot): + bot.add_cog({name}(bot)) +''' + +cog_extras = ''' + def __unload(self): + # clean up logic goes here + pass + + async def __local_check(self, ctx): + # checks that apply to every command in here + return True + + async def __global_check(self, ctx): + # checks that apply to every command to the bot + return True + + async def __error(self, ctx, error): + # error handling to every command in here + pass + + async def __before_invoke(self, ctx): + # called before a command is called here + pass + + async def __after_invoke(self, ctx): + # called after a command is called here + pass + +''' + + +# certain file names and directory names are forbidden +# see: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx +# although some of this doesn't apply to Linux, we might as well be consistent +_base_table = { + '<': '-', + '>': '-', + ':': '-', + '"': '-', + # '/': '-', these are fine + # '\\': '-', + '|': '-', + '?': '-', + '*': '-', +} + +# +_base_table.update((chr(i), None) for i in range(32)) + +translation_table = str.maketrans(_base_table) + +def to_path(parser, name, *, replace_spaces=False): + if isinstance(name, Path): + return name + + if sys.platform == 'win32': + forbidden = ('CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', \ + 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9') + if len(name) <= 4 and name.upper() in forbidden: + parser.error('invalid directory name given, use a different one') + + name = name.translate(translation_table) + if replace_spaces: + name = name.replace(' ', '-') + return Path(name) + +def newbot(parser, args): + if sys.version_info < (3, 5): + parser.error('python version is older than 3.5, consider upgrading.') + + new_directory = to_path(parser, args.directory) / to_path(parser, args.name) + + # as a note exist_ok for Path is a 3.5+ only feature + # since we already checked above that we're >3.5 + try: + new_directory.mkdir(exist_ok=True, parents=True) + except OSError as e: + parser.error('could not create our bot directory ({})'.format(e)) + + cogs = new_directory / 'cogs' + + try: + cogs.mkdir(exist_ok=True) + init = cogs / '__init__.py' + init.touch() + except OSError as e: + print('warning: could not create cogs directory ({})'.format(e)) + + try: + with open(str(new_directory / 'config.py'), 'w', encoding='utf-8') as fp: + fp.write('token = "place your token here"\ncogs = []\n') + except OSError as e: + parser.error('could not create config file ({})'.format(e)) + + try: + with open(str(new_directory / 'bot.py'), 'w', encoding='utf-8') as fp: + base = 'Bot' if not args.sharded else 'AutoShardedBot' + fp.write(bot_template.format(base=base, prefix=args.prefix)) + except OSError as e: + parser.error('could not create bot file ({})'.format(e)) + + if not args.no_git: + try: + with open(str(new_directory / '.gitignore'), 'w', encoding='utf-8') as fp: + fp.write(gitignore_template) + except OSError as e: + print('warning: could not create .gitignore file ({})'.format(e)) + + print('successfully made bot at', new_directory) + +def newcog(parser, args): + if sys.version_info < (3, 5): + parser.error('python version is older than 3.5, consider upgrading.') + + cog_dir = to_path(parser, args.directory) + try: + cog_dir.mkdir(exist_ok=True) + except OSError as e: + print('warning: could not create cogs directory ({})'.format(e)) + + directory = cog_dir / to_path(parser, args.name) + directory = directory.with_suffix('.py') + try: + with open(str(directory), 'w', encoding='utf-8') as fp: + extra = cog_extras if args.full else '' + if args.class_name: + name = args.class_name + else: + name = str(directory.stem) + if '-' in name: + name = name.replace('-', ' ').title().replace(' ', '') + else: + name = name.title() + fp.write(cog_template.format(name=name, extra=extra)) + except OSError as e: + parser.error('could not create cog file ({})'.format(e)) + else: + print('successfully made cog at', directory) + +def add_newbot_args(subparser): + parser = subparser.add_parser('newbot', help='creates a command bot project quickly') + parser.set_defaults(func=newbot) + + parser.add_argument('name', help='the bot project name') + parser.add_argument('directory', help='the directory to place it in (default: .)', nargs='?', default=Path.cwd()) + parser.add_argument('--prefix', help='the bot prefix (default: $)', default='$', metavar='') + parser.add_argument('--sharded', help='whether to use AutoShardedBot', action='store_true') + parser.add_argument('--no-git', help='do not create a .gitignore file', action='store_true', dest='no_git') + +def add_newcog_args(subparser): + parser = subparser.add_parser('newcog', help='creates a new cog template quickly') + parser.set_defaults(func=newcog) + + parser.add_argument('name', help='the cog name') + parser.add_argument('directory', help='the directory to place it in (default: cogs)', nargs='?', default=Path('cogs')) + parser.add_argument('--class-name', help='the class name of the cog (default: )', dest='class_name') + parser.add_argument('--full', help='add all special methods as well', action='store_true') + +def parse_args(): + parser = argparse.ArgumentParser(prog='discord', description='Tools for helping with discord.py') + + version = 'discord.py v{0.__version__} for Python {1[0]}.{1[1]}.{1[2]}'.format(discord, sys.version_info) + parser.add_argument('-v', '--version', action='version', version=version, help='shows the library version') + parser.set_defaults(func=core) + + subparser = parser.add_subparsers(dest='subcommand', title='subcommands') + add_newbot_args(subparser) + add_newcog_args(subparser) + return parser, parser.parse_args() + +def main(): + parser, args = parse_args() + args.func(parser, args) + +main()