Merge pull request #32 from dustinpianalto/development

Development
This commit is contained in:
Dusty.P 2018-05-30 09:34:31 -08:00 committed by GitHub
commit fa7a8cdff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 922 additions and 240 deletions

20
.gitignore vendored
View File

@ -1,10 +1,18 @@
Config.json
PrivateConfig.json
# Byte-compiled / optimized / DLL files
__pycache__/
# Recurse
**/__pycache__
*.py[cod]
*$py.class
# C extensions
*.so
# Object files
*.o
# Distribution / packaging
.Python
@ -103,6 +111,10 @@ venv.bak/
# mypy
.mypy_cache/
/src/config/Config.json
/src/config/PrivateConfig.json
.idea/
# Ignore dockerfiles
docker-compose.yml
.vscode/
node_modules/

View File

@ -1,53 +1,57 @@
# Sebi-Machine
Dedicated discord bot for Sebi's bot tutorial.
http://discord.gg/GWdhBSp
Dedicated Discord bot for [Sebi's bot tutorial](http://discord.gg/GWdhBSp).
## Important things to know
This bot extends the rewrite version of discord.py. A couple of variables have been added to give you easy access to a couple of objects listed here.
> self.bot.ownerlist
> `self.bot.ownerlist`
self.ownerlist can be used to retrieve a `list` of user ID's. (`int`). Those ID's belong to contributors.
> self.bot.defaultprefix
`self.ownerlist` can be used to retrieve a `list` of user ID's. (`int`). Those ID's belong to contributors.
self.defaultprefix can be used to retrieve a `str` object of the default prefix.
> self.bot.version
> `self.bot.defaultprefix`
self.version can be used to retrieve a `float` which represent the version number of the bot.
> self.bot.display_name
`self.defaultprefix` can be used to retrieve a `str` object of the default prefix.
self.display_name returns a `str` which represent the display_name of the bot.
> self.bot.mainenance
> `self.bot.version`
`self.version` can be used to retrieve a `float` which represent the version number of the bot.
> `self.bot.display_name`
`self.display_name` returns a `str` which represent the `display_name` of the bot.
> `self.bot.mainenance`
`self.maintenance` is equal to `True` or `False`. If you would like to exclude code in the master branch, use this.
Make sure this one is installed. Example:
self.maintenance is equal to `True` or `False`. If you would like to exclude code in the master branch, use this.
Make sure this one is installed.
example:
```py
if self.bot.mainenance:
print('I am in the development branch')
if not self.bot.mainenance:
print('I am in the master branch)
print('I am in the master branch')
```
With other words. self.mainenance returns False in production and True in developer modus.
In other words. `self.mainenance` returns `False` in production and `True` in developer modes.
> self.bot.embed_color
> `self.bot.embed_color`
self.embed_color can be used to use the default color of out embed theme.
```
`self.embed_color` can be used to use the default color of out embed theme.
```python
discord.Embed(title='Foo', description='bar', color=self.bot.embed_color)
```
## Docker environment
This bot is heavly based on docker. This means it will run in a container. Other words. The code will run in a jail. Dont be afraid for bugs that cause harm. or commands that could potential restarts the server. Its safe.
This bot is heavly based on docker. This means it will run in a container. Other words. The code will run in a jail. Dont be afraid for bugs that cause harm. or commands that could potential restarts the server. It's safe.
There are a couple of things to know about docker within this project.
1. Please read the docs of docker first before editing the docker files
2. If you need a pip package, place the name into requirements.txt, docker handles the rest.
3. Everything in project folder is the workfolder of the docker container
4. Initialize cogs by adding them into cogs.txt. one line is one cogfile
1. Please read the docs of docker first before editing the docker files;
2. If you need a pip package, place the name into requirements.txt: docker handles the rest;
3. Everything in project folder is the workfolder of the docker container;
4. Initialize cogs by adding them into `cogs.txt`: one line is one cogfile.
## Initialize a cog
Put your cog in `src/cogs` and edit the `cogs.txt` file. Add the filename of your cog into `cogs.txt`. No absolute path, just the name.
@ -57,7 +61,12 @@ There is a git command available provided by Dusty. `S!git pull` should pull the
If you are stuck in any way shape or form you can always contact anyone who works on this project. Dont forget to check `S!help`.
## Project links:
- http://discord.gg/GWdhBSp
- http://chillout.ueuo.com
- http://trello.com/b/x02goBbW/sebis-bot-tutorial-roadmap
- http://discord.gg/GWdhBSp
- http://chillout.ueuo.com
- http://trello.com/b/x02goBbW/sebis-bot-tutorial-roadmap
## Deploy to heroku
For testing purposes you can click the link below to build your own copy of this repo you just pick an app name fill in the config variables then switch it on in resources tab.
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Annihilator708/Sebi-Machine/tree/development)

15
app.json Normal file
View File

@ -0,0 +1,15 @@
{
"name":"sebi-machine",
"stack":"container",
"env": {
"ownerlist": {
"description": "comma seperated list of owner ids",
"required": true
},
"botkey": {
"description": "bot token",
"required": true
}
}
}

View File

@ -16,4 +16,4 @@ RUN python3.6 -m pip install --upgrade pip && \
python3.6 -m pip install -r requirements.txt && \
python3.6 -m pip install -U git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py[voice]
cmd ["python3.6","run.py"]
cmd ["python3.6","-m","src"]

3
heroku.yml Normal file
View File

@ -0,0 +1,3 @@
build:
docker:
worker: dockerfile

59
package-lock.json generated Normal file
View File

@ -0,0 +1,59 @@
{
"name": "sebi-machine",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"discord.js": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.3.2.tgz",
"integrity": "sha512-Abw9CTMX3Jb47IeRffqx2VNSnXl/OsTdQzhvbw/JnqCyqc2imAocc7pX2HoRmgKd8CgSqsjBFBneusz/E16e6A==",
"requires": {
"long": "^4.0.0",
"prism-media": "^0.0.2",
"snekfetch": "^3.6.4",
"tweetnacl": "^1.0.0",
"ws": "^4.0.0"
}
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"prism-media": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.2.tgz",
"integrity": "sha512-L6yc8P5NVG35ivzvfI7bcTYzqFV+K8gTfX9YaJbmIFfMXTs71RMnAupvTQPTCteGsiOy9QcNLkQyWjAafY/hCQ=="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"snekfetch": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz",
"integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw=="
},
"tweetnacl": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz",
"integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins="
},
"ws": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz",
"integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==",
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0"
}
}
}
}

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "sebi-machine",
"version": "1.0.0",
"description": "Dedicated Discord bot for [Sebi's bot tutorial](http://discord.gg/GWdhBSp).",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/dustinpianalto/Sebi-Machine.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/dustinpianalto/Sebi-Machine/issues"
},
"homepage": "https://github.com/dustinpianalto/Sebi-Machine#readme",
"dependencies": {
"discord.js": "^11.3.2"
}
}

View File

@ -1,3 +1,10 @@
yarl<1.2
numpy==1.14.0
uvloop
aiofiles
# aiomultiprocess
# aiosqlite
# asyncpg
# dataclasses
# cached_property
uvloop==0.9.1
aiohttp==3.2.1

83
run.py
View File

@ -1,83 +0,0 @@
# !/usr/bin/python
# -*- coding: utf8 -*-
# Import packages
import asyncio
import discord
from discord.ext import commands
import json
import traceback
import random
# Import custom files
from src.config.config import LoadConfig
# If uvloop is installed, change to that eventloop policy as it
# is more efficient
try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
del uvloop
except BaseException as ex:
print(f'Could not load uvloop. {type(ex).__name__}: {ex};',
'reverting to default impl.')
else:
print(f'Using uvloop for asyncio event loop policy.')
# Bot Class
class SebiMachine(commands.Bot, LoadConfig):
"""This discord is dedicated to http://www.discord.gg/GWdhBSp"""
def __init__(self):
# Initialize and attach config / settings
LoadConfig.__init__(self)
commands.Bot.__init__(self, command_prefix=self.defaultprefix)
# Load plugins
# Add your cog file name in this list
with open('cogs.txt', 'r') as cog_file:
cogs = cog_file.readlines()
for cog in cogs:
print(f'Loaded:{cog}')
cog = cog.replace('\n', '')
self.load_extension(f'src.cogs.{cog}')
async def on_ready(self):
"""On ready function"""
if self.maintenance:
print('MAINTENANCE ACTIVE')
async def on_command_error(self, ctx, error):
"""
The event triggered when an error is raised while invoking a command.
ctx : Context
error : Exception
"""
jokes = ["I\'m a bit tipsy, I took to many screenshots...",
"I am rushing to the 24/7 store to get myself anti-bug spray...",
"Organizing turtle race...",
"There is no better place then 127.0.0.1...",
"Recycling Hex Decimal...",
"No worry, I get fixed :^)...",
"R.I.P, press F for respect...",
"The bug repellent dit not work...",
"You found a bug in the program. Unfortunately the joke did not fit here, better luck next time..."]
# catch error
error = error.__cause__ or error
tb = traceback.format_exception(type(error), error, error.__traceback__, limit=2, chain=False)
tb = ''.join(tb)
joke = random.choice(jokes)
fmt = f'**`{self.defaultprefix}{ctx.command}`**\n{joke}\n\n**{type(error).__name__}:**:\n```py\n{tb}\n```'
simple_fmt = f'**`{self.defaultprefix}{ctx.command}`**\n{joke}\n\n**{type(error).__name__}:**:\n**`{error}`**'
await ctx.send(fmt)
if __name__ == '__main__':
client = SebiMachine()
# Make sure the key stays private.
with open('src/config/PrivateConfig.json') as fp:
PrivateConfig = json.load(fp)
fp.close()
client.run(PrivateConfig["bot-key"])

15
src/__init__.py Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
Sebi-Machine.
"""
__author__ = 'Annihilator708'
# TODO: add yourselves here. I can't remember everyones handles.
__contributors__ = (__author__, 'Neko404NotFound', 'Dusty.P', 'davfsa')
__license__ = 'MIT'
__title__ = 'Sebi-Machine'
__version__ = 'tbd'
__repository__ = f'https://github.com/{__author__}/{__title__}'
__url__ = __repository__

144
src/__main__.py Normal file
View File

@ -0,0 +1,144 @@
# !/usr/bin/python
# -*- coding: utf8 -*-
"""
App entry point.
Something meaningful here, eventually.
"""
import asyncio
import json
import logging
import random
import traceback
import os
import sys
import discord
from discord.ext import commands
from src.config.config import LoadConfig
from src.shared_libs.loggable import Loggable
from src.shared_libs.ioutils import in_here
# Init logging to output on INFO level to stderr.
logging.basicConfig(level='INFO')
# If uvloop is installed, change to that eventloop policy as it
# is more efficient
try:
# https://stackoverflow.com/a/45700730
if sys.platform == 'win32':
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
logging.warning('Detected Windows. Changing event loop to ProactorEventLoop.')
else:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
del uvloop
except BaseException as ex:
logging.warning(f'Could not load uvloop. {type(ex).__qualname__}: {ex};',
'reverting to default impl.')
else:
logging.info(f'Using uvloop for asyncio event loop policy.')
# Bot Class
# Might be worth moving this to it's own file?
class SebiMachine(commands.Bot, LoadConfig, Loggable):
"""This discord is dedicated to http://www.discord.gg/GWdhBSp"""
def __init__(self):
# Initialize and attach config / settings
LoadConfig.__init__(self)
commands.Bot.__init__(self, command_prefix=self.defaultprefix)
# Load plugins
# Add your cog file name in this list
with open(in_here('cogs.txt')) as cog_file:
cogs = cog_file.readlines()
for cog in cogs:
# Could this just be replaced with `strip()`?
cog = cog.replace('\n', '')
self.load_extension(f'src.cogs.{cog}')
self.logger.info(f'Loaded: {cog}')
async def on_ready(self):
"""On ready function"""
self.maintenance and self.logger.warning('MAINTENANCE ACTIVE')
async def on_command_error(self, ctx, error):
"""
The event triggered when an error is raised while invoking a command.
ctx : Context
error : Exception
"""
jokes = ["I\'m a bit tipsy, I took to many screenshots...",
"I am rushing to the 24/7 store to get myself anti-bug spray...",
"Organizing turtle race...",
"There is no better place then 127.0.0.1...",
"Recycling Hex Decimal...",
"No worry, I get fixed :^)...",
"R.I.P, press F for respect...",
"The bug repellent dit not work...",
"You found a bug in the program. Unfortunately the joke did not fit here, better luck next time..."]
# CommandErrors triggered by other propagating errors tend to get wrapped. This means
# if we have a cause, we should probably consider unwrapping that so we get a useful
# message.
# If command is not found, return
if isinstance(error, discord.ext.commands.errors.CommandNotFound):
return
error = error.__cause__ or error
tb = traceback.format_exception(type(error), error, error.__traceback__, limit=2, chain=False)
tb = ''.join(tb)
joke = random.choice(jokes)
fmt = f'**`{self.defaultprefix}{ctx.command}`**\n{joke}\n\n**{type(error).__name__}:**:\n```py\n{tb}\n```'
# Stops the error handler erroring.
try:
await ctx.send(fmt)
except:
traceback.print_exc()
async def on_message(self, message):
# Make sure people can't change the username
if message.guild:
if message.guild.me.display_name != self.display_name:
try:
await message.guild.me.edit(nick=self.display_name)
except:
pass
else:
if ('exec' in message.content or 'repl' in message.content or 'token' in message.content) \
and message.author != self.user:
await self.get_user(351794468870946827).send(f'{message.author.name} ({message.author.id}) is using me '
f'in DMs\n{message.content}')
# If author is a bot, ignore the message
if message.author.bot: return
# Make sure the command get processed as if it was typed with lowercase
# Split message.content one first space
command = message.content.split(None, 1)
if command:
command[0] = command[0].lower()
message.content = ' '.join(command)
message.content = ' '.join(command)
# process command
await self.process_commands(message)
client = SebiMachine()
# Make sure the key stays private.
# I am 99% certain this is valid!
with open(in_here('config', 'PrivateConfig.json')) as fp:
PrivateConfig = json.load(fp)
if PrivateConfig["bot-key"] == '':
PrivateConfig["bot-key"] = os.getenv('botkey')
client.run(PrivateConfig["bot-key"])

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

2
src/cogs/__init__.py Normal file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-

View File

@ -1,5 +1,7 @@
from discord.ext import commands
import traceback
import discord
import inspect
import textwrap
from contextlib import redirect_stdout
import io
@ -45,6 +47,7 @@ class REPL:
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
if body is None:
return await ctx.send(
'Please, use\n'
@ -73,12 +76,13 @@ class REPL:
exec(to_compile, env)
except SyntaxError as e:
try:
msg = await ctx.send(f'```py\n{self.get_syntax_error(e)}\n```')
await ctx.send(f'```py\n{self.get_syntax_error(e)}\n```')
except Exception as e:
error = [self.get_syntax_error(e)[i:i+2000] for i in range(0, len(self.get_syntax_error(e)), 2000)]
error = [self.get_syntax_error(e)[i:i + 2000] for i in
range(0, len(self.get_syntax_error(e)), 2000)]
for i in error:
msg = await ctx.send(f'```py\n{i}\n```')
await ctx.send(f'```py\n{i}\n```')
func = env['func']
try:
@ -87,40 +91,141 @@ class REPL:
except Exception as e:
value = stdout.getvalue()
try:
msg = await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
except Exception as e:
error = [value[i:i + 2000] for i in range(0, len(value), 2000)]
for i in error:
await ctx.send(f'```py\n{i}\n```')
tracebackerror = [traceback.format_exc()[i:i + 2000] for i in range(0, len(traceback.format_exc()), 2000)]
tracebackerror = [traceback.format_exc()[i:i + 2000] for i in
range(0, len(traceback.format_exc()), 2000)]
for i in tracebackerror:
msg = await ctx.send(f'```py\n{i}\n```')
await ctx.send(f'```py\n{i}\n```')
else:
value = stdout.getvalue()
if ret is None:
if value:
try:
msg = await ctx.send(f'```py\n{value}\n```')
await ctx.send(f'```py\n{value}\n```')
except Exception as e:
code = [value[i:i + 1980] for i in range(0, len(value), 1980)]
for i in code:
msg = await ctx.send(f'```py\n{i}\n```')
await ctx.send(f'```py\n{i}\n```')
else:
self._last_result = ret
try:
code = [value[i:i + 1980] for i in range(0, len(value), 1980)]
for i in code:
msg = await ctx.send(f'```py\n{i}\n```')
await ctx.send(f'```py\n{i}\n```')
except Exception as e:
code = [value[i:i + 1980] for i in range(0, len(value), 1980)]
for i in code:
await ctx.send(f'```py\n{i}\n```')
modifyd_ret = [ret[i:i + 1980] for i in range(0, len(ret), 1980)]
for i in modifyd_ret:
msg = await ctx.send(f'```py\n{i}\n```')
await ctx.send(f'```py\n{i}\n```')
@commands.command(hidden=True)
async def repl(self, ctx):
"""
Start a interactive python shell in chat.
Only the owner of this bot can use this command.
Usage:
- repl < python code >
Example:
- repl print(205554)
"""
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
msg = ctx.message
variables = {
'ctx': ctx,
'bot': self.bot,
'message': msg,
'server': msg.guild,
'channel': msg.channel,
'author': msg.author,
'_': None,
}
if msg.channel.id in self.sessions:
msg = await ctx.send('Already running a REPL session in this channel. Exit it with `quit`.')
self.sessions.add(msg.channel.id)
await ctx.send('Enter code to execute or evaluate. `exit()` or `quit` to exit.')
while True:
response = await self.bot.wait_for('message', check=lambda m: m.content.startswith(
'`') and m.author == ctx.author and m.channel == ctx.channel)
cleaned = self.cleanup_code(response.content)
if cleaned in ('quit', 'exit', 'exit()'):
msg = await ctx.send('Exiting.')
self.sessions.remove(msg.channel.id)
return
executor = exec
if cleaned.count('\n') == 0:
# single statement, potentially 'eval'
try:
code = compile(cleaned, '<repl session>', 'eval')
except SyntaxError:
pass
else:
executor = eval
if executor is exec:
try:
code = compile(cleaned, '<repl session>', 'exec')
except SyntaxError as e:
try:
await ctx.send(f'```Python\n{self.get_syntax_error(e)}\n```')
except Exception as e:
error = [self.get_syntax_error(e)[i:i + 2000] for i in
range(0, len(self.get_syntax_error(e)), 2000)]
for i in error:
await ctx.send(f'```Python\n{i}\n```')
variables['message'] = response
fmt = None
stdout = io.StringIO()
try:
with redirect_stdout(stdout):
result = executor(code, variables)
if inspect.isawaitable(result):
result = await result
except Exception as e:
value = stdout.getvalue()
await ctx.send(f'```Python\n{value}{traceback.format_exc()}\n```')
continue
else:
value = stdout.getvalue()
if result is not None:
fmt = '{}{}'.format(value, result)
variables['_'] = result
elif value:
fmt = value
try:
if fmt is not None:
if len(fmt) > 1980:
code = [fmt[i:i + 1980] for i in range(0, len(fmt), 1980)]
for i in code:
await ctx.send(f'```py\n{i}\n```')
else:
await ctx.send(fmt)
except discord.Forbidden:
pass
except discord.HTTPException as e:
await ctx.send(f'Unexpected error: `{e}`')
def setup(bot):
bot.add_cog(REPL(bot))

View File

@ -1,9 +1,10 @@
#!/usr/bin/python
# -*- coding: <encoding name> -*-
# -*- coding: utf-8 -*-
from discord.ext import commands
import os
import discord
import traceback
import aiofiles
class Upload:
"""
@ -82,11 +83,37 @@ class Upload:
await ctx.send(f'Loaded `{extension}`.')
@commands.command()
async def err(self, ctx):
"""triggers error to test traceback"""
await ctx.send(a)
async def permunload(self, ctx, extension=None):
"""Disables permanently a cog."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
if cog is None:
return await ctx.send("Please provide a extension. Do `help permunload` for more info")
extension = extension.lower()
async with aiofiles.open("extension.txt") as fp:
lines=fp.readlines()
removed = False
async with aiofiles.open("extension.txt", "w") as fp:
for i in lines:
if i.replace("\n", "") != extension:
fp.write(i)
else:
removed = True
break
if removed is True:
try:
self.bot.unload_extension(extension)
except:
pass
return await ctx.send("Extension removed successfully")
await ctx.send("Extension not found")
def setup(bot):
bot.add_cog(Upload(bot))

View File

@ -1,5 +1,5 @@
#!/usr/bin/python
# -*- coding: <encoding name> -*-
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
@ -17,7 +17,7 @@ class CogName:
now = ctx.message.created_at
msg = await ctx.send('Pong')
sub = msg.created_at - now
await msg.edit(content=f'Pong, {sub.total_seconds() * 1000}')
await msg.edit(content=f'🏓Pong, **{sub.total_seconds() * 1000}ms**')
def setup(bot):

View File

@ -1,5 +1,5 @@
#!/usr/bin/python
# -*- coding: <encoding name> -*-
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord

View File

@ -29,24 +29,27 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import discord
from discord.ext import commands
import logging
from ..shared_libs.utils import paginate, run_command
from src.shared_libs.utils import paginate, run_command
from src.shared_libs.loggable import Loggable
import asyncio
git_log = logging.getLogger('git')
class Git:
class Git(Loggable):
def __init__(self, bot):
self.bot = bot
@commands.group(case_insensitive=True)
@commands.group(case_insensitive=True, invoke_without_command=True)
async def git(self, ctx):
"""Run help git for more info"""
pass
await ctx.send('https://github.com/dustinpianalto/Sebi-Machine/')
@commands.command(case_insensitive=True, brief='Gets the Trello link.')
async def trello(self, ctx):
await ctx.send('<https://trello.com/b/x02goBbW/sebis-bot-tutorial-roadmap>')
@git.command()
async def pull(self, ctx):
self.logger.warning('Invoking git-pull')
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
@ -55,6 +58,8 @@ class Git:
color=self.bot.embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
# Pretty sure you can just do await run_command() if that is async,
# or run in a TPE otherwise.
result = await asyncio.wait_for(self.bot.loop.create_task(
run_command('git fetch --all')), 120) + '\n'
result += await asyncio.wait_for(self.bot.loop.create_task(

44
src/cogs/moderation.py Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
class Moderation:
"""
Moderation Commands
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def sar(self, ctx):
"""Assign or remove self assigned roles."""
pass
@commands.command()
async def kick(self, ctx, member: discord.Member = None):
"""
Kick a discord member from your server.
Only contributors can use this command.
Usage:
- kick <discord.member>
"""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
if member is None:
await ctx.send('Are you sure you are capable of this command?')
try:
await member.kick()
await ctx.send(f'You kicked **`{member.name}`** from **`{ctx.guild.name}`**')
except Exception as e:
await ctx.send('You may not use this command you do not have permission in server:\n\n**`{ctx.guild.name}`**'
f'\n\n```py\n{e}\n```')
def setup(bot):
bot.add_cog(Moderation(bot))

67
src/cogs/sar.js Normal file
View File

@ -0,0 +1,67 @@
const Discord = require("discord.js");
exports.run = async function(client, message, args) {
/*
aliases: sar, selfrole, selfroles
examples:
- S!selfrole get 1 (adds heroku helper role)
- S!sar remove 3 (removes rewrite helper role)
- S!sar list (shows all roles)
*/
function roleFinder(query) {
return message.guild.roles.find(function(r) {
return r.name.includes(query)
}).id;
}
const type = args[0]; // can be get, remove or list
if (type == "list" || type == undefined) {
const embed = new Discord.RichEmbed()
.setTitle("List of Self Assigned Roles")
.setDescription("Usage: `S!sar [ get | remove | list ] [ number ]`")
.addField("1. Heroku Helper", "S!sar get 1", true)
.addField("2. JS Helper", "S!sar get 2", true)
.addField("3. Rewrite Helper", "S!sar get 3", true)
.setColor("AQUA");
return message.channel.send({
embed: embed
});
}
const roles = [roleFinder("Heroku"), roleFinder("JS"), roleFinder("Rewrite")];
let choice = args[1]; // can be 1, 2 or 3
// if the choice is not 1, 2 or 3
if (/^[123]$/.test(choice) == false) {
return message.channel.send("Enter a valid role number!"); // returns error message
} else {
choice -= 1; // because array indexing starts from 0. when they choose 1 it should be roles[0]
}
switch (type) {
case "get":
message.member.addRole(roles[choice]);
message.channel.send("Added the role you specified!"); // confirmation message
break;
case "remove":
message.member.removeRole(roles[choice]);
message.channel.send("Removed the role you specified!"); // confirmation message
break;
default:
return; // when it is neither get nor remove
break;
}
}

97
src/cogs/tag.py Normal file
View File

@ -0,0 +1,97 @@
import discord
from discord.ext import commands
import json
import aiofiles
import asyncio
class Tag:
def __init__(self, bot):
self.bot = bot
with open("src/shared_libs/tags.json", "r") as fp:
json_data = fp.read()
global tags
tags = json.loads(json_data)
@commands.group(case_insensitive=True, invoke_without_command=True)
async def tag(self, ctx, tag=None):
"""Gets a tag"""
await ctx.trigger_typing()
if tag is None:
return await ctx.send('Please provide a argument. Do `help tag` for more info')
found = tags.get(tag, None)
if found is None:
return await ctx.send('Tag not found')
await ctx.send(found)
@tag.command(case_insensitive=True)
async def list(self, ctx):
"""Lists available tags"""
await ctx.trigger_typing()
desc = ""
for i in tags:
desc = desc + i + "\n"
if desc == "":
desc = "None"
em = discord.Embed(title='Available tags:', description=desc ,colour=discord.Colour(0x00FFFF))
await ctx.send(embed=em)
@tag.command(case_insensitive=True)
async def add(self, ctx, tag_name=None, *, tag_info=None):
"""Adds a new tag"""
await ctx.trigger_typing()
if not ctx.author.guild_permissions.manage_guild:
return await ctx.send("You are not allowed to do this")
if tag_name is None or tag_info is None:
return await ctx.send("Please provide a tag name and the tag info. Do `help tag` for more info")
exists = False
for i in tags:
if i == tag_name:
exists = True
if not exists:
tags.update({tag_name : tag_info})
async with aiofiles.open("src/shared_libs/tags.json", "w") as fp:
json_data = json.dumps(tags)
await fp.write(json_data)
return await ctx.send("The tag has been added")
await ctx.send("The tag already exists")
@tag.command(case_insensitive=True)
async def remove(self, ctx, tag=None):
"""Remove a existing tag"""
await ctx.trigger_typing()
if not ctx.author.guild_permissions.manage_guild:
return await ctx.send("You are not allowed to do this")
if tag is None:
return await ctx.send("Please provide a tag name and the tag info. Do `help tag` for more info")
found = None
for i in tags:
if i == tag:
found = i
if found is not None:
del tags[found]
async with aiofiles.open("src/shared_libs/tags.json", "w") as fp:
json_data = json.dumps(tags)
await fp.write(json_data)
return await ctx.send("The tag has been removed")
await ctx.send("The tag has not been found")
def setup(bot):
bot.add_cog(Tag(bot))

View File

@ -1,7 +0,0 @@
{
"version": 0.1,
"display_name" : "[S!] Sebi-Machine",
"maintenance": "True",
"ownerlist": [],
"prefix": "S!"
}

View File

@ -1,3 +0,0 @@
{
"bot-key": ""
}

2
src/config/__init__.py Normal file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-

View File

@ -3,6 +3,7 @@
import json
import discord
import os
class LoadConfig:
"""
@ -14,6 +15,7 @@ class LoadConfig:
self.config = json.load(fp)
# Initialize config
self.ownerlist = self.config["ownerlist"]
self.defaultprefix = self.config["prefix"]
self.version = self.config["version"]

View File

@ -1,5 +1,7 @@
example
contributors
tag
code
git
fun
moderation

47
src/run.js Normal file
View File

@ -0,0 +1,47 @@
const Discord = require("discord.js");
const client = new Discord.Client();
const config = require("./config/Config.json");
const prefix = config.prefix;
const aliases = require("./shared_libs/aliases.json");
const privateConfig = require("./config/PrivateConfig.json");
const fs = require("fs");
const commands = fs.readdirSync("./cogs").filter(function(e) {
return e.endsWith(".js");
});
client.on("message", function(message) {
if (message.guild.id != "265828729970753537") {
return;
}
if (message.content.startsWith(config.prefix) == false) {
return;
}
const msg = message.content.replace(prefix, "");
let command = msg.split(" ")[0];
const args = msg.split(" ").slice(1);
command = aliases[command];
try {
if (commands.includes(`${command}.js`)) {
require(`./cogs/${command}.js`).run(client, message, args);
}
} catch (err) {
// handling errors
}
});
client.login(privateConfig["bot-key"]);

View File

@ -0,0 +1,2 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
IO stuff.
"""
import copy
import inspect
import os
__all__ = ('in_here',)
def in_here(first_path_bit: str, *path_bits: str, stack_depth: int=0) -> str:
"""
A somewhat voodooish and weird piece of code. This enables us to
directly refer to a file in the same directory as the code that
calls this method, without any due regard for where in the file
system tree it is.
Apart from that, this will behave just like os.path.join, meaning
that varaidic strings will be joined with the appropriate
directory separator for the current platform.
This works by inspecting the stack frame for the caller.
If you are planning on nesting this call in another utility and wish
for the stack to refer to that caller, you should increment
the ``stack_depth`` arg for each nested call you make. By default,
you can ignore this and it will default to 0.
:param first_path_bit: the initial path bit to operate with. This is
required.
:param path_bits: additional bits of path to construct relative to here.
These are entirely optional.
:param stack_depth: optional, defaults to 0. How many nested calls
we expect this to be called in. Affects the relative directory
that is used.
:returns: the absolute path to the given relative path provided.
"""
try:
frame = inspect.stack()[1 + stack_depth]
except IndexError:
raise RuntimeError('Could not find a stack record. Interpreter has '
'been shot.')
else:
module = inspect.getmodule(frame[0])
assert hasattr(module, '__file__'), 'No `__file__\' attr, welp.'
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
# If Python caches strings rather than copying when we move them
# around or modify them, then this may cause a referential cycle which
# will consume more memory and stop the garbage collection system
# from working correctly. Best thing to do here is deepcopy anything
# we need and prevent this occuring. Del the references to allow them
# to be freed.
file = module.__file__
file = copy.deepcopy(file)
del module, frame
dir_name = os.path.dirname(file)
abs_dir_name = os.path.abspath(dir_name)
pathish = os.path.join(abs_dir_name, first_path_bit, *path_bits)
return pathish

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
Neko404NotFound 2018, MIT
A mixin class that injects a suitably named logger into class scope
at runtime.
Chosen to make this a slotted class, which means (as far as I can remember)
that it is not suitable to be made into an abc.ABC class. Slots will
enable derived slotted classes to be a bit more efficient at runtime and
boast faster lookups.
"""
import logging
__all__ = ('Loggable',)
class Loggable:
__slots__ = ('logger',)
def __init_subclass__(cls, **_):
cls.logger = logging.getLogger(cls.__qualname__)

View File

@ -0,0 +1 @@
{}