PKeI}%77meeseeksbox/.__init__.py.un~VimUnDo$%0Am7vGP'p, y.version_info = (0,0,1)  XbTZ_ XbTY.version_info = (0,0,1)5PK)eINcppmeeseeksbox/.commands.py.swpb0VIM 8.0LSbXء'.bussonniermatthiasMacBook-Pro~bussonniermatthias/dev/meeseeksbox/meeseeksbox/commands.pyutf-8 3210#"! Utpj_kN+ad=j_^! Z+ d V L  [ O C \ 0 _ 4  5  Nr:e;X7mA$#l+:! process.check_returncode() print('== Cloned..') ['git', 'clone', 'https://x-access-token:{}@github.com/{}/{}'.format(atk, org_name, repo_name)]) process = subprocess.run( print('== Cloning current repository, this can take some time..') print('== Done cleaning ') subprocess.run('rm -rf {}'.format(repo_name).split(' ')) print('== Cleaning up previsous work... ') if os.path.exists(repo_name): atk = session.token() # this process can take some time, regen token # clone locally # body = pr_data['body'] merge_sha = pr_data['merge_commit_sha'] pr_data = r.json() json=None) org_name, repo_name, prnumber), 'https://api.github.com/repos/{}/{}/pulls/{}'.format( r = session.ghrequest('GET', print('== Collecting data on Pull-request...') # collect extended payload on the PR repo_name = payload['repository']['name'] org_name = payload['organization']['login'] prtitle = payload['issue']['title'] prnumber = payload['issue']['number'] # collect initial payload target_branch = argumentsdef pep8ify(*, session, payload, arguments):@admin session.post_comment(comment_url, "Hello @{user}. Waiting for your orders.".format(user=user)) user = payload['issue']['user']['login'] comment_url = payload['issue']['comments_url']def replyadmin(*, session, payload, arguments):@admin )) """ ``` Namespaces are one honking great idea -- let's do more of those! If the implementation is easy to explain, it may be a good idea. If the implementation is hard to explain, it's a bad idea. Although never is often better than *right* now. Now is better than never. Although that way may not be obvious at first unless you're Dutch. There should be one-- and preferably only one --obvious way to do it. In the face of ambiguity, refuse the temptation to guess. Unless explicitly silenced. Errors should never pass silently. Although practicality beats purity. Special cases aren't special enough to break the rules. Readability counts. Sparse is better than dense. Flat is better than nested. Complex is better than complicated. Simple is better than complex. Explicit is better than implicit. Beautiful is better than ugly. >>> import this ``` Zen of Pyton ([pep 20](https://www.python.org/dev/peps/pep-0020/)) """ dedent( session.post_comment(comment_url, comment_url = payload['issue']['comments_url']def zen(*, session, payload, arguments):@everyone from textwrap import dedent session.post_comment(comment_url, c.format(user=user)) ) ) "I'm Mr. Meeseek, @{user}, Look at meee ! ", "Look at me, @{user}, I'm Mr. Meeseeks! ", ("Helloooo @{user}, I'm Mr. Meeseeks! Look at me!", c = random.choice( user = payload['comment']['user']['login'] comment_url = payload['issue']['comments_url'] print("I'm replying to a user, look at me.")def replyuser(*, session, payload, arguments):@everyonefrom .scopes import admin, everyonefrom .utils import Session, fix_issue_body, fix_comment_body#from friendlyautopep8 import run_on_cwdimport sysimport mockimport pipesimport gitimport subprocessimport osimport random"""Define a few commands"""ad i z n=< ~ } W /  g . -  i h = i h 'url'], json={'state': 'closed'}) session.ghrequest('PATCH', payload['issue'][ 'comments_url'], body='Done as {}/{}#{}.'.format(org, repo, new_issue['number'])) session.post_comment(payload['issue'][ target_session.post_comment(new_comment_url, body=body) not_set_labels) body = "I was not able to apply the following label(s): %s " % ','.join( if not_set_labels: body, op, url, original_org, original_repo)) target_session.post_comment(new_comment_url, body=fix_comment_body( url = comment['html_url'] op = comment['user']['login'] body = comment['body'] continue if comment['id'] == request_id: for comment in original_comments: 'GET', payload['issue']['comments_url'], None).json() original_comments = session.ghrequest( new_comment_url = new_issue['comments_url'] new_issue = new_response.json() ) labels=migrate_labels original_number, migration_requester), issue_body, original_poster, original_repo, original_org, fix_issue_body( new_response = target_session.create_issue(org, repo, issue_title,ad-Nb%5   D " s r A @ $ a L p o n g >  ` ' qD ~DC<ld@=-PC@? not_set_labels = [l for l in original_labels if l not in available_labels] migrate_labels = [l for l in original_labels if l in available_labels] available_labels = [l['name'] for l in available_labels] None).json() org=org, repo=repo), 'https://api.github.com/repos/{org}/{repo}/labels'.format( available_labels = target_session.ghrequest('GET', if original_labels: original_labels = [l['name'] for l in payload['issue']['labels']] request_id = payload['comment']['id'] migration_requester = payload['comment']['user']['login'] original_number = payload['issue']['number'] original_poster = payload['issue']['user']['login'] original_repo = payload['repository']['name'] original_org return session.post_comment(payload['issue']['comments_url'], "It appears that I can't do that") if not target_session: target_session = yield org_repo org, repo = arguments.split('/') org_repo = arguments arguments = arguments[3:] if arguments.startswith('to '): """ Link to non-migrated labels. - Works through pagination of labels - Works through pagination of comments """Todo:def migrate_issue_request(*, session:Session, payload:dict, arguments:str):@admin session.ghrequest('DELETE', url.format(name=tag)) for tag in tags: url = "https://api.github.com/repos/{org}/{repo}/issues/{num}/labels/{name}".format(**locals()) name = '{name}' tags = [arg.strip() for arg in arguments.split(',')] num = payload.get('issue').get('number') repo = payload['repository']['name'] org = payload['organization']['login']def untag(*, session, payload, arguments):@admin session.ghrequest('POST', url, json=tags) tags = [arg.strip() for arg in arguments.split(',')] url = "https://api.github.com/repos/{org}/{repo}/issues/{num}/labels".format(**locals()) num = payload.get('issue').get('number') repo = payload['repository']['name'] org = payload['organization']['login']def tag(*, session, payload, arguments):@admin return new_pr.json() print('Backported as PR', new_number) new_number = new_pr.json().get('number', None) }) "base": target_branch "head": "{}:{}".format(org_name, remote_submit_branch), "body": msg, "title": "Backport PR #%i on branch %s" % (prnumber, target_branch), new_pr = session.ghrequest('POST', 'https://api.github.com/repos/{}/{}/pulls'.format(org_name, repo_name), json={ # Make the PR on GitHub # ToDO checkout master and get rid of branch repo.branches.workbranch.delete(repo, 'workbranch', force=True) repo.git.checkout('master') repo.remotes.origin.push('workbranch:{}'.format(remote_submit_branch)) print("== Pushing work....:") remote_submit_branch = 'auto-backport-of-pr-{}'.format(prnumber) # Push the backported work print("== ") print(msg) print() print("== PR #%i applied, with msg:" % prnumber) repo.git.commit('--amend', '-m', msg) msg = "Backport PR #%i: %s" % (prnumber, prtitle) + '\n\n' + description # write the commit message cmd, file=sys.stderr) print('\nPatch did not apply. Resolve conflicts (add, not commit), then re-run `%s`' % cmd = ' '.join(pipes.quote(arg) for arg in sys.argv) print('\n' + repo.git.status(), file=sys.stderr) print('\n' + e.stderr.decode('utf8', 'replace'), file=sys.stderr) except Exception as e:ad uC r,+ i  M not_set_labels = [l for l in original_labels if l not in available_labels] migrate_labels = [l for l in original_labels if l in available_labels] available_labels = [l['name'] for l in available_labels] None).json() org=org, repo=repo), 'https://api.github.com/repos/{org}/{repo}/labels'.format( available_labels = target_session.ghrequest('GET', if original_labels: original_labels = [l['name'] for l in payload['issue']['labels']] request_id = payload['comment']['id'] migration_requester = payload['comment']['user']['login'] original_number = payload['issue']['number'] original_poster = payload['issue']['user']['login'] original_repo = payload['repository']['name'] original_org = payload['organization']['login'] issue_body = payload['issue']['body'] issue_title = payload['issue']['title']ad _ed:tD R 3 2  a A 8 x q p =  \ 4  yXbGF2N+*]D%$^? h5 gf; repo.git.cherry_pick(*args) with mock.patch.dict('os.environ', {'GIT_EDITOR': 'true'}): try: args = ('-m', '1', merge_sha) print("Cherry-picking %s" % merge_sha) description = body.replace('@', ' ').replace('#', ' ') # remove mentions from description, to avoid pings: print('== All has been fetched correctly') num=prnumber, mergesha=merge_sha)) repo.remotes.origin.fetch('{mergesha}'.format( print('== Fetching Commits to backport...') repo.git.checkout('workbranch') repo.remotes.origin.fetch('refs/heads/{}:workbranch'.format(target_branch)) print('== Fetching branch to backport on ...') repo = git.Repo(repo_name) # do the backport on local filesystem subprocess.run('git config --global user.name FriendlyBot'.split(' ')) subprocess.run('git config --global user.email ipy.bot@bot.com'.split(' ')) process.check_returncode() print('== Cloned..') ['git', 'clone', 'https://x-access-token:{}@github.com/{}/{}'.format(atk, org_name, repo_name)]) process = subprocess.run( print('== Cloning current repository, this can take some time..') print('== Done cleaning ') subprocess.run('rm -rf {}'.format(repo_name).split(' ')) print('== Cleaning up previsous work... ') if os.path.exists(repo_name): atk = session.token() # this process can take some time, regen token # clone locally body = pr_data['body'] merge_sha = pr_data['merge_commit_sha'] pr_data = r.json() json=None) org_name, repo_name, prnumber), 'https://api.github.com/repos/{}/{}/pulls/{}'.format( r = session.ghrequest('GET', print('== Collecting data on Pull-request...') # collect extended payload on the PR repo_name = payload['repository']['name'] org_name = payload['organization']['login'] prtitle = payload['issue']['title'] prnumber = payload['issue']['number'] # collect initial payload target_branch = argumentsdef backport(session, payload, arguments):@admin return new_pr.json() print('Backported as PR', new_number) new_number = new_pr.json().get('number', None) }) "base": target_branch "head": "{}:{}".format(org_name, remote_submit_branch), "body": msg, "title": "Backport PR #%i on branch %s" % (prnumber, target_branch), new_pr = session.ghrequest('POST', 'https://api.github.com/repos/{}/{}/pulls'.format(org_name, repo_name), json={ # Make the PR on GitHub # ToDO checkout master and get rid of branch repo.branches.workbranch.delete(repo, 'workbranch', force=True) repo.git.checkout('master') repo.remotes.origin.push('workbranch:{}'.format(remote_submit_branch)) print("== Pushing work....:") remote_submit_branch = 'auto-backport-of-pr-{}'.format(prnumber) # Push the backported work repo.git.commit('-m', msg) msg = "Autofix pep 8 of #%i: %s" % (prnumber, prtitle) + '\n\n' # write the commit message print('== All has been fetched correctly') num=prnumber, mergesha=merge_sha)) repo.remotes.origin.fetch('{mergesha}'.format( print('== Fetching Commits to backport...') repo.git.checkout('workbranch') repo.remotes.origin.fetch('refs/heads/{}:workbranch'.format(target_branch)) print('== Fetching branch to backport on ...') repo = git.Repo(repo_name) # do the backport on local filesystem subprocess.run('git config --global user.name FriendlyBot'.split(' ')) subprocess.run('git config --global user.email ipy.bot@bot.com'.split(' '))PK eI meeseeksbox/.commands.py.un~VimUnDoeд/Rt[L- session.post_comment(comments_url, "",XbSI_#XbRI# target_session = yield org_repo5_XbSK if not target_session:5_,XbS)L- session.post_comment(comments_url, ""5_LXbS2LL session.post_comment(comments_url, "It appears that I can't do that"5_(v(XbSHLM session.post_comment(comments_url, "It appears that I can't do that")L5PK}eI_meeseeksbox/.core.py.un~VimUnDo >z9bl$G-^(?XbS_!XbS! else:5_'XbS( print(''5_:XbS: print('org/repo not found'5_?XbS? print('org/repo not found', org5_?XbS? print('org/repo not found', org5_QXbSQ print('org/repo not found', org_repo, self.auth..5_XbS5_XbS5PKeI8e.meeseeksbox/__init__.py""" MeeseeksBox Base of a framework to write stateless bots on GitHub. Mainly writte to use the (currently Beta) new GitHub "Integration" API, and handle authencation of user. """ import os import base64 from .core import Config from .core import MeeseeksBox version_info = (0,0,2) __version__ = '.'.join(map(str,version_info)) def load_config_from_env(): """ Load the configuration, for now stored in the environment """ config={} integration_id = os.environ.get('GITHUB_INTEGRATION_ID') botname = os.environ.get('GITHUB_BOT_NAME', None) if not integration_id: raise ValueError('Please set GITHUB_INTEGRATION_ID') if not botname: raise ValueError('Need to set a botname') if "@" in botname: print("Don't include @ in the botname !") botname = botname.replace('@','') at_botname = '@'+botname integration_id = int(integration_id) config['key'] = base64.b64decode(bytes(os.environ.get('B64KEY'), 'ASCII')) config['botname'] = botname config['at_botname'] = at_botname config['integration_id'] = integration_id config['webhook_secret'] = os.environ.get('WEBHOOK_SECRET') return Config(**config).validate() PKeI00meeseeksbox/commands.py""" Define a few commands """ import random import os import subprocess import git import pipes import mock import sys #from friendlyautopep8 import run_on_cwd from .utils import Session, fix_issue_body, fix_comment_body from .scopes import admin, everyone @everyone def replyuser(*, session, payload, arguments): print("I'm replying to a user, look at me.") comment_url = payload['issue']['comments_url'] user = payload['comment']['user']['login'] c = random.choice( ("Helloooo @{user}, I'm Mr. Meeseeks! Look at me!", "Look at me, @{user}, I'm Mr. Meeseeks! ", "I'm Mr. Meeseek, @{user}, Look at meee ! ", ) ) session.post_comment(comment_url, c.format(user=user)) from textwrap import dedent @everyone def zen(*, session, payload, arguments): comment_url = payload['issue']['comments_url'] session.post_comment(comment_url, dedent( """ Zen of Pyton ([pep 20](https://www.python.org/dev/peps/pep-0020/)) ``` >>> import this Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those! ``` """ )) @admin def replyadmin(*, session, payload, arguments): comment_url = payload['issue']['comments_url'] user = payload['issue']['user']['login'] session.post_comment(comment_url, "Hello @{user}. Waiting for your orders.".format(user=user)) @admin def pep8ify(*, session, payload, arguments): target_branch = arguments # collect initial payload prnumber = payload['issue']['number'] prtitle = payload['issue']['title'] org_name = payload['organization']['login'] repo_name = payload['repository']['name'] # collect extended payload on the PR print('== Collecting data on Pull-request...') r = session.ghrequest('GET', 'https://api.github.com/repos/{}/{}/pulls/{}'.format( org_name, repo_name, prnumber), json=None) pr_data = r.json() merge_sha = pr_data['merge_commit_sha'] # body = pr_data['body'] # clone locally # this process can take some time, regen token atk = session.token() if os.path.exists(repo_name): print('== Cleaning up previsous work... ') subprocess.run('rm -rf {}'.format(repo_name).split(' ')) print('== Done cleaning ') print('== Cloning current repository, this can take some time..') process = subprocess.run( ['git', 'clone', 'https://x-access-token:{}@github.com/{}/{}'.format(atk, org_name, repo_name)]) print('== Cloned..') process.check_returncode() subprocess.run('git config --global user.email ipy.bot@bot.com'.split(' ')) subprocess.run('git config --global user.name FriendlyBot'.split(' ')) # do the backport on local filesystem repo = git.Repo(repo_name) print('== Fetching branch to backport on ...') repo.remotes.origin.fetch('refs/heads/{}:workbranch'.format(target_branch)) repo.git.checkout('workbranch') print('== Fetching Commits to backport...') repo.remotes.origin.fetch('{mergesha}'.format( num=prnumber, mergesha=merge_sha)) print('== All has been fetched correctly') # write the commit message msg = "Autofix pep 8 of #%i: %s" % (prnumber, prtitle) + '\n\n' repo.git.commit('-m', msg) # Push the backported work remote_submit_branch = 'auto-backport-of-pr-{}'.format(prnumber) print("== Pushing work....:") repo.remotes.origin.push('workbranch:{}'.format(remote_submit_branch)) repo.git.checkout('master') repo.branches.workbranch.delete(repo, 'workbranch', force=True) # ToDO checkout master and get rid of branch # Make the PR on GitHub new_pr = session.ghrequest('POST', 'https://api.github.com/repos/{}/{}/pulls'.format(org_name, repo_name), json={ "title": "Backport PR #%i on branch %s" % (prnumber, target_branch), "body": msg, "head": "{}:{}".format(org_name, remote_submit_branch), "base": target_branch }) new_number = new_pr.json().get('number', None) print('Backported as PR', new_number) return new_pr.json() @admin def backport(session, payload, arguments): target_branch = arguments # collect initial payload prnumber = payload['issue']['number'] prtitle = payload['issue']['title'] org_name = payload['organization']['login'] repo_name = payload['repository']['name'] # collect extended payload on the PR print('== Collecting data on Pull-request...') r = session.ghrequest('GET', 'https://api.github.com/repos/{}/{}/pulls/{}'.format( org_name, repo_name, prnumber), json=None) pr_data = r.json() merge_sha = pr_data['merge_commit_sha'] body = pr_data['body'] # clone locally # this process can take some time, regen token atk = session.token() if os.path.exists(repo_name): print('== Cleaning up previsous work... ') subprocess.run('rm -rf {}'.format(repo_name).split(' ')) print('== Done cleaning ') print('== Cloning current repository, this can take some time..') process = subprocess.run( ['git', 'clone', 'https://x-access-token:{}@github.com/{}/{}'.format(atk, org_name, repo_name)]) print('== Cloned..') process.check_returncode() subprocess.run('git config --global user.email ipy.bot@bot.com'.split(' ')) subprocess.run('git config --global user.name FriendlyBot'.split(' ')) # do the backport on local filesystem repo = git.Repo(repo_name) print('== Fetching branch to backport on ...') repo.remotes.origin.fetch('refs/heads/{}:workbranch'.format(target_branch)) repo.git.checkout('workbranch') print('== Fetching Commits to backport...') repo.remotes.origin.fetch('{mergesha}'.format( num=prnumber, mergesha=merge_sha)) print('== All has been fetched correctly') # remove mentions from description, to avoid pings: description = body.replace('@', ' ').replace('#', ' ') print("Cherry-picking %s" % merge_sha) args = ('-m', '1', merge_sha) try: with mock.patch.dict('os.environ', {'GIT_EDITOR': 'true'}): repo.git.cherry_pick(*args) except Exception as e: print('\n' + e.stderr.decode('utf8', 'replace'), file=sys.stderr) print('\n' + repo.git.status(), file=sys.stderr) cmd = ' '.join(pipes.quote(arg) for arg in sys.argv) print('\nPatch did not apply. Resolve conflicts (add, not commit), then re-run `%s`' % cmd, file=sys.stderr) # write the commit message msg = "Backport PR #%i: %s" % (prnumber, prtitle) + '\n\n' + description repo.git.commit('--amend', '-m', msg) print("== PR #%i applied, with msg:" % prnumber) print() print(msg) print("== ") # Push the backported work remote_submit_branch = 'auto-backport-of-pr-{}'.format(prnumber) print("== Pushing work....:") repo.remotes.origin.push('workbranch:{}'.format(remote_submit_branch)) repo.git.checkout('master') repo.branches.workbranch.delete(repo, 'workbranch', force=True) # ToDO checkout master and get rid of branch # Make the PR on GitHub new_pr = session.ghrequest('POST', 'https://api.github.com/repos/{}/{}/pulls'.format(org_name, repo_name), json={ "title": "Backport PR #%i on branch %s" % (prnumber, target_branch), "body": msg, "head": "{}:{}".format(org_name, remote_submit_branch), "base": target_branch }) new_number = new_pr.json().get('number', None) print('Backported as PR', new_number) return new_pr.json() @admin def tag(*, session, payload, arguments): org = payload['organization']['login'] repo = payload['repository']['name'] num = payload.get('issue').get('number') url = "https://api.github.com/repos/{org}/{repo}/issues/{num}/labels".format(**locals()) tags = [arg.strip() for arg in arguments.split(',')] session.ghrequest('POST', url, json=tags) @admin def untag(*, session, payload, arguments): org = payload['organization']['login'] repo = payload['repository']['name'] num = payload.get('issue').get('number') tags = [arg.strip() for arg in arguments.split(',')] name = '{name}' url = "https://api.github.com/repos/{org}/{repo}/issues/{num}/labels/{name}".format(**locals()) for tag in tags: session.ghrequest('DELETE', url.format(name=tag)) @admin def migrate_issue_request(*, session:Session, payload:dict, arguments:str): """Todo: - Works through pagination of comments - Works through pagination of labels Link to non-migrated labels. """ if arguments.startswith('to '): arguments = arguments[3:] org_repo = arguments org, repo = arguments.split('/') target_session = yield org_repo if not target_session: session.post_comment(payload['issue']['comments_url'], "It appears that I can't do that") return issue_title = payload['issue']['title'] issue_body = payload['issue']['body'] original_org = payload['organization']['login'] original_repo = payload['repository']['name'] original_poster = payload['issue']['user']['login'] original_number = payload['issue']['number'] migration_requester = payload['comment']['user']['login'] request_id = payload['comment']['id'] original_labels = [l['name'] for l in payload['issue']['labels']] if original_labels: available_labels = target_session.ghrequest('GET', 'https://api.github.com/repos/{org}/{repo}/labels'.format( org=org, repo=repo), None).json() available_labels = [l['name'] for l in available_labels] migrate_labels = [l for l in original_labels if l in available_labels] not_set_labels = [l for l in original_labels if l not in available_labels] new_response = target_session.create_issue(org, repo, issue_title, fix_issue_body( issue_body, original_poster, original_repo, original_org, original_number, migration_requester), labels=migrate_labels ) new_issue = new_response.json() new_comment_url = new_issue['comments_url'] original_comments = session.ghrequest( 'GET', payload['issue']['comments_url'], None).json() for comment in original_comments: if comment['id'] == request_id: continue body = comment['body'] op = comment['user']['login'] url = comment['html_url'] target_session.post_comment(new_comment_url, body=fix_comment_body( body, op, url, original_org, original_repo)) if not_set_labels: body = "I was not able to apply the following label(s): %s " % ','.join( not_set_labels) target_session.post_comment(new_comment_url, body=body) session.post_comment(payload['issue'][ 'comments_url'], body='Done as {}/{}#{}.'.format(org, repo, new_issue['number'])) session.ghrequest('PATCH', payload['issue'][ 'url'], json={'state': 'closed'}) PKeIux meeseeksbox/core.pyimport re import os import hmac import tornado.web import tornado.httpserver import tornado.ioloop from .utils import Authenticator from yieldbreaker import YieldBreaker class Config: botname = None integration_id = None key = None botname = None at_botname = None integration_id = None webhook_secret = None def __init__(self, **kwargs): self.__dict__.update(kwargs) def validate(self): missing = [attr for attr in dir(self) if not attr.startswith( '_') and getattr(self, attr) is None] if missing: raise ValueError( 'The followingg configuration options are missing : {}'.format(missing)) return self def verify_signature(payload, signature, secret): """ Make sure hooks are encoded correctly """ expected = 'sha1=' + hmac.new(secret.encode('ascii'), payload, 'sha1').hexdigest() return hmac.compare_digest(signature, expected) class BaseHandler(tornado.web.RequestHandler): def error(self, message): self.set_status(500) self.write({'status': 'error', 'message': message}) def success(self, message='', payload={}): self.write({'status': 'success', 'message': message, 'data': payload}) class MainHandler(BaseHandler): def get(self): self.finish('No') def process_mentionning_comment(body, bot_re): """ Given a comment body and a bot name parse this into a tuple of (command, arguments) """ lines = body.splitlines() lines = [l.strip() for l in lines if bot_re.search(l)] lines = [bot_re.split(l)[-1].strip() for l in lines] command_args = [l.split(' ', 1) for l in lines] command_args = [c if len(c) > 1 else (c[0], None) for c in command_args] return command_args class WebHookHandler(MainHandler): def initialize(self, actions, config, auth, *args, **kwargs): self.actions = actions self.config = config self.auth = auth super().initialize(*args, **kwargs) print('Webhook initialize got', args, kwargs) def get(self): self.getfinish("Webhook alive and listening") def post(self): if not 'X-Hub-Signature' in self.request.headers: return self.error('WebHook not configured with secret') # TODO: Extract fom X-GitHub-Event if not verify_signature(self.request.body, self.request.headers['X-Hub-Signature'], self.config.webhook_secret): return self.error('Cannot validate GitHub payload with ' 'provided WebHook secret') payload = tornado.escape.json_decode(self.request.body) org = payload.get('repository', {}).get('owner', {}).get('login') if hasattr(self.config, 'org_whitelist') and (org not in self.config.org_whitelist): print('Non allowed orgg:', org) self.error('Not allowed org.') sender = payload.get('sender', {}).get('login', {}) if hasattr(self.config, 'user_whitelist') and (sender not in self.config.user_whitelist): print('Not allowed user:', sender) self.error('Not allowed user.') action = payload.get("action", None) if payload.get('commits'): # TODO print("commits were likely pushed....") return if action: print('## dispatching request', self.request.headers.get('X-GitHub-Delivery')) return self.dispatch_action(action, payload) else: print('No action available for the webhook :', payload) @property def mention_bot_re(self): botname = self.config.botname return re.compile('@?' + re.escape(botname) + '(?:\[bot\])?', re.IGNORECASE) def dispatch_action(self, type_, payload): botname = self.config.botname # new issue/PR opened if type_ == 'opened': issue = payload.get('issue', None) if not issue: print('request has no issue key:', payload) return self.error('Not really good, request has no issue') if issue: user = payload['issue']['user']['login'] if user == self.config.botname.lower() + '[bot]': return self.finish("Not responding to self") # todo dispatch on on-open # new comment created elif type_ == 'created': comment = payload.get('comment', None) installation = payload.get('installation', None) if comment: user = payload['comment']['user']['login'] if user == botname.lower() + '[bot]': print('Not responding to self') return self.finish("Not responding to self") if '[bot]' in user: print('Not responding to another bot') return self.finish("Not responding to another bot") body = payload['comment']['body'] print('Got a comment', body) if self.mention_bot_re.findall(body): self.dispatch_on_mention(body, payload, user) else: print('Was not mentioned', self.config.botname, body, '|', user) elif installation and installation.get('account'): print('we got a new installation maybe ?!', payload) return self.finish() else: print('not handled', payload) else: print("can't deal with ", type_, "yet") def dispatch_on_mention(self, body, payload, user): # to dispatch to commands installation_id = payload['installation']['id'] org = payload['organization']['login'] repo = payload['repository']['name'] session = self.auth.session(installation_id) is_admin = session.is_collaborator(org, repo, user) command_args = process_mentionning_comment(body, self.mention_bot_re) for (command, arguments) in command_args: print(" :: treating", command, arguments) handler = self.actions.get(command, None) if handler: print(" :: testing who can use ", str(handler)) if ((handler.scope == 'admin') and is_admin) or (handler.scope == 'everyone'): print(" :: authorisation granted ", handler.scope) maybe_gen = handler( session=session, payload=payload, arguments=arguments) import types if type(maybe_gen) == types.GeneratorType: gen = YieldBreaker(maybe_gen) for org_repo in gen: torg, trepo = org_repo.split('/') session_id = self.auth.idmap.get(org_repo) if session_id: target_session = self.auth.session(session_id) if target_session.is_collaborator(torg, trepo, user): gen.send(target_session) else: gen.send(None) else: print('org/repo not found', org_repo, self.auth.id_map) gen.send(None) else: print('I Cannot let you do that') else: print('unnknown command', command) class MeeseeksBox: def __init__(self, commands, config): self.commands = commands self.port = int(os.environ.get('PORT', 5000)) self.application = None self.config = config self.auth = Authenticator(self.config.integration_id, self.config.key) self.auth._build_auth_id_mapping() def start(self): self.application = tornado.web.Application([ (r"/", MainHandler), (r"/webhook", WebHookHandler, {'actions': self.commands, 'config': self.config, 'auth': self.auth}) ]) tornado.httpserver.HTTPServer(self.application).listen(self.port) tornado.ioloop.IOLoop.instance().start() PKeI(]meeseeksbox/scopes.py""" Define various scopes """ def admin(function): function.scope='admin' return function def everyone(function): function.scope='everyone' return function PKeI_meeseeksbox/utils.py""" Utility functions to work with github. """ import jwt import datetime import json import requests import re API_COLLABORATORS_TEMPLATE = 'https://api.github.com/repos/{org}/{repo}/collaborators/{username}' ACCEPT_HEADER = 'application/vnd.github.machine-man-preview+json' """ Regular expression to relink issues/pr comments correctly. Pay attention to not relink things like foo#23 as they already point to a specific repository. """ RELINK_RE = re.compile('(?:(?<=[:,\s])|(?<=^))(#\d+)\\b') def fix_issue_body(body, original_poster, original_repo, original_org, original_number, migration_requester): """ This, for now does only simple fixes, like link to the original issue. This should be improved to quote mention of people """ body = RELINK_RE.sub('{org}/{repo}\\1'.format(org=original_org, repo=original_repo), body) return body + \ """\n\n---- \nOriginally opened as {org}/{repo}#{number} by @{reporter}, migration requested by @{requester} """.format(org=original_org, repo=original_repo, number=original_number, reporter=original_poster, requester=migration_requester) def fix_comment_body(body, original_poster, original_url, original_org, original_repo): """ This, for now does only simple fixes, like link to the original comment. This should be improved to quote mention of people """ body = RELINK_RE.sub('{org}/{repo}\\1'.format(org=original_org, repo=original_repo), body) return """[`@{op}` commented]({original_url}): {body}""".format(op=original_poster, original_url=original_url, body=body) class Authenticator: def __init__(self, integration_id, rsadata): self.since = int(datetime.datetime.now().timestamp()) self.duration = 60*10 self._token = None self.integration_id = integration_id self.rsadata = rsadata # TODO: this mapping is built at startup, we should update it when we # have new / deleted installations self.idmap = {} def session(self, installation_id): return Session(self.integration_id, self.rsadata, installation_id) def list_installations(self): """ Todo: Pagination """ response = self._integration_authenticated_request( 'GET', "https://api.github.com/integration/installations") return response.json() def _build_auth_id_mapping(self): """ Build an organisation/repo -> installation_id mappingg in order to be able to do cross repository operations. """ installations = self.list_installations() for installation in installations: iid = installation['id'] session = self.session(iid) repositories = session.ghrequest( 'GET', installation['repositories_url'], json=None).json() for repo in repositories['repositories']: self.idmap[repo['full_name']] = iid def _integration_authenticated_request(self, method, url): self.since= int(datetime.datetime.now().timestamp()) payload = dict({ 'iat': self.since, 'exp': self.since + self.duration, 'iss': self.integration_id, }) tok = jwt.encode(payload, key=self.rsadata, algorithm='RS256') headers = {'Authorization': 'Bearer {}'.format(tok.decode()), 'Accept': ACCEPT_HEADER, 'Host': 'api.github.com', 'User-Agent': 'python/requests'} req = requests.Request(method, url, headers=headers) prepared = req.prepare() with requests.Session() as s: return s.send(prepared) class Session(Authenticator): def __init__(self, integration_id, rsadata, installation_id): super().__init__(integration_id, rsadata) self.installation_id = installation_id def token(self): now = datetime.datetime.now().timestamp() if (now > self.since + self.duration-60) or (self._token is None): self.regen_token() return self._token def regen_token(self): method = 'POST' url = 'https://api.github.com/installations/%s/access_tokens'%self.installation_id resp = self._integration_authenticated_request(method, url) try: self._token = json.loads(resp.content.decode())['token'] except: raise ValueError(resp.content, url) def ghrequest(self, method, url, json=None): def prepare(): atk = self.token() headers = {'Authorization': 'Bearer {}'.format(atk), 'Accept': ACCEPT_HEADER, 'Host': 'api.github.com', 'User-Agent': 'python/requests'} req = requests.Request(method, url, headers=headers, json=json) return req.prepare() with requests.Session() as s: response = s.send(prepare()) if response.status_code == 401: self.regen_token() response = s.send(prepare()) response.raise_for_status() return response def is_collaborator(self, org, repo, username): """ Check if a user is collaborator on this repository Right now this is a boolean, there is a new API (application/vnd.github.korra-preview) with github which allows to get finer grained decision. """ get_collaborators_query = API_COLLABORATORS_TEMPLATE.format(org=org, repo=repo, username=username) resp = self.ghrequest('GET', get_collaborators_query, None) if resp.status_code == 204: return True elif resp.status_code == 404: return False else: resp.raise_for_status() def post_comment(self, comment_url, body): self.ghrequest('POST', comment_url, json={"body":body}) def get_collaborator_list(self, org, repo): get_collaborators_query = 'https://api.github.com/repos/{org}/{repo}/collaborators'.format(org=org, repo=repo) resp = self.ghrequest('GET', get_collaborators_query, None) if resp.status_code == 200: return resp.json() else: resp.raise_for_status() def create_issue(self, org:str, repo:str , title:str, body:str, *, labels=None, assignees=None): arguments = { "title": title, "body": body, } if labels: if type(labels) in (list, tuple): arguments['labels'] = labels else: raise ValueError('Labels must be a list of a tuple') if assignees: if type(assignees) in (list, tuple): arguments['assignees'] = assignees else: raise ValueError('Assignees must be a list or a tuple') return self.ghrequest('POST', 'https://api.github.com/repos/{}/{}/issues'.format(org, repo), json=arguments) PK!H;@QP!meeseeksbox-0.0.2.dist-info/WHEEL1 0 RZq+D-Dv;_[*7Fp ܦpv/fݞoL(*IPK!H<XA$meeseeksbox-0.0.2.dist-info/METADATAWmob/vspvr_aȥg.%)SJ.% C$gyfWTz9NjA^kYJ9|ק)]U/s TZq4vyS*ٜM'r7EscVzkqTV7WłVe/{7lK|Tm߶8I+s:Ùi%]Ե5~xv~AJŭr>_!0[_)ϕbigmkoVy{.ZUiƛZ?o*2 H8}o^.!.h%Nf,9/*s] qmsI;_A*%9VMaFi|R('͛Ͽsߟ'Zr`KOrHOu(H} %ɝ+Qǎ SSMJUFWυ \A8eC1rRҕ9p yp5GRShvѾ3+}kKd$U87?=}}zr|.= {.y5mQu^B͚X֩C}ރ7%zWTaZ]-BP=EM%UsYu߅f {ٍc2_2DP5?"ӡ~#ZvEvÜʄ:R&{/M)î|+O+u{d:}iG̘eēS/ h2d{;wt}m5x1al8l{/= Qjol"-Gw7 BfƎ& WYrlxe-];ގ]BljP;l#<+1Mz8W|™5{hǕP/ˉ9FV([^1x JV|4oԑ.Fbi#9 30;o9ty}/7OnQZva'"x3,\__\bdlYA>27lP2ebX9i}= bˌc:k&㈪tMA'|N `9\t/z#K'n,P_+-iK ^JvNW16- /BLiËVJ:j+hE85`e~z ~~`|.3fb8vݾL:q'o(PQ#SF&;[L64YmnjGՈ{ך>@ vaI { hJTgns1ۄMD!uF9E? JӬ|ߤ@Xc_ X/ 04Nꕔo͑Tɑtd٭:h`?B\@,Ct ༫ϙ"-{RoPKeI}%77meeseeksbox/.__init__.py.un~PK)eINcppqmeeseeksbox/.commands.py.swpPK eI rmeeseeksbox/.commands.py.un~PK}eI_|meeseeksbox/.core.py.un~PKeI8e.meeseeksbox/__init__.pyPKeI00meeseeksbox/commands.pyPKeIux meeseeksbox/core.pyPKeI(]meeseeksbox/scopes.pyPKeI_meeseeksbox/utils.pyPK!H;@QP!meeseeksbox-0.0.2.dist-info/WHEELPK!H<XA$&meeseeksbox-0.0.2.dist-info/METADATAPK!Hy/8"meeseeksbox-0.0.2.dist-info/RECORDPK e!