# default_exp core #export import json,tweepy,hmac,hashlib 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()