# default_exp core
API details.
#export
import json,tweepy,hmac,hashlib,traceback
from ipaddress import ip_address,ip_network
from http.server import HTTPServer, BaseHTTPRequestHandler
from fastcore.imports import *
from fastcore.foundation import *
from fastcore.utils import *
from fastcore.script import *
from configparser import ConfigParser
#export
def tweet_text(payload):
"Send a tweet announcing release based on `payload`"
rel_json = payload['release']
url = rel_json['url']
owner,repo = re.findall(r'https://api.github.com/repos/([^/]+)/([^/]+)/', url)[0]
tweet_tmpl = """New #{repo} release: v{tag_name}. {html_url}
{body}"""
res = tweet_tmpl.format(repo=repo, tag_name=rel_json['tag_name'], html_url=rel_json['html_url'], body=rel_json['body'])
if len(res)<=280: return res
return res[:279] + "…"
#export
def check_sig(content, headers, secret):
digest = hmac.new(secret, content, hashlib.sha1).hexdigest()
assert f'sha1={digest}' == headers.get('X-Hub-Signature')
#export
class _RequestHandler(BaseHTTPRequestHandler):
def _post(self):
if self.server.check_ip:
src_ip = ip_address(self.client_address[0])
assert any((src_ip in wl) for wl in self.server.whitelist)
self.send_response(200)
self.end_headers()
content = self.rfile.read(int(self.headers.get('content-length')))
if self.server.debug:
print(self.headers)
return
payload = json.loads(content.decode())
if payload['action']=='released':
check_sig(content, self.headers, self.server.gh_secret)
tweet = tweet_text(payload)
stat = self.server.api.update_status(tweet)
print(stat.id)
self.wfile.write('ok'.encode(encoding='utf_8'))
def do_POST(self):
try: self._post()
except Exception as e: sys.stderr.write(traceback.format_exc())
def log_message(self, fmt, *args): sys.stderr.write(fmt%args)
#export
@call_parse
def run_server(hostname: Param("Host name or IP", str)='localhost',
port: Param("Port to listen on", int)=8000,
debug: Param("If True, do not trigger actions, just print", bool_arg)=False,
inifile: Param("Path to settings ini file", str)='twitter.ini',
check_ip: Param("Check source IP against GitHub list", bool_arg)=True):
"Run a GitHub webhook server that tweets about new releases"
os.environ['PYTHONUNBUFFERED'] = '1'
sys.stdout.reconfigure(line_buffering=True)
sys.stderr.reconfigure(line_buffering=True)
print(f"Listening on {hostname}:{port}")
assert os.path.exists(inifile), f"{inifile} not found"
cfg = ConfigParser(interpolation=None)
cfg.read([inifile])
cfg = cfg['DEFAULT']
auth = tweepy.OAuthHandler(cfg['consumer_key'], cfg['consumer_secret'])
auth.set_access_token(cfg['access_token'], cfg['access_token_secret'])
with HTTPServer((hostname, port), _RequestHandler) as httpd:
httpd.gh_secret = bytes(cfg['gh_secret'], 'utf-8')
httpd.api = tweepy.API(auth)
httpd.whitelist = L(urljson('https://api.github.com/meta')['hooks']).map(ip_network)
httpd.check_ip,httpd.debug = check_ip,debug
httpd.serve_forever()
# run_server(check_ip=False, debug=True)
#hide
# with HTTPServer(server_address, RequestHandler) as httpd: httpd.handle_request()
# rel = Path('release.json').read_text()
# wh_json = json.loads(Path('ping.json').read_text())
# _api.destroy_status(1311413699366678529);
#export
import shutil
#export
@call_parse
def install_service(hostname: Param("Host name or IP", str)='0.0.0.0',
port: Param("Port to listen on", int)=8000,
inifile: Param("Path to settings ini file", str)='twitter.ini',
check_ip: Param("Check source IP against GitHub list", bool_arg)=True,
service_path: Param("Directory to write service file to", str)="/etc/systemd/system/"):
"Install fastwebhook as a service"
script_loc = shutil.which('fastwebhook')
inifile = Path(inifile).absolute()
_unitfile = f"""[Unit]
Description=fastwebhook
Wants=network-online.target
After=network-online.target
[Service]
ExecStart={script_loc} --inifile {inifile} --check_ip {check_ip} --hostname {hostname} --port {port}
Restart=always
[Install]
WantedBy=multi-user.target"""
Path("fastwebhook.service").write_text(_unitfile)
run_proc("sudo", "cp", "fastwebhook.service", service_path)
#hide
from nbdev.export import notebook2script
notebook2script()
Converted 00_core.ipynb. Converted index.ipynb.