Skip to content

Commit

Permalink
Merge pull request #113 from LanceMaverick/admin-dashboard
Browse files Browse the repository at this point in the history
Improvements to api.
  • Loading branch information
LanceMaverick committed Mar 8, 2017
2 parents 1d5e6b6 + dee0013 commit 39edab5
Show file tree
Hide file tree
Showing 15 changed files with 656 additions and 49 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Custom
beard_cache/*
db_binary_entries
*.dbbin
beards/*/config.yml
config.py
Expand Down
7 changes: 7 additions & 0 deletions beards/relay_beard/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
A beard named relay_beard for skybeard-2.

This beard is designed to relay information to the bot via the telegram bot API. When complete, it will provide endpoints like:

/relayMYSUPERSECRETKEY/sendMessage

etc..
84 changes: 84 additions & 0 deletions beards/relay_beard/python/relay_beard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import string
import random

import pyconfig

from skybeard.beards import BeardChatHandler
from skybeard.bearddbtable import BeardDBTable
from skybeard.decorators import onerror, admin
from skybeard.server import app, web


async def make_key():
"""Makes key for relay commands."""
return "".join([random.choice(string.ascii_letters) for x in range(20)])


class RelayBeard(BeardChatHandler):

__userhelp__ = """This beard sets up a relay point for telegram commands.
To use, get a key with /getrelaykey and then you can relay commands to the bot using the following syntax:
<code>&lt;hostname&gt;:&lt;port&gt;/relay&lt;key&gt;/&lt;telegram api endpoint&gt;</code>
"""

__commands__ = [
('getrelaykey', 'get_key', 'Gets key for relay commands.'),
('revokerelaykey', 'revoke_key', 'Revokes your personal relay key.')
]

# __init__ is implicit

@admin
@onerror
async def get_key(self, msg):
with type(self).key_table as table:
e = table.find_one(user_id=msg['from']['id'])
if not e:
table.insert(
dict(
user_id=msg['from']['id'],
key=await make_key()
))
e = table.find_one(user_id=msg['from']['id'])

await self.sender.sendMessage("Key is: {}".format(e['key']))

@admin
@onerror
async def revoke_key(self, msg):
with type(self).key_table as table:
e = table.find_one(user_id=msg['from']['id'])
if e:
table.delete(**e)
await self.sender.sendMessage("Key revoked.")
else:
await self.sender.sendMessage("No key to revoke.")


RelayBeard.key_table = BeardDBTable(RelayBeard, "key_table")


@app.add_route('/relay{key:[a-zA-Z]+}/{command}', methods=['GET', 'POST'])
async def relay_to_telegram(request):
command_for_telegram = request.match_info['command']
key = request.match_info['key']
with RelayBeard.key_table as table:
e = table.find_one(key=key)

session = pyconfig.get('aiohttp_session')
if e:
if await request.read():
data = await request.json()
else:
data = None
async with session.request(
request.method,
"https://api.telegram.org/bot{botkey}/{cmd}".format(
botkey=pyconfig.get('key'),
cmd=command_for_telegram),
data=data) as resp:
ret_json = await resp.json()

return web.json_response(ret_json)
5 changes: 5 additions & 0 deletions beards/relay_beard/setup_beard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from skybeard.utils import setup_beard

setup_beard(
"relay_beard",
)
330 changes: 330 additions & 0 deletions dashboard/angular.min.js

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions dashboard/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
(function(){
var app = angular.module( "APIBeardDemo", ['ng'] );
app.service(
"APIBeardService",
['$http', function APIBeardFunction($http) {
var that = this;

this.beards = [];
this.availableCommands = [];

this.setBeards = function(beards) {
this.beards = beards;
};

this.getBeards = function() {
return this.beards;
};

this.fetchBeards = function(){
$http.get("http://localhost:8000/loadedBeards").then( function(data) {
that.setBeards(data.data);
});
};

this.fetchAvailableCommands = function() {
$http.get("http://localhost:8000/availableCommands").then(
function(data) {
that.availableCommands = data.data;
});
};
}
]);

app.controller(
'PanelController',
["APIBeardService", function(APIBeardService) {
this.tab = 1;

APIBeardService.fetchBeards();
this.selectTab = function(setTab) {
this.tab = setTab;
// TODO make it not hard coded
if(this.tab === 1) {
APIBeardService.fetchBeards();
};
};

this.isSelected = function(checkTab) {
return this.tab === checkTab;
};
}]);

app.controller(
'loadedBeardsController',
['APIBeardService', function(APIBeardService) {
this.APIBeardService = APIBeardService;
}]);

app.directive('loadedBeards', function(){
return {
restrict: "E",
templateUrl: "loaded-beards.html"
};
});
})(); // End of closure
6 changes: 6 additions & 0 deletions dashboard/bootstrap.min.css

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html ng-app="APIBeardDemo">
<head>
<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript" src="https://pugjs.org/js/pug.js"></script>
<script>
var pug = require('pug')
var html = pug.renderFile("index.pug");
</script>
</head>

<body>
<section ng-controller="PanelController as panel">
<ul class="nav nav-pills navbar-center">
<li ng-class="{active:panel.isSelected(1)}">
<a href ng-click="panel.selectTab(1)">Beards</a>
</li>
</ul>
<loaded-beards ng-show="panel.isSelected(1)"></loaded-beards>
</section>
</body>
</html>
2 changes: 2 additions & 0 deletions dashboard/index.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
p
| Hello there!
22 changes: 22 additions & 0 deletions dashboard/loaded-beards.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<section ng-controller="loadedBeardsController as lbCtrl">
<button type="button"
class="btn btn-default"
ng-click="lbCtrl.APIBeardService.loadBeards()">Click me to update beard info!</button>
<div ng-show="lbCtrl.APIBeardService.getBeards().length > 0">
<ul ng-repeat="beard in lbCtrl.APIBeardService.getBeards()">
<li>{{beard}}</li>
</ul>
<h3>Available commands</h3>
<button type="button"
class="btn btn-default"
ng-click="lbCtrl.APIBeardService.fetchAvailableCommands()">
Click me to get available commands.
</button>
<ul ng-repeat="(beard, commands) in lbCtrl.APIBeardService.availableCommands">
<li>{{ beard }}</li>
<ul ng-repeat="command in commands">
<li> /{{command.command}} - {{command.hint}}</li>
</ul>
</ul>
</div>
</section>
2 changes: 2 additions & 0 deletions dashboard/start_web_server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
python -m http.server 8001
28 changes: 26 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python
import os
import aiohttp
import asyncio
import logging
import itertools
Expand Down Expand Up @@ -128,17 +129,40 @@ def main(config):
"The command /{} occurs in more than "
"one beard.".format(cmd.cmd))
all_cmds.add(cmd)

bot = telepot.aio.DelegatorBot(
pyconfig.get('key'),
list(delegator_beard_gen(Beard.beards))
)

loop = asyncio.get_event_loop()
loop.create_task(bot.message_loop())

async def bot_message_loop_and_aiothttp_session():
async with aiohttp.ClientSession() as session:
pyconfig.set('aiohttp_session', session)
await bot.message_loop()

# loop.create_task(bot.message_loop())
loop.create_task(bot_message_loop_and_aiothttp_session())

if pyconfig.get('start_server'):
from skybeard.server import app
# From https://github.com/aio-libs/aiohttp-cors
#
# Must be done after the beards are loaded.
import aiohttp_cors
# Configure default CORS settings.
cors = aiohttp_cors.setup(app, defaults={
"*": aiohttp_cors.ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
)
})

# Configure CORS on all routes.
for route in list(app.router.routes()):
cors.add(route)

handler = app.make_handler()
f = loop.create_server(handler, config.host, config.port)
Expand Down
51 changes: 29 additions & 22 deletions skybeard/beards.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
from .bearddbtable import BeardDBTable
from .logging import TelegramHandler
from .predicates import command_predicate
from skybeard.server import async_post, async_get, web
# from skybeard.server import async_post, async_get, web
from skybeard.server import app
from aiohttp import web

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -48,18 +50,18 @@ def create_command(cmd_or_pred, coro, hlp=None):
return Command(cmd_or_pred, coro, hlp)
raise TypeError("cmd_or_pred must be str or callable.")

def create_route(route, coro, method):
"""creates an endpoint for the web server"""
if method.lower() == 'post':
@async_post(route)
def handler(request):
return coro(request)
elif method.lower() == 'get':
@async_get(route)
def handler(request):
return coro(request)
else:
raise ValueError('HTTP method "{}" not supported'.format(method))
# def create_route(route, coro, method):
# """creates an endpoint for the web server"""
# if method.lower() == 'post':
# @async_post(route)
# def handler(request):
# return coro(request)
# elif method.lower() == 'get':
# @async_get(route)
# def handler(request):
# return coro(request)
# else:
# raise ValueError('HTTP method "{}" not supported'.format(method))


class Beard(type):
Expand All @@ -77,14 +79,18 @@ def __new__(mcs, name, bases, dct):
tmp = dct["__commands__"].pop(0)
dct["__commands__"].append(create_command(*tmp))

b = type.__new__(mcs, name, bases, dct)
if "__routes__" in dct:
for r in dct["__routes__"]:
tmp = list(r)
tmp[1] = getattr(b, r[1])
create_route(*tmp)
endpoint = r[1]
if isinstance(r[2], str):
methods = (r[2],)
else:
methods = r[2]
fn_to_route = getattr(mcs, r[1])

app.add_route(fn_to_route, endpoint=endpoint, methods=methods)

return b
return type.__new__(mcs, name, bases, dct)

def __init__(cls, name, bases, attrs):
# If specified as base beard, do not add to list
Expand Down Expand Up @@ -280,20 +286,21 @@ async def on_chat_message(self, msg):
else:
await getattr(self, cmd.coro)(msg)

@async_get('/loadedBeards')

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

@async_get('/availableCommands')

@app.add_route('/availableCommands', methods=['GET'])
async def available_commands(request):
d = {}
for beard in Beard.beards:
cmds = []
for cmd in beard.__commands__:
if isinstance(cmd, SlashCommand):
cmds.append(dict(command = cmd.cmd, hint = cmd.hlp))
cmds.append(dict(command=cmd.cmd, hint=cmd.hlp))
if cmds:
d[beard.__name__] = cmds

return web.json_response(d)

0 comments on commit 39edab5

Please sign in to comment.