-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Made Sanic server part of the core bot.
- Loading branch information
1 parent
d7c1500
commit 3a716c5
Showing
5 changed files
with
252 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')) |