Skip to content
Permalink
Browse files
structure and implement basic features
  • Loading branch information
soperd committed Oct 20, 2018
1 parent d0aa774 commit a28b6cefffb25a85f97d546b3ad14b0a2a3adbeb
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 1 deletion.
@@ -3,6 +3,9 @@ __pycache__/
*.py[cod]
*$py.class

# config
config.json

# C extensions
*.so

Empty file.
@@ -1,3 +1,11 @@
{
"token": "TOKEN_HERE"
"discord": {
"token": "TOKEN_HERE",
"services": ["SERVICE_1", "SERVICE_2"]
},
"services": {
"SERVICE_1": {
"token": "SERVICE_TOKEN"
}
}
}
@@ -0,0 +1,54 @@
import json
from os import path

from frozen import FrozenClass


class ConfigDict(FrozenClass):
""" ConfigDict is a dict with attributes as it's contents. """

def __init__(self, parent=None, **kwargs):
""" Takes a dictionary and stores them as attributes. """
# get all items from kwargs and assigned them as attributes
for k, v in kwargs.items():
# if the item is a dict, embed another ConfigDict
if type(v) is dict:
setattr(self, k, ConfigDict(parent=self, **v))
else:
setattr(self, k, v)

self._parent = parent
# freeze object
self._freeze()

def __getitem__(self, attr):
""" Makes ConfigDict subscriptable. """
return getattr(self, attr)

def __contains__(self, attr):
return True if attr in self.__dict__.keys() else False


class Configuration(ConfigDict):
""" Configuration is a ConfigDict with ability to read json files. """

def __init__(self, fp):
""" Takes a file-like or filename fp and stores it as attributes. """
close = False
# if fp is str convert to file-like
if type(fp) is str:
fp = open(fp, "r")
close = True

# call super
super().__init__(parent=self, **json.load(fp))

if close:
fp.close()


# global_config is the global configuration for the file
global_config = Configuration(
path.join(path.dirname(__file__), "config.json"))


@@ -0,0 +1,28 @@
import unittest
import os.path as path

from frozen import FrozenException
from config import Configuration


class ConfigurationTestCase(unittest.TestCase):

def setUp(self):
dirname = path.dirname(__file__)
self.config_file = path.join(dirname, "config.example.json")
self.config = Configuration(self.config_file)

def test_mutation(self):
with self.assertRaises(FrozenException):
self.config.discord.token = "hello"

def test_missing_attr(self):
with self.assertRaises(AttributeError):
self.config.not_here

def test_working_attr(self):
self.assertTrue(self.config.discord.token, "TOKEN_HERE")

def test_working_subscript(self):
self.assertTrue(self.config["discord"]["token"], "TOKEN_HERE")

@@ -0,0 +1,21 @@

class FrozenException(Exception):

def __init__(self, message):
super().__init__(message)


class FrozenClass(object):

_frozen = False

def __setattr__(self, name, value):
if self._frozen:
raise FrozenException("This object is immutable")

super().__setattr__(name, value)

def _freeze(self):
self._frozen = True


@@ -0,0 +1,4 @@
from .service import Service, global_services

from .pickle import PickleService
from .weather import WeatherService
@@ -0,0 +1,20 @@
from io import BytesIO
from tempfile import NamedTemporaryFile

from PIL import Image
import requests

from .service import Service, register_service


@register_service("pickle")
class PickleService(Service):

def pickle_user(self, avatar_url: str):
res = requests.get(avatar_url)
print(avatar_url)
image = Image.open(BytesIO(res.content))
image = image.rotate(180)
temp = NamedTemporaryFile()
image.save(temp, format="jpeg")
return temp
@@ -0,0 +1,34 @@
import sys
import os
sys.path.insert(0, os.path.abspath(".."))

from config import ConfigDict, global_config


global_services = {}

def register_service(name):
global services
def wrap(c):
if name in global_config.services:
# create service using global_config
s = c(global_config.services[name])
else:
# create service without config
s = c()
global_services[name] = s
return c
return wrap


class Service(object):

def __init__(self, config=ConfigDict()):
if type(config) is dict:
config = ConfigDict(**config)
elif type(config) is not ConfigDict \
and not issubclass(type(config), ConfigDict):
raise TypeError("config must be dict or ConfigDict")

self.config = config

@@ -0,0 +1,18 @@
import requests

from .service import Service, register_service


@register_service("weather")
class WeatherService(Service):

def __init__(self, config):
super().__init__(config)

self._api_endpoint = "https://api.darksky.net/forecast/{}".format(
self.config.token)

def get_weather_at(self, long: float, lat: float):
res = requests.get("{}/{},{}?units=uk2".format(
self._api_endpoint, long, lat))
return res.json()["daily"]["summary"]
@@ -0,0 +1,14 @@
from os import path

from wrappers import DiscordBot
from config import global_config


def main():
b = DiscordBot(global_config.discord)
b.start()


if __name__ == "__main__":
main()

@@ -0,0 +1,3 @@
from .bot import ChatBot

from .discord import DiscordBot
@@ -0,0 +1,36 @@
import sys
import os
# so we can access ../config.py
sys.path.insert(0, os.path.abspath('..'))

from config import ConfigDict
from handlers import global_services


class ChatBot(object):
""" ChatBot is an interface for chat bots. """

def __init__(self, config=ConfigDict(), services=None):
if type(config) is dict:
config = ConfigDict(**config)
elif type(config) is not ConfigDict \
and not issubclass(type(config), ConfigDict):
raise TypeError("config must be dict or ConfigDict")

self.services = services
self.config = config

# get service configs
if "services" in self.config and services is None:
self.services = {
k: v
for k, v in global_services.items()
if k in self.config.services}

def start(self):
raise NotImplementedError("start is not implemented!")

def stop(self):
raise NotImplementedError("stop is not implemented!")


@@ -0,0 +1,48 @@
import discord
import asyncio

from .bot import ChatBot


class DiscordBot(ChatBot):

def start(self):
self.client = discord.Client()
# nested event handlers because self.client
# doesn't exist at class definition scope
@self.client.event
async def on_ready():
await self._ready()

@self.client.event
async def on_message(message):
await self._handle_message(message)

self.client.run(self.config.token)

async def _ready(self):
print("DiscordBot running!")

async def _handle_message(self, message):
client = self.client

if message.author.bot:
return

contents_args = message.content.split(" ")
if contents_args[0] == "~weather":
service = self.services["weather"]
await client.send_message(message.channel,
service.get_weather_at(51.94, -1.55))
elif contents_args[0] == "~pickle":
service = self.services["pickle"]
await client.send_file(message.channel,
service.pickle_user(
message.mentions[0].avatar_url.replace("webp", "jpg")),
filename="lol.jpg")

if client.user in message.mentions:
await client.send_message(message.channel,
"{} hello".format(message.author.mention))


0 comments on commit a28b6ce

Please sign in to comment.