Skip to content

Commit

Permalink
Made Sanic server part of the core bot.
Browse files Browse the repository at this point in the history
  • Loading branch information
natfarleydev committed Jan 19, 2017
1 parent d7c1500 commit 3a716c5
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 2 deletions.
8 changes: 6 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
pave_event_space,
include_callback_query_chat_id)

logger = logging.getLogger(__name__)

from skybeard.beards import Beard, BeardChatHandler, SlashCommand
from skybeard import server
from skybeard.help import create_help
import config

logger = logging.getLogger(__name__)


class DuplicateCommand(Exception):
pass

Expand Down Expand Up @@ -102,7 +104,9 @@ def main(config):
)

loop = asyncio.get_event_loop()
# TODO DOES NOT WORK
loop.create_task(bot.message_loop())
asyncio.ensure_future(server.start())
print('Listening ...')

loop.run_forever()
Expand Down
77 changes: 77 additions & 0 deletions skybeard/server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""This submodule is responsible for running the server.
It is made in a functional programming style. For more information see
https://en.wikipedia.org/wiki/Functional_programming.
"""

# for https://github.com/channelcat/sanic/issues/275
import asyncio
import sanic
from sanic.server import HttpProtocol
import pyconfig

from .app import app
from .telegram import setup_telegram


def _start_server(the_app, *args, **kwargs):
return the_app.run(*args, **kwargs)


# from https://github.com/channelcat/sanic/issues/275
async def run_web_app(app, port, *, loop, logger, request_timeout=20):

# Some setup for telegram. WARNING: this function does stuff behind the
# scenes!
await setup_telegram()

connections = {}
signal = sanic.server.Signal()
handler_factory=lambda: HttpProtocol(
loop=loop,
connections=connections,
signal=signal,
request_handler=app.handle_request,
request_timeout=request_timeout,
request_max_size=1024*1024
)

server = await loop.create_server(handler_factory, None, port)
for sock in server.sockets:
sockname = sock.getsockname()
logger.info("Listening on %s:%d", sockname[0], sockname[1])

try:
await asyncio.Future(loop=loop)
finally:
server.close()
await server.wait_closed()

# Complete all tasks on the loop
signal.stopped = True
for connection in connections.keys():
connection.close_if_idle()
while connections:
await asyncio.sleep(0.1, loop=loop)


async def start(debug=False):
"""Starts the Sanic server.
This functions starts the Sanic server and sets up the telegram functions
for the app to use.
Returns the started process.
"""
import logging
logger = logging.getLogger("async_sanic")

port = pyconfig.get('sanic_port', 8000)
await run_web_app(app, port, loop=asyncio.get_event_loop(), logger=logger)


# def stop():
# global proc
# return proc.stop()
64 changes: 64 additions & 0 deletions skybeard/server/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import logging

import sanic
from sanic import Sanic, Blueprint
from sanic.response import json, text
from sanic.exceptions import NotFound

try:
from sanic_cors import CORS
except ImportError:
assert False, "Missing dependency (sanic_cors). To install: pip install https://github.com/ashleysommer/sanic-cors/"

from skybeard.beards import Beard

from . import telegram as tg
from . import database
# from . import utils

logger = logging.getLogger(__name__)


app = Sanic(__name__)

# CORS: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
CORS(app)
key_blueprint = Blueprint('key', url_prefix='/key[A-z]+')


@app.route('/')
async def hello_world(request):
return text(
("Hello World! Your API beard is working! "
"Running Sanic version: {}. Beards running: {}.").format(
sanic.__version__,
Beard.beards
))


@app.route('/loadedBeards', methods=["GET"])
async def loaded_beards(request):
return json([str(x) for x in Beard.beards])


@key_blueprint.route('/relay/<method:[A-z]+>', methods=["POST", "GET"])
async def relay_tg_request(request, method):
"""Acts as a proxy for telegram's sendMessage."""
resp = await getattr(tg, request.method.lower())(
'sendMessage', data=request.json)
async with resp:
ret_json = await resp.json()

return json(ret_json)


# blueprint middleware is global! Only use app for clarity.
@app.middleware('request')
async def authentication(request):
if "key" not in request.url:
return
if not database.is_key_match(request.url):
raise NotFound(
"URL not found or key not recognised.")

app.blueprint(key_blueprint)
63 changes: 63 additions & 0 deletions skybeard/server/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import dataset
import random
import string
import functools
import logging
import re
import pyconfig

logger = logging.getLogger(__name__)


def get_all_keys():
with dataset.connect(config.db_name) as db:
table = db['keys']
return table.all()


def make_key(chat_id):
"""Make key."""
with dataset.connect(config.db_name) as db:
table = db['keys']
return table.insert(
dict(
chat_id=chat_id,
key="".join(
(random.choice(string.ascii_letters) for x in range(20))
)
)
)


@functools.lru_cache()
def get_key(chat_id):
"""Get key.
If key exists, get key. If key does not exist, create key and get it.
"""
with dataset.connect(pyconfig.get('db_name')) as db:
table = db['keys']
existing_key = table.find_one(chat_id=chat_id)
if existing_key:
return existing_key
else:
make_key(chat_id)

return get_key(chat_id)


@functools.lru_cache()
def is_key_match(url):

matches = re.findall(r"/key([A-z]+)/.*", url)
logger.debug("Matches found: {}".format(matches))
if not matches:
return

key = matches[0]
logger.debug("Key is: {}".format(key))
with dataset.connect(pyconfig.get('db_name')) as db:
table = db['keys']
if table.find_one(key=key):
return True
42 changes: 42 additions & 0 deletions skybeard/server/telegram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import aiohttp
import pyconfig


def make_bot_function(session, meth, key):
"""Makes a function for communicating with the telegram bot."""
async def g(tg_method, data=None):
"""A thin wrapper around aiohttp method for {}""".format(meth)
url = "https://api.telegram.org/bot{}/{}".format(key, tg_method)
resp = await getattr(session, meth)(
url,
data=data if data else None
)
return resp

return g


def not_implemented(*args, **kwargs):
raise NotImplementedError(
"This function has not yet been created with setup_telegram()")


post = not_implemented
get = not_implemented
_session = not_implemented


async def setup_telegram(*args):
"""Sets up telegram functions for the app to use.
NOTE: this is not a referentially transparent funtion; it relies on the
BeardChatHandler.key and should not be called until BeardChatHandler has
been set up.
"""
global _session
global post
global get
_session = aiohttp.ClientSession()
post = make_bot_function(_session, 'post', pyconfig.get('key'))
get = make_bot_function(_session, 'get', pyconfig.get('key'))

0 comments on commit 3a716c5

Please sign in to comment.