<commit_before>import os
from flask import Flask, request
from flask.ext import restful  # @UnresolvedImport
import github
from apscheduler.schedulers.background import BackgroundScheduler
import requests
from datetime import datetime, timedelta
import sys
from random import randrange

app = Flask(
    __name__,
    template_folder='../templates',
    static_folder='../static'
)
api = restful.Api(app)

def getReviewerMotivation():
    motivations = [
        'A proper review is appreciated',
        'Some warm words would be nice',
        'Please look at the code',
        'I just want to get this merged ASAP',
        'Not sure why you are here',
        'I always like your reviews',
        'Who are you?',
        'Check out this awesome code change and tell them where they fucked up!'
    ]
    return motivations[randrange(len(motivations) - 1)]

def _add_comment(repo, pull_request, message):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repo)
    pull_request = repository.get_pull(pull_request)
    pull_request.create_issue_comment(message)

class PullRequest(object):
    def __init__(self, data):
        self.data = data

    def execute(self):
        print(self.data['action'])
        if self.data['action'] == 'opened':
            return self.execute_opened()
        if self.data['action'] == 'synchronize':
            return self.execute_synchronize()
        if self.data['action'] == 'edited':
            return self.execute_edited()
        if self.data['action'] == 'closed':
            return self.execute_closed()

    def execute_opened(self):
        print(self.data['pull_request']['title'])

        contributors = {contributor.author.login: contributor.total for contributor in get_contributors(self.data['repository']['id'])}
        author = self.data['pull_request']['user']['login']
        possible_reviewers = [{'name': contributor, 'total': contributors[contributor]}
                              for contributor in contributors
                              if contributor != author and contributors[contributor] >= 10]
        possible_reviewers = sorted(possible_reviewers, key=lambda reviewer: -1 * reviewer['total'])

        reviewers = []
        for i in range(2):
            if len(possible_reviewers) == 0:
                break
            reviewers.append(possible_reviewers.pop(0))
        if len(possible_reviewers) > 0:
            reviewers.append(possible_reviewers[randrange(len(possible_reviewers) - 1)])

        message = '''[democratic collaboration](https://github.com/TooAngel/democratic-collaboration)
Approved reviews will speed up the merge, request changes will slow it down.

Please review the PR to help.

'''

        # if len(reviewers) > 0:
        #     message += 'Summoning some reviewers:\n'
        #     for reviewer in reviewers:
        #         message += ' - @{}: {}\n'.format(reviewer['name'], getReviewerMotivation())

        _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_synchronize(self):
        repository = github_client.get_repo(self.data['repository']['id'])
        issue = repository.get_issue(self.data['pull_request']['number'])
        labels = [item for item in issue.labels if item.name == 'WIP']
        if labels:
            message = 'Code update recognized, countdown starts fresh.'
            _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_edited(self):
        # TODO check PR and add message that this is under voting
        # print(self.data)
        print('edited')
        print(self.data.keys())
        print(self.data['changes'])

    def execute_closed(self):
        # TODO Anything to do here?
        # print(self.data)
        # print(self.data.keys())
        pass

class Github(restful.Resource):
    def handle_push(self, data):
        print('push - ignored')
        # print(data)

    def handle_pull_request(self, data):
        pull_request = PullRequest(data)
        pull_request.execute()

    def handle_pull_request_review(self, data):
        if data['action'] == 'submitted':
            if 'state' not in data['review']:
                print('No state')
                print(self.data.keys())
                return

            if data['review']['state'] == 'commented':
                print('Review comment')
                return
            token = os.getenv('TOKEN')
            github_client = github.Github(token)
            repository = github_client.get_repo(data['repository']['id'])
            pull_request = repository.get_pull(data['pull_request']['number'])
            check_pull_request(repository, pull_request, True)

    def post(self):
        data = request.json
        header = request.headers['X-GitHub-Event']
        print(header)
        if header == 'push':
            return self.handle_push(data)
        if header == 'pull_request':
            return self.handle_pull_request(data)
        if header == 'pull_request_review':
            return self.handle_pull_request_review(data)
        print('post not handled')
        print(data.keys())

class Restart(restful.Resource):
    def get(self):
        func = request.environ.get('werkzeug.server.shutdown')
        if func is None:
            raise RuntimeError('Not running with the Werkzeug Server')
        func()

api.add_resource(Restart, '/restart/')
api.add_resource(Github, '/github/')

def get_contributors(repository_name):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repository_name)
    return repository.get_stats_contributors()

def toDateTime(value):
    return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')

def get_reviews(repo, number):
    url = 'https://api.github.com/repos/{}/pulls/{}/reviews'.format(repo.full_name, number)
    headers = {
    'Accept': 'application/vnd.github.black-cat-preview+json',
    'Authorization': 'token {}'.format(os.getenv('TOKEN'))
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 404:
        print(url)
        print(response.content)
        print('Status Code 404')
        return {}
    response.raise_for_status()
    data = response.json()
    if 'message' in data and data['message'] == 'Not Found':
        print('message: Not Found')
        return {}
    reviews_decided = [review for review in data if review['state'] != 'COMMENTED']
    last_reviews = {}
    for review in reviews_decided:
        if review['user']['login'] not in last_reviews:
            last_reviews[review['user']['login']] = review
            continue
        if toDateTime(last_reviews[review['user']['login']]['submitted_at']) < toDateTime(review['submitted_at']):
            last_reviews[review['user']['login']] = review
            continue
    return last_reviews

def mergeable_pull_request(pull_request):
    # TODO check number of commits and messages for 'fixup!'
    return not pull_request.title.startswith('[WIP]')

def check_pull_request(repository, pull_request, commentOnIssue):
    print(pull_request.title)
    contributors = {contributor.author.login: contributor.total for contributor in (get_contributors(repository.id) or {})}
    if not mergeable_pull_request(pull_request):
        issue = repository.get_issue(pull_request.number)
        labels = [item for item in issue.labels if item.name == 'WIP']
        if not labels:
            print('Set WIP label')
            issue.add_to_labels('WIP')
            issue.create_comment('DCBOT: Adding WIP label, the title is prefixed with [WIP]')
        return

    issue = repository.get_issue(pull_request.number)
    labels = [item for item in issue.labels if item.name == 'WIP']
    if labels:
        print('Remove label')
        issue.remove_from_labels(labels[0])
        # TODO should summon reviewers here or on PR create if the PR is not WIP from the start
        issue.create_comment('''DCBOT: Removing WIP label, the title is not prefixed with [WIP]''')

    author = pull_request.user.login
    possible_reviewers = {contributor: contributors[contributor] for contributor in contributors if contributor != author}

    # Sum of total number of commits, initialize votes with the authors weight
    votes_total = sum(contributors[contributor] for contributor in contributors)
    votes = 0
    if author in contributors:
        votes = contributors[author]

    reviews = get_reviews(repository, pull_request.number)
    for review in reviews:
        if review not in possible_reviewers:
            print('{} not in reviewers'.format(review))
            continue
        if reviews[review]['state'] == 'APPROVED':
            votes += possible_reviewers[review]
            continue
        if reviews[review]['state'] == 'CHANGES_REQUESTED':
            votes -= possible_reviewers[review]
            continue
        print(reviews[review]['state'])

    coefficient = 0
    if votes_total != 0:
        coefficient = float(votes) / float(votes_total)

    if coefficient < 0:
        print('Negative coefficient')
        return

    commits = pull_request.get_commits()
    commit = max(commits, key=lambda commit: commit.commit.author.date)

    issue_events = [event for event in issue.get_events() if event.event == 'unlabeled' and event.raw_data['label']['name'] == 'WIP']
    issue_events = sorted(issue_events, key=lambda event: event.created_at, reverse=True)
    last_unlabel_date = issue_events[0].created_at if len(issue_events) > 0 else datetime(1960, 1, 1)

    events = [event for event in pull_request.head.repo.get_events() if event.type == 'PushEvent' and event.payload['ref'] == 'refs/heads/{}'.format(pull_request.head.ref)]
    events = sorted(events, key=lambda event: event.created_at, reverse=True)
    last_push_date = events[0].created_at if len(events) > 0 else datetime(1960, 1, 1)
    last_commit_date = commit.commit.author.date
    # print('commit date:', last_commit_date)
    # print('unlabel date:', last_unlabel_date)
    # print('push date', last_push_date)
    max_date = max(last_commit_date, last_unlabel_date, last_push_date)
    # print('max date', max_date)
    age = datetime.now() - max_date

    # Formular:
    # 5 days base value
    # commits in pull request times 5 days
    # days_to_merge = (1 - coefficient) * calculated days
    merge_duration = timedelta(days=(1 - coefficient) * (5 + pull_request.commits * 5))
    days_to_merge = merge_duration - age
    message = '''A new review, yeah.

    Votes: {}/{}
    Coefficient: {}
    Merging in {} days {} hours
    Age {} days {} hours'''.format(votes, votes_total, coefficient, days_to_merge.days, days_to_merge.seconds / 3600, age.days, age.seconds / 3600)
    print(message)
    if commentOnIssue:
        issue.create_comment(message)

    if age >= days_to_merge:
        print('Would merge now')
        try:
            pull_request.merge()
        except Exception as e:
            # Maybe add a comment that the conflicts should be resolved
            print(e)

def check_pull_requests():
    print(datetime.now())
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repositories = ['tooangel/democratic-collaboration', 'tooangel/screeps']

    for repository_name in repositories:
        repository = github_client.get_repo(repository_name)
        for pull_request in repository.get_pulls():
            check_pull_request(repository, pull_request, False)

if __name__ == '__main__':
    sched = BackgroundScheduler()
    sched.start()

    sched.add_job(check_pull_requests, 'cron', hour='*')

    app.debug = os.getenv('DEBUG', 'false').lower() == 'true'
    app.run(host='0.0.0.0', port=int(os.getenv("PORT", 5001)))
<commit_msg>Include PR created date<commit_after>import os
from flask import Flask, request
from flask.ext import restful  # @UnresolvedImport
import github
from apscheduler.schedulers.background import BackgroundScheduler
import requests
from datetime import datetime, timedelta
import sys
from random import randrange

app = Flask(
    __name__,
    template_folder='../templates',
    static_folder='../static'
)
api = restful.Api(app)

def getReviewerMotivation():
    motivations = [
        'A proper review is appreciated',
        'Some warm words would be nice',
        'Please look at the code',
        'I just want to get this merged ASAP',
        'Not sure why you are here',
        'I always like your reviews',
        'Who are you?',
        'Check out this awesome code change and tell them where they fucked up!'
    ]
    return motivations[randrange(len(motivations) - 1)]

def _add_comment(repo, pull_request, message):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repo)
    pull_request = repository.get_pull(pull_request)
    pull_request.create_issue_comment(message)

class PullRequest(object):
    def __init__(self, data):
        self.data = data

    def execute(self):
        print(self.data['action'])
        if self.data['action'] == 'opened':
            return self.execute_opened()
        if self.data['action'] == 'synchronize':
            return self.execute_synchronize()
        if self.data['action'] == 'edited':
            return self.execute_edited()
        if self.data['action'] == 'closed':
            return self.execute_closed()

    def execute_opened(self):
        print(self.data['pull_request']['title'])

        contributors = {contributor.author.login: contributor.total for contributor in get_contributors(self.data['repository']['id'])}
        author = self.data['pull_request']['user']['login']
        possible_reviewers = [{'name': contributor, 'total': contributors[contributor]}
                              for contributor in contributors
                              if contributor != author and contributors[contributor] >= 10]
        possible_reviewers = sorted(possible_reviewers, key=lambda reviewer: -1 * reviewer['total'])

        reviewers = []
        for i in range(2):
            if len(possible_reviewers) == 0:
                break
            reviewers.append(possible_reviewers.pop(0))
        if len(possible_reviewers) > 0:
            reviewers.append(possible_reviewers[randrange(len(possible_reviewers) - 1)])

        message = '''[democratic collaboration](https://github.com/TooAngel/democratic-collaboration)
Approved reviews will speed up the merge, request changes will slow it down.

Please review the PR to help.

'''

        # if len(reviewers) > 0:
        #     message += 'Summoning some reviewers:\n'
        #     for reviewer in reviewers:
        #         message += ' - @{}: {}\n'.format(reviewer['name'], getReviewerMotivation())

        _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_synchronize(self):
        repository = github_client.get_repo(self.data['repository']['id'])
        issue = repository.get_issue(self.data['pull_request']['number'])
        labels = [item for item in issue.labels if item.name == 'WIP']
        if labels:
            message = 'Code update recognized, countdown starts fresh.'
            _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_edited(self):
        # TODO check PR and add message that this is under voting
        # print(self.data)
        print('edited')
        print(self.data.keys())
        print(self.data['changes'])

    def execute_closed(self):
        # TODO Anything to do here?
        # print(self.data)
        # print(self.data.keys())
        pass

class Github(restful.Resource):
    def handle_push(self, data):
        print('push - ignored')
        # print(data)

    def handle_pull_request(self, data):
        pull_request = PullRequest(data)
        pull_request.execute()

    def handle_pull_request_review(self, data):
        if data['action'] == 'submitted':
            if 'state' not in data['review']:
                print('No state')
                print(self.data.keys())
                return

            if data['review']['state'] == 'commented':
                print('Review comment')
                return
            token = os.getenv('TOKEN')
            github_client = github.Github(token)
            repository = github_client.get_repo(data['repository']['id'])
            pull_request = repository.get_pull(data['pull_request']['number'])
            check_pull_request(repository, pull_request, True)

    def post(self):
        data = request.json
        header = request.headers['X-GitHub-Event']
        print(header)
        if header == 'push':
            return self.handle_push(data)
        if header == 'pull_request':
            return self.handle_pull_request(data)
        if header == 'pull_request_review':
            return self.handle_pull_request_review(data)
        print('post not handled')
        print(data.keys())

class Restart(restful.Resource):
    def get(self):
        func = request.environ.get('werkzeug.server.shutdown')
        if func is None:
            raise RuntimeError('Not running with the Werkzeug Server')
        func()

api.add_resource(Restart, '/restart/')
api.add_resource(Github, '/github/')

def get_contributors(repository_name):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repository_name)
    return repository.get_stats_contributors()

def toDateTime(value):
    return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')

def get_reviews(repo, number):
    url = 'https://api.github.com/repos/{}/pulls/{}/reviews'.format(repo.full_name, number)
    headers = {
    'Accept': 'application/vnd.github.black-cat-preview+json',
    'Authorization': 'token {}'.format(os.getenv('TOKEN'))
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 404:
        print(url)
        print(response.content)
        print('Status Code 404')
        return {}
    response.raise_for_status()
    data = response.json()
    if 'message' in data and data['message'] == 'Not Found':
        print('message: Not Found')
        return {}
    reviews_decided = [review for review in data if review['state'] != 'COMMENTED']
    last_reviews = {}
    for review in reviews_decided:
        if review['user']['login'] not in last_reviews:
            last_reviews[review['user']['login']] = review
            continue
        if toDateTime(last_reviews[review['user']['login']]['submitted_at']) < toDateTime(review['submitted_at']):
            last_reviews[review['user']['login']] = review
            continue
    return last_reviews

def mergeable_pull_request(pull_request):
    # TODO check number of commits and messages for 'fixup!'
    return not pull_request.title.startswith('[WIP]')

def check_pull_request(repository, pull_request, commentOnIssue):
    print(pull_request.title)
    contributors = {contributor.author.login: contributor.total for contributor in (get_contributors(repository.id) or {})}
    if not mergeable_pull_request(pull_request):
        issue = repository.get_issue(pull_request.number)
        labels = [item for item in issue.labels if item.name == 'WIP']
        if not labels:
            print('Set WIP label')
            issue.add_to_labels('WIP')
            issue.create_comment('DCBOT: Adding WIP label, the title is prefixed with [WIP]')
        return

    issue = repository.get_issue(pull_request.number)
    labels = [item for item in issue.labels if item.name == 'WIP']
    if labels:
        print('Remove label')
        issue.remove_from_labels(labels[0])
        # TODO should summon reviewers here or on PR create if the PR is not WIP from the start
        issue.create_comment('''DCBOT: Removing WIP label, the title is not prefixed with [WIP]''')

    author = pull_request.user.login
    possible_reviewers = {contributor: contributors[contributor] for contributor in contributors if contributor != author}

    # Sum of total number of commits, initialize votes with the authors weight
    votes_total = sum(contributors[contributor] for contributor in contributors)
    votes = 0
    if author in contributors:
        votes = contributors[author]

    reviews = get_reviews(repository, pull_request.number)
    for review in reviews:
        if review not in possible_reviewers:
            print('{} not in reviewers'.format(review))
            continue
        if reviews[review]['state'] == 'APPROVED':
            votes += possible_reviewers[review]
            continue
        if reviews[review]['state'] == 'CHANGES_REQUESTED':
            votes -= possible_reviewers[review]
            continue
        print(reviews[review]['state'])

    coefficient = 0
    if votes_total != 0:
        coefficient = float(votes) / float(votes_total)

    if coefficient < 0:
        print('Negative coefficient')
        return

    commits = pull_request.get_commits()
    commit = max(commits, key=lambda commit: commit.commit.author.date)

    issue_events = [event for event in issue.get_events() if event.event == 'unlabeled' and event.raw_data['label']['name'] == 'WIP']
    issue_events = sorted(issue_events, key=lambda event: event.created_at, reverse=True)
    last_unlabel_date = issue_events[0].created_at if len(issue_events) > 0 else datetime(1960, 1, 1)

    events = [event for event in pull_request.head.repo.get_events() if event.type == 'PushEvent' and event.payload['ref'] == 'refs/heads/{}'.format(pull_request.head.ref)]
    events = sorted(events, key=lambda event: event.created_at, reverse=True)
    last_push_date = events[0].created_at if len(events) > 0 else datetime(1960, 1, 1)
    last_commit_date = commit.commit.author.date
    # print('pull request created', pull_request.created_at)
    # print('commit date:', last_commit_date)
    # print('unlabel date:', last_unlabel_date)
    # print('push date', last_push_date)
    max_date = max(last_commit_date, last_unlabel_date, last_push_date, pull_request.created_at)
    # print('max date', max_date)
    age = datetime.now() - max_date

    # Formular:
    # 5 days base value
    # commits in pull request times 5 days
    # days_to_merge = (1 - coefficient) * calculated days
    merge_duration = timedelta(days=(1 - coefficient) * (5 + pull_request.commits * 5))
    days_to_merge = merge_duration - age
    message = '''A new review, yeah.

    Votes: {}/{}
    Coefficient: {}
    Merging in {} days {} hours
    Age {} days {} hours'''.format(votes, votes_total, coefficient, days_to_merge.days, days_to_merge.seconds / 3600, age.days, age.seconds / 3600)
    print(message)
    if commentOnIssue:
        issue.create_comment(message)

    if age >= days_to_merge:
        print('Would merge now')
        try:
            pull_request.merge()
        except Exception as e:
            # Maybe add a comment that the conflicts should be resolved
            print(e)

def check_pull_requests():
    print(datetime.now())
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repositories = ['tooangel/democratic-collaboration', 'tooangel/screeps']

    for repository_name in repositories:
        repository = github_client.get_repo(repository_name)
        for pull_request in repository.get_pulls():
            check_pull_request(repository, pull_request, False)

if __name__ == '__main__':
    sched = BackgroundScheduler()
    sched.start()

    sched.add_job(check_pull_requests, 'cron', hour='*')

    app.debug = os.getenv('DEBUG', 'false').lower() == 'true'
    app.run(host='0.0.0.0', port=int(os.getenv("PORT", 5001)))
<|endoftext|>


<commit_before>import os
from flask import Flask, request
from flask.ext import restful  # @UnresolvedImport

app = Flask(
    __name__,
    template_folder='../templates',
    static_folder='../static'
)
api = restful.Api(app)

class Github(restful.Resource):
    def post(self, name):
        data = request.json
        print(data)

api.add_resource(Github, '/github/')

if __name__ == '__main__':
    app.debug = os.getenv('DEBUG', 'false').lower() == 'true'
    app.run(host='0.0.0.0', port=int(os.getenv("PORT", 5001)))
<commit_msg>Print X-GitHub-Event header<commit_after>import os
from flask import Flask, request
from flask.ext import restful  # @UnresolvedImport

app = Flask(
    __name__,
    template_folder='../templates',
    static_folder='../static'
)
api = restful.Api(app)

class Github(restful.Resource):
    def post(self):
        header = request.headers['X-GitHub-Event']
        print(header)
        data = request.json
        print(data)

api.add_resource(Github, '/github/')

if __name__ == '__main__':
    app.debug = os.getenv('DEBUG', 'false').lower() == 'true'
    app.run(host='0.0.0.0', port=int(os.getenv("PORT", 5001)))
<|endoftext|>


<commit_before>import os
from flask import Flask, request
from flask.ext import restful  # @UnresolvedImport
import github
from apscheduler.schedulers.background import BackgroundScheduler
import requests
from datetime import datetime
import sys

app = Flask(
    __name__,
    template_folder='../templates',
    static_folder='../static'
)
api = restful.Api(app)

def _add_comment(repo, pull_request, message):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repo)
    pull_request = repository.get_pull(pull_request)
    pull_request.create_issue_comment('DCBOT: {}'.format(message))

class PullRequest(object):
    def __init__(self, data):
        self.data = data

    def execute(self):
        print(self.data['action'])
        if self.data['action'] == 'opened':
            return self.execute_opened()
        if self.data['action'] == 'synchronize':
            return self.execute_synchronize()
        if self.data['action'] == 'edited':
            return self.execute_edited()
        if self.data['action'] == 'closed':
            return self.execute_closed()

    def execute_opened(self):
        # TODO check PR
        # print(self.data)
        # print(self.data.keys())
        print(self.data['pull_request']['title'])
        message = '''
This repository is under [democratic collaboration](https://github.com/TooAngel/democratic-collaboration) and will be merged automatically.

The merge decision is based on the outcome of the reviews:
 - `Approve` add the reviewer value (number of commits) to the `metric`
 - `Request changes` substract the reviewer value from the `metric`

 - `metric > 99%` merge now
 - `metric > 75%` merge in 1 day
 - `metric > 50%` merge in 3 days
 - `metric >= 0%` merge in 7 days

Please review the PR to make a good democratic decision.

This is the placeholder for reviewer summoning :-) (top two + random)
'''
        _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_synchronize(self):
        # TODO check PR
        # print(self.data)
        # print(self.data.keys())
        message = 'Code update recognized, countdown starts fresh.'
        _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_edited(self):
        # TODO check PR and add message that this is under voting
        print(self.data)
        print(self.data.keys())
        print(self.data['changes'])

    def execute_closed(self):
        # TODO Anything to do here?
        # print(self.data)
        # print(self.data.keys())
        pass

class Github(restful.Resource):
    def handle_push(self, data):
        print('push - ignored')
        # print(data)

    def handle_pull_request(self, data):
        pull_request = PullRequest(data)
        pull_request.execute()

    def handle_pull_request_review(self, data):
        if data['action'] == 'submitted':
            if 'state' not in data['review']:
                print('No state')
                print(self.data)
                return

            if data['review']['state'] == 'commented':
                # TODO take as last point for countdown
                get_contributors()
                return
            print(data['review']['state'])
            print(data['review'])
            print(data)
            print(data.keys())

            token = os.getenv('TOKEN')
            github_client = github.Github(token)
            repository = github_client.get_repo(data['repository']['id'])
            pull_request = repository.get_pull(data['pull_request']['number'])
            check_pull_request(repository, pull_request)

    def post(self):
        data = request.json
        header = request.headers['X-GitHub-Event']
        print(header)
        if header == 'push':
            return self.handle_push(data)
        if header == 'pull_request':
            return self.handle_pull_request(data)
        if header == 'pull_request_review':
            return self.handle_pull_request_review(data)
        print(data)

class Restart(restful.Resource):
    def get(self):
        func = request.environ.get('werkzeug.server.shutdown')
        if func is None:
            raise RuntimeError('Not running with the Werkzeug Server')
        func()

api.add_resource(Restart, '/restart/')
api.add_resource(Github, '/github/')

def get_contributors(repository_name):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repository_name)
    return repository.get_stats_contributors()

def toDateTime(value):
    return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')

def get_reviews(owner, repo, number):
    if not owner:
        owner = 'tooangel'
    if not repo:
        repo = 'democratic-collaboration'
    url = 'https://api.github.com/repos/{}/{}/pulls/{}/reviews'.format(owner, repo, number)
    headers = {
    'Accept': 'application/vnd.github.black-cat-preview+json',
    'Authorization': 'token {}'.format(os.getenv('TOKEN'))
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 404:
        return {}
    response.raise_for_status()
    data = response.json()
    if 'message' in data and data['message'] == 'Not Found':
        return {}
    reviews_decided = [review for review in data if review['state'] != 'COMMENTED']
    last_reviews = {}
    for review in reviews_decided:
        if review['user']['login'] not in last_reviews:
            last_reviews[review['user']['login']] = review
            continue
        if toDateTime(last_reviews[review['user']['login']]['submitted_at']) < toDateTime(review['submitted_at']):
            last_reviews[review['user']['login']] = review
            continue
    return last_reviews

def mergeable_pull_request(pull_request):
    # TODO check number of commits and messages for 'fixup!'
    return not pull_request.title.startswith('[WIP]')

def check_pull_request(repository, pull_request):
    contributors = {contributor.author.login: contributor.total for contributor in get_contributors(repository.id)}
    if not mergeable_pull_request(pull_request):
        issue = repository.get_issue(pull_request.number)
        labels = [item for item in issue.labels if item.name == 'WIP']
        if not labels:
            issue.add_to_labels('WIP')
            issue.create_comment('DCBOT: Adding WIP label, the title is prefixed with [WIP]')
        return

    issue = repository.get_issue(pull_request.number)
    labels = [item for item in issue.labels if item.name == 'WIP']
    if labels:
        issue.remove_from_labels(labels[0])
        issue.create_comment('DCBOT: Removing WIP label, the title is not prefixed with [WIP]')

    author = pull_request.user.login
    possible_reviewers = {contributor: contributors[contributor] for contributor in contributors if contributor != author}

    # Sum of total contriution without the author of the pull request
    votes_total = sum(possible_reviewers[possible_reviewer] for possible_reviewer in possible_reviewers)
    votes = 0

    reviews = get_reviews(False, False, pull_request.number)

    for review in reviews:
        if review not in possible_reviewers:
            print('{} not in reviewers'.format(review))
            continue

        if reviews[review]['state'] == 'APPROVED':
            votes += possible_reviewers[review]
            continue
        votes += possible_reviewers[review]

    print(pull_request.title)
    percentage = votes
    if votes > 0:
        percentage = votes_total / votes

    issue.create_comment('''
DCBOT: Current status percentage: {} votes: {} total: {}
    '''.format(percentage, votes, votes_total))

    if percentage > 0.99:
        print('Would merge now')
        pull_request.merge()

    # TODO chech vote percentage vs time to last event

    print(votes, votes_total)

def check_pull_requests():
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repositories = ['tooangel/democratic-collaboration', 'tooangel/screeps']

    for repository_name in repositories:
        repository = github_client.get_repo(repository_name)

        for pull_request in repository.get_pulls():
            check_pull_request(repository, pull_request)

if __name__ == '__main__':
    sched = BackgroundScheduler()
    sched.start()

    sched.add_job(check_pull_requests, 'cron', second=0, minute=0)

    app.debug = os.getenv('DEBUG', 'false').lower() == 'true'
    app.run(host='0.0.0.0', port=int(os.getenv("PORT", 5001)))
<commit_msg>Fix current status messages<commit_after>import os
from flask import Flask, request
from flask.ext import restful  # @UnresolvedImport
import github
from apscheduler.schedulers.background import BackgroundScheduler
import requests
from datetime import datetime
import sys

app = Flask(
    __name__,
    template_folder='../templates',
    static_folder='../static'
)
api = restful.Api(app)

def _add_comment(repo, pull_request, message):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repo)
    pull_request = repository.get_pull(pull_request)
    pull_request.create_issue_comment('DCBOT: {}'.format(message))

class PullRequest(object):
    def __init__(self, data):
        self.data = data

    def execute(self):
        print(self.data['action'])
        if self.data['action'] == 'opened':
            return self.execute_opened()
        if self.data['action'] == 'synchronize':
            return self.execute_synchronize()
        if self.data['action'] == 'edited':
            return self.execute_edited()
        if self.data['action'] == 'closed':
            return self.execute_closed()

    def execute_opened(self):
        # TODO check PR
        # print(self.data)
        # print(self.data.keys())
        print(self.data['pull_request']['title'])
        message = '''
This repository is under [democratic collaboration](https://github.com/TooAngel/democratic-collaboration) and will be merged automatically.

The merge decision is based on the outcome of the reviews:
 - `Approve` add the reviewer value (number of commits) to the `metric`
 - `Request changes` substract the reviewer value from the `metric`

 - `metric > 99%` merge now
 - `metric > 75%` merge in 1 day
 - `metric > 50%` merge in 3 days
 - `metric >= 0%` merge in 7 days

Please review the PR to make a good democratic decision.

This is the placeholder for reviewer summoning :-) (top two + random)
'''
        _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_synchronize(self):
        # TODO check PR
        # print(self.data)
        # print(self.data.keys())
        message = 'Code update recognized, countdown starts fresh.'
        _add_comment(self.data['repository']['id'], self.data['pull_request']['number'], message)

    def execute_edited(self):
        # TODO check PR and add message that this is under voting
        print(self.data)
        print(self.data.keys())
        print(self.data['changes'])

    def execute_closed(self):
        # TODO Anything to do here?
        # print(self.data)
        # print(self.data.keys())
        pass

class Github(restful.Resource):
    def handle_push(self, data):
        print('push - ignored')
        # print(data)

    def handle_pull_request(self, data):
        pull_request = PullRequest(data)
        pull_request.execute()

    def handle_pull_request_review(self, data):
        if data['action'] == 'submitted':
            if 'state' not in data['review']:
                print('No state')
                print(self.data)
                return

            if data['review']['state'] == 'commented':
                # TODO take as last point for countdown
                get_contributors()
                return
            print(data['review']['state'])
            print(data['review'])
            print(data)
            print(data.keys())

            token = os.getenv('TOKEN')
            github_client = github.Github(token)
            repository = github_client.get_repo(data['repository']['id'])
            pull_request = repository.get_pull(data['pull_request']['number'])
            check_pull_request(repository, pull_request)

    def post(self):
        data = request.json
        header = request.headers['X-GitHub-Event']
        print(header)
        if header == 'push':
            return self.handle_push(data)
        if header == 'pull_request':
            return self.handle_pull_request(data)
        if header == 'pull_request_review':
            return self.handle_pull_request_review(data)
        print(data)

class Restart(restful.Resource):
    def get(self):
        func = request.environ.get('werkzeug.server.shutdown')
        if func is None:
            raise RuntimeError('Not running with the Werkzeug Server')
        func()

api.add_resource(Restart, '/restart/')
api.add_resource(Github, '/github/')

def get_contributors(repository_name):
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repository = github_client.get_repo(repository_name)
    return repository.get_stats_contributors()

def toDateTime(value):
    return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')

def get_reviews(repo, number):
    url = 'https://api.github.com/repos/{}/pulls/{}/reviews'.format(repo.full_name, number)
    headers = {
    'Accept': 'application/vnd.github.black-cat-preview+json',
    'Authorization': 'token {}'.format(os.getenv('TOKEN'))
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 404:
        print(url)
        print(response.content)
        print('Status Code 404')
        return {}
    response.raise_for_status()
    data = response.json()
    if 'message' in data and data['message'] == 'Not Found':
        print('message: Not Found')
        return {}
    reviews_decided = [review for review in data if review['state'] != 'COMMENTED']
    last_reviews = {}
    for review in reviews_decided:
        if review['user']['login'] not in last_reviews:
            last_reviews[review['user']['login']] = review
            continue
        if toDateTime(last_reviews[review['user']['login']]['submitted_at']) < toDateTime(review['submitted_at']):
            last_reviews[review['user']['login']] = review
            continue
    return last_reviews

def mergeable_pull_request(pull_request):
    # TODO check number of commits and messages for 'fixup!'
    return not pull_request.title.startswith('[WIP]')

def check_pull_request(repository, pull_request):
    print(pull_request.title)
    contributors = {contributor.author.login: contributor.total for contributor in get_contributors(repository.id)}
    if not mergeable_pull_request(pull_request):
        issue = repository.get_issue(pull_request.number)
        labels = [item for item in issue.labels if item.name == 'WIP']
        if not labels:
            issue.add_to_labels('WIP')
            issue.create_comment('DCBOT: Adding WIP label, the title is prefixed with [WIP]')
        return

    issue = repository.get_issue(pull_request.number)
    labels = [item for item in issue.labels if item.name == 'WIP']
    if labels:
        issue.remove_from_labels(labels[0])
        issue.create_comment('DCBOT: Removing WIP label, the title is not prefixed with [WIP]')

    author = pull_request.user.login
    possible_reviewers = {contributor: contributors[contributor] for contributor in contributors if contributor != author}

    # Sum of total contriution without the author of the pull request
    votes_total = sum(possible_reviewers[possible_reviewer] for possible_reviewer in possible_reviewers)
    votes = 0

    reviews = get_reviews(repository, pull_request.number)
    print(reviews)
    for review in reviews:
        if review not in possible_reviewers:
            print('{} not in reviewers'.format(review))
            continue
        print(reviews[review]['state'])
        if reviews[review]['state'] == 'APPROVED':
            votes += possible_reviewers[review]
            continue
        if reviews[review]['state'] == 'CHANGES_REQUESTEDCHANGES_REQUESTED':
            votes -= possible_reviewers[review]
            continue

    percentage = votes
    if votes > 0:
        percentage = votes / votes_total

    message = '''DCBOT: Current status percentage: {} votes: {} total: {}'''.format(percentage, votes, votes_total)
    print(message)
    # issue.create_comment(message)

    if percentage > 0.99:
        print('Would merge now')
        # pull_request.merge()

    # TODO chech vote percentage vs time to last event

    print(votes, votes_total)

def check_pull_requests():
    token = os.getenv('TOKEN')
    github_client = github.Github(token)
    repositories = ['tooangel/democratic-collaboration', 'tooangel/screeps']

    for repository_name in repositories:
        repository = github_client.get_repo(repository_name)

        for pull_request in repository.get_pulls():
            check_pull_request(repository, pull_request)

if __name__ == '__main__':
    sched = BackgroundScheduler()
    sched.start()

    sched.add_job(check_pull_requests, 'cron', second=0, minute=0)

    app.debug = os.getenv('DEBUG', 'false').lower() == 'true'
    app.run(host='0.0.0.0', port=int(os.getenv("PORT", 5001)))
<|endoftext|>


<commit_before>#!/usr/bin/env python3

import time
from queue import Queue
from scapy.all import IP, DNS, DNSRR, ls
from tuntap import TunThread
import dns
import query
from packet import Packet

hostname = 'vpn.bgpat.net'

class PacketPool(Queue, dict):

    def Nop(self):
        return None

rxpool = PacketPool()
txpool = PacketPool()

class VPNServer(TunThread):
    daemon = True
    name = 'tun_server'
    addr = '192.168.200.1'
    gateway = '192.168.200.1'

    def receive(self, data):
        global rxpool
        pkt = Packet(data, hostname=hostname)
        rxpool.put(pkt)

tun = VPNServer()
tun.start()

class DNSServer(dns.ServerThread):
    daemon = True

    def receive(self, req, addr, port):
        global rxpool, txpool
        qname = req.qd.qname.decode('utf8')
        # print(qname)
        ini = query.TxInitialize(qname, hostname=hostname)
        params = ini.decode()
        if params is not None:
            print('tx initialize', params)
            txpool[(addr, params['id'])] = Packet(params['count'])
            rdata = query.Ok(count=params['count'], sequence=params['count'])
            ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=1)
            res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
            return res
        send = query.TxSend(qname, hostname=hostname)
        params = send.decode()
        if params is not None:
            print('tx send', params, txpool)
            pkt = txpool[(addr, params['id'])]
            pkt[params['sequence']] = params['data']
            remain = pkt.count - len(pkt)
            # print('remain', remain)
            if not remain:
                tun.send(pkt.unpack())
                del txpool[(addr, params['id'])]
            rdata = query.Ok(count=remain, sequence=params['sequence'])
            ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=1)
            res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
            return res
        recv = query.Receive(qname, hostname=hostname)
        params = recv.decode()
        if params is not None:
            print('recv', params, rxpool)
            if params is not None:
                pkt = rxpool[params['id']]
                if params['sequence'] in pkt:
                    del pkt[params['sequence']]
                seq = list(pkt.keys())
                # print('recv seq', seq)
                if len(seq) == 0:
                    del rxpool[params['id']]
                    rdata = query.Error()
                    rtype = 1
                else:
                    seq = seq[0]
                    rdata = query.RxSend(data=pkt[seq], sequence=seq, id=pkt.id)
                    rtype = 5
                ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=rtype)
                res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
                return res
        poll = query.Polling(qname, hostname=hostname)
        params = poll.decode()
        if params is not None:
            # print('Polling', rxpool.empty())
            if rxpool.empty():
                rdata = query.Error()
                rtype = 1
            else:
                pkt = rxpool.get()
                if not pkt.id in rxpool:
                    rdata = query.Error()
                    rtype = 1
                rxpool[pkt.id] = pkt
                rdata = query.RxInitialize(data=pkt[0], count=pkt.count, id=pkt.id)
                rxpool.task_done()
                rtype = 5
                # print('rx init', rdata)
            # print(rdata, rtype)
            ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=rtype)
            res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
            return res

dnsd = DNSServer()
dnsd.start()

while True:
    time.sleep(1)
<commit_msg>Add hostname for RX query<commit_after>#!/usr/bin/env python3

import time
from queue import Queue
from scapy.all import IP, DNS, DNSRR, ls
from tuntap import TunThread
import dns
import query
from packet import Packet

hostname = 'vpn.bgpat.net'

class PacketPool(Queue, dict):

    def Nop(self):
        return None

rxpool = PacketPool()
txpool = PacketPool()

class VPNServer(TunThread):
    daemon = True
    name = 'tun_server'
    addr = '192.168.200.1'
    gateway = '192.168.200.1'

    def receive(self, data):
        global rxpool
        pkt = Packet(data, hostname=hostname)
        rxpool.put(pkt)

tun = VPNServer()
tun.start()

class DNSServer(dns.ServerThread):
    daemon = True

    def receive(self, req, addr, port):
        global rxpool, txpool
        qname = req.qd.qname.decode('utf8')
        # print(qname)
        ini = query.TxInitialize(qname, hostname=hostname)
        params = ini.decode()
        if params is not None:
            print('tx initialize', params)
            txpool[params['id']] = Packet(params['count'])
            rdata = query.Ok(count=params['count'], sequence=params['count'])
            ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=1)
            res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
            return res
        send = query.TxSend(qname, hostname=hostname)
        params = send.decode()
        if params is not None:
            print('tx send', params, txpool)
            pkt = txpool[params['id']]
            pkt[params['sequence']] = params['data']
            remain = pkt.count - len(pkt)
            # print('remain', remain)
            if not remain:
                tun.send(pkt.unpack())
                del txpool[params['id']]
            rdata = query.Ok(count=remain, sequence=params['sequence'])
            ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=1)
            res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
            return res
        recv = query.Receive(qname, hostname=hostname)
        params = recv.decode()
        if params is not None:
            print('recv', params, rxpool)
            if params is not None:
                pkt = rxpool[params['id']]
                if params['sequence'] in pkt:
                    del pkt[params['sequence']]
                seq = list(pkt.keys())
                # print('recv seq', seq)
                if len(seq) == 0:
                    del rxpool[params['id']]
                    rdata = query.Error()
                    rtype = 1
                else:
                    seq = seq[0]
                    rdata = query.RxSend(data=pkt[seq], sequence=seq, id=pkt.id, hostname=hostname)
                    rtype = 5
                ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=rtype)
                res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
                return res
        poll = query.Polling(qname, hostname=hostname)
        params = poll.decode()
        if params is not None:
            if rxpool.empty():
                rdata = query.Error()
                rtype = 1
            else:
                pkt = rxpool.get()
                if not pkt.id in rxpool:
                    rdata = query.Error()
                    rtype = 1
                rxpool[pkt.id] = pkt
                rdata = query.RxInitialize(data=pkt[0], count=pkt.count, id=pkt.id, hostname=hostname)
                rxpool.task_done()
                rtype = 5
                print('rx init', rdata)
            print(rdata, rtype)
            ans = DNSRR(rrname=req.qd.qname, ttl=1, rdata=bytes(rdata), type=rtype)
            res = DNS(id=req.id, qr=1, qd=req.qd, an=ans)
            return res

dnsd = DNSServer()
dnsd.start()

while True:
    time.sleep(1)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""Server module."""
from __future__ import unicode_literals
import socket
import email.utils
import os
import io

def resolve_uri(uri):
    """Return request body and file type."""
    root = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'webroot')
    uri_path = os.path.join(root, uri[1:])
    file_path = uri.split('/')
    file_path = [item for item in file_path if item]
    if file_path == []:
        file_type = u'text/html'
        list_ = os.listdir(uri_path)
        compiler = u'
'
        for file in list_:
            compiler += '
{}
'.format(file, file)
        compiler += '
'
        return (compiler.encode('utf-8'), file_type)
    file_type = file_path[-1].split('.')
    try:
        if len(file_type) == 1:
            file_type = u'text/html'
            list_ = os.listdir(uri_path)
            compiler = u'
'
            for file in list_:
                compiler += '
{}
'.format(uri, file, file)
            compiler += '
'
            return (compiler.encode('utf-8'), file_type)
        file = io.open(uri_path, 'rb')
        body_content = file.read()
        file_type = file_path[-1].split('.')
        file_type = file_type[-1]
        if file_type == 'jpg' or file_type == 'png':
            return (body_content, file_type)
        else:
            return (body_content, file_type)
    except (IOError, OSError):
        print('Iam here fuck')
        return False

def parse_request(request):
    """Parse and validate request to confirm parts are correct."""
    lines = request.split('\r\n')
    words = lines[0].split()
    if lines[0][:3] == 'GET':
        pass
    else:
        raise NameError
    if lines[0][-8:] == 'HTTP/1.1':
        pass
    else:
        raise TypeError
    if len(words) == 3 and words[1][0] == '/':
        pass
    else:
        raise SyntaxError
    return words[1]

def response_ok(body_content):
    """Return 200 ok."""
    first = u'HTTP/1.1 200 OK'
    second_line = u'Content-Type:' + body_content[1] + '; charset=utf-8'
    date = u'Date: ' + email.utils.formatdate(usegmt=True)
    header_break = u''
    body = body_content[0]
    fourth_line = u'Content-Length: {}'.format(len(body))
    string_list = [first, second_line, date, fourth_line, header_break]
    string_list = '\r\n'.join(string_list) + '\r\n'
    string_list = string_list.encode('utf-8') + body
    return string_list

def response_error(error='500 Internal Server Error'):
    """Return 500 internal server error."""
    first = u'HTTP/1.1 {}'.format(error)
    second_line = u'Content-Type: text/plain; charset=utf-8'
    date = email.utils.formatdate(usegmt=True)
    header_break = u''
    body = u'The system is down'
    bytes_ = body.encode('utf-8')
    fourth_line = u'Content-Length: {}'.format(len(bytes_))
    string_list = [first, second_line, date, fourth_line, header_break, body]
    string_list = '\r\n'.join(string_list)
    return string_list

def server():
    """Return message to client."""
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    address = ('127.0.0.1', 5001)
    server.bind(address)
    server.listen(1)
    try:
        while True:
            conn, addr = server.accept()
            try:
                buffer_length = 8
                reply_complete = False
                full_string = u""
                while not reply_complete:
                    part = conn.recv(buffer_length)
                    full_string = full_string + part.decode('utf-8')
                    if len(part) < buffer_length:
                        reply_complete = True
                print(full_string)
                try:
                    uri = parse_request(full_string)
                    body_content = resolve_uri(uri)
                    if body_content:
                        conn.send(response_ok(body_content))
                    else:
                        conn.sendall(response_error(u'404 Page Not Found'))
                    conn.close()
                except NameError('Method not GET'):
                    conn.sendall(response_error(u'405 Method Not Allowed'))
                    conn.close()
                except TypeError('HTTP protol incorrect'):
                    conn.sendall(response_error(u'505 HTTP Version Not Supported'))
                    conn.close()
                except SyntaxError('URI incorrect'):
                    conn.sendall(response_error(u'404 Page Not Found'))
                    conn.close()
                except IOError('File not found'):
                    conn.sendall(response_error(u'404 Page Not Found'))
                    conn.close()
            except SystemError('Request not fully received'):
                conn.sendall(response_error())
                conn.close()
    except KeyboardInterrupt:
        conn.close()
    finally:
        server.close()

if __name__ == '__main__':
    server()
<commit_msg>refactored the response_uri function all tests working.<commit_after># -*- coding: utf-8 -*-
"""Server module."""
from __future__ import unicode_literals
import socket
import email.utils
import os
import io

def resolve_uri(uri):
    """Return request body and file type."""
    root = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'webroot')
    full_path = os.path.join(root, uri[1:])
    path_list = uri.split('/')
    path_list = [item for item in path_list if item]
    if path_list == []:
        return html_compiler(full_path)
    file_type = path_list[-1].split('.')
    try:
        if len(file_type) == 1:
            return html_compiler(full_path, uri)
        file = io.open(full_path, 'rb')
        body_content = file.read()
        file_type = file_type[-1]
        return (body_content, file_type)
    except (IOError, OSError):
        print('I am here fuck')
        return False

def html_compiler(full_path, uri=''):
    """Return a byte encoded html file."""
    file_type = u'text/html'
    list_ = os.listdir(full_path)
    html = u'
'
    for file in list_:
        html += '
{}
'.format(uri, file, file)
    html += '
'
    return (html.encode('utf-8'), file_type)

def parse_request(request):
    """Parse and validate request to confirm parts are correct."""
    lines = request.split('\r\n')
    words = lines[0].split()
    if lines[0][:3] == 'GET':
        pass
    else:
        raise NameError
    if lines[0][-8:] == 'HTTP/1.1':
        pass
    else:
        raise TypeError
    if len(words) == 3 and words[1][0] == '/':
        pass
    else:
        raise SyntaxError
    return words[1]

def response_ok(body_tuple):
    """Return 200 ok."""
    first = u'HTTP/1.1 200 OK'
    second_line = u'Content-Type:' + body_tuple[1] + '; charset=utf-8'
    date = u'Date: ' + email.utils.formatdate(usegmt=True)
    header_break = u''
    body = body_tuple[0]
    fourth_line = u'Content-Length: {}'.format(len(body))
    string_list = [first, second_line, date, fourth_line, header_break]
    string_list = '\r\n'.join(string_list) + '\r\n'
    string_list = string_list.encode('utf-8') + body
    return string_list

def response_error(error='500 Internal Server Error'):
    """Return 500 internal server error."""
    first = u'HTTP/1.1 {}'.format(error)
    second_line = u'Content-Type: text/plain; charset=utf-8'
    date = email.utils.formatdate(usegmt=True)
    header_break = u''
    body = u'The system is down'
    bytes_ = body.encode('utf-8')
    fourth_line = u'Content-Length: {}'.format(len(bytes_))
    string_list = [first, second_line, date, fourth_line, header_break, body]
    string_list = '\r\n'.join(string_list)
    return string_list

def server():
    """Return message to client."""
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    address = ('127.0.0.1', 5001)
    server.bind(address)
    server.listen(1)
    try:
        while True:
            conn, addr = server.accept()
            try:
                buffer_length = 8
                reply_complete = False
                full_string = u""
                while not reply_complete:
                    part = conn.recv(buffer_length)
                    full_string = full_string + part.decode('utf-8')
                    if len(part) < buffer_length:
                        reply_complete = True
                print(full_string)
                try:
                    uri = parse_request(full_string)
                    body_tuple = resolve_uri(uri)
                    if body_tuple:
                        conn.send(response_ok(body_tuple))
                    else:
                        conn.sendall(response_error(u'404 Page Not Found'))
                    conn.close()
                except NameError('Method not GET'):
                    conn.sendall(response_error(u'405 Method Not Allowed'))
                    conn.close()
                except TypeError('HTTP protol incorrect'):
                    conn.sendall(response_error(u'505 HTTP Version Not Supported'))
                    conn.close()
                except SyntaxError('URI incorrect'):
                    conn.sendall(response_error(u'404 Page Not Found'))
                    conn.close()
                except IOError('File not found'):
                    conn.sendall(response_error(u'404 Page Not Found'))
                    conn.close()
            except SystemError('Request not fully received'):
                conn.sendall(response_error())
                conn.close()
    except KeyboardInterrupt:
        conn.close()
    finally:
        server.close()

if __name__ == '__main__':
    server()
<|endoftext|>


<commit_before># Jacqueline Kory Westlund
# May 2016
#
# The MIT License (MIT)
#
# Copyright (c) 2016 Personal Robots Group
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import rospy # ROS
import datetime # for header times and timeouts
import time # for sleep
import logging # log messages
import Queue # for queuing messages for the main game loop
from sar_opal_msgs.msg import OpalCommand # ROS msgs to talk to game
from sar_opal_msgs.msg import OpalAction # ROS msgs for game actions
from sar_robot_command_msgs.msg import RobotCommand # ROS msgs for robot cmd
from sar_robot_command_msgs.msg import RobotState # ROS msgs for robot state
from std_msgs.msg import Header # standard ROS msg header
from sar_game_command_msgs.msg import GameState # ROS msgs for game state
from sar_game_command_msgs.msg import GameCommand # ROS msgs for game commands

class ss_ros():
    # ROS node
    # set up rostopics we publish: commands to the game (on a tablet or on a
    # PC/touchscreen), commands to the robot, and game state messages
    game_pub = rospy.Publisher('/sar/opal_command', OpalCommand,
            queue_size = 10)
    robot_pub = rospy.Publisher('/sar/robot_command', RobotCommand,
            queue_size = 10)
    state_pub = rospy.Publisher('/sar/game_state', GameState, queue_size = 10)

    def __init__(self, ros_node, queue):
        """ Initialize ROS """
        # we get a reference to the main ROS node so we can do callbacks
        # to publish messages, and subscribe to stuff # TODO being used?
        self.ros_node = ros_node
        # we get a reference to the main game node's queue so we can
        # give it messages
        self.game_node_queue = queue

        # set up logger
        self.logger = logging.getLogger(__name__)
        self.logger.info("Subscribing to topics: /sar/opal_action, " +
            "/sar/robot_state, /sar/game_command")

        # initialize the flags we use to track responses from the robot
        # and from the user
        self.robot_doing_action = False
        self.robot_speaking = False
        self.response_received = None

        # subscribe to messages from opal game
        rospy.Subscriber('/sar/opal_action', OpalAction,
                self.on_opal_action_msg)
        # subscribe to messages about the robot's state
        rospy.Subscriber('/sar/robot_state', RobotState, self.on_robot_state_msg)
        # subscribe to game commands (commands we are sent to start, pause,
        # and stop the game)
        rospy.Subscriber('/sar/game_command', GameCommand,
                self.on_game_command_msg)

    def send_opal_command(self, command, properties=None):
        """ Publish opal command message """
        self.logger.info("Sending opal command: " + command)
        # build message
        msg = OpalCommand()
        # add header
        msg.header = Header()
        msg.header.stamp = rospy.Time.now()
        # Add appropriate command and properties if there are any.
        # We check properties for each command individually, since some
        # require properties, and if there are none, we shouldn't send
        # the message. We assume any properties provided are in the
        # correct format for the command.
        if "RESET" in command:
            msg.command = OpalCommand.RESET
        elif "DISABLE_TOUCH" in command:
            msg.command = OpalCommand.DISABLE_TOUCH
        elif "ENABLE_TOUCH" in command:
            msg.command = OpalCommand.ENABLE_TOUCH
        elif "SIDEKICK_DO" in command:
            msg.command = OpalCommand.SIDEKICK_DO
            # properties: a string with the name of action to do
        elif "SIDEKICK_SAY" in command:
            msg.command = OpalCommand.SIDEKICK_SAY
            # properties: a string with the name of audio file to play
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "SIDEKICK_SAY command! Not sending empty command.")
                return
        elif "LOAD_OBJECT" in command:
            msg.command = OpalCommand.LOAD_OBJECT
            # properties: JSON defining what object to load
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "LOAD_OBJECT command! Not sending empty command.")
                return
        elif "CLEAR" in command:
            msg.command = OpalCommand.CLEAR
            # properties: optionally, string defining what objects to
            # remove
            if properties:
                msg.properties = properties
        elif "MOVE_OBJECT" in command:
            msg.command = OpalCommand.MOVE_OBJECT
            # properties: JSON defining what object to move where
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "MOVE_OBJECT command! Not sending empty command.")
                return
        elif "HIGHLIGHT_OBJECT" in command:
            msg.command = OpalCommand.HIGHLIGHT_OBJECT
            # properties: a string with name of the object to highlight
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "HIGHLIGHT_OBJECT command! Not sending empty command.")
                return
        elif "REQUEST_KEYFRAME" in command:
            msg.command = OpalCommand.REQUEST_KEYFRAME
        elif "FADE_SCREEN" in command:
            msg.command = OpalCommand.FADE_SCREEN
        elif "UNFADE_SCREEN" in command:
            msg.command = OpalCommand.UNFADE_SCREEN
        elif "NEXT_PAGE" in command:
            msg.command = OpalCommand.NEXT_PAGE
        elif "PREV_PAGE" in command:
            msg.command = OpalCommand.PREV_PAGE
        elif "EXIT" in command:
            msg.command = OpalCommand.EXIT
        elif "SET_CORRECT" in command:
            msg.command = OpalCommand.SET_CORRECT
            # properties: JSON listing names of objects that are
            # correct or incorrect
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "SET_CORRECT command! Not sending empty command.")
                return
        elif "SHOW_CORRECT" in command:
            msg.command = OpalCommand.SHOW_CORRECT
        elif "HIDE_CORRECT" in command:
            msg.command = OpalCommand.HIDE_CORRECT
        elif "SETUP_STORY_SCENE" in command:
            msg.command = OpalCommand.SETUP_STORY_SCENE
            # properties: JSON listing scene attributes
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "SETUP_STORY_SCENE command! Not sending empty command.")
                return
        else:
            self.logger.warning("Not sending invalid OpalCommand: ", command)
            return
        # send message
        self.game_pub.publish(msg)
        self.logger.debug(msg)

    def send_opal_command_and_wait(self, command, response, timeout,
            properties=None):
        """ Publish opal command message and wait for a response """
        self.send_opal_command(command, properties)
        self.wait_for_response(response, timeout)

    def send_robot_command(self, command, properties=None):
        """ Publish robot command message """
        self.logger.info("Sending robot command: " + str(command))
        # build message
        msg = RobotCommand()
        # add header
        msg.header = Header()
        msg.header.stamp = rospy.Time.now()
        # add appropriate command
        if "SLEEP" in command:
            msg.command = RobotCommand.SLEEP
        elif "WAKEUP" in command:
            msg.command = RobotCommand.WAKEUP
        elif "DO" in command:
            msg.command = RobotCommand.DO
            # DO commands take properties in the form of a string
            # contianing text to say and/or actions to do. We assume
            # these are provided in the correct string format.
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a DO command! "
                        + "Not sending empty command.")
                return
        # send message
        self.robot_pub.publish(msg)
        self.logger.debug(msg)

    def send_robot_command_and_wait(self, command, response, timeout,
            properties=None):
        """ Publish robot command message and wait for a response """
        # timeout should be a datetime.timedelta object
        self.send_robot_command(command, properties)
        self.wait_for_response(response, timeout)

    def send_game_state(self, state):
        """ Publish a game state message """
        self.logger.info("Sending game state: " + str(state))
        # build message
        msg = GameState()
        # add header
        msg.header = Header()
        msg.header.stamp = rospy.Time.now()
        # add constant indicating which game we are
        msg.game = GameState.STORYTELLING
        # add appropriate state
        if "START" in state:
            msg.state = GameState.START
        if "IN_PROGRESS" in state:
            msg.state = GameState.IN_PROGRESS
        if "PAUSED" in state:
            msg.state = GameState.PAUSED
        if "TIMEOUT" in state:
            msg.state = GameState.TIMEOUT
        if "END" in state:
            msg.state = GameState.END
        # send message
        self.state_pub.publish(msg)
        self.logger.debug(msg)

    def on_game_command_msg(self, data):
        """ Called when we receive GameCommand messages """
        self.logger.info("Received GameCommand message: GAME=" +
                str(data.game) + ", COMMAND=" + str(data.command))
        # if the game field doesn't list the constant referring to this
        # game, we can ignore the message.
        if GameCommand.STORYTELLING is not data.game:
            self.logger.info("Not for us... ignoring.")
            return

        # We need to act based on the game commands - queue up the
        # command received so the main game loop can take action.
        if data.command is GameCommand.START:
            self.game_node_queue.put("START")
        if data.command is GameCommand.PAUSE:
            self.game_node_queue.put("PAUSE")
        if data.command is GameCommand.CONTINUE:
            self.game_node_queue.put("CONTINUE")
        if data.command is GameCommand.END:
            self.game_node_queue.put("END")

    def on_opal_action_msg(self, data):
        """ Called when we receive OpalAction messages """
        self.logger.info("Received OpalAction message: ACTION="
                + data.action + ", MESSAGE=" + data.message)

        # Currently, we are only using OpalAction messages to get
        # responses from the user. So we only care whether the action
        # was a PRESS and whether it was on an object that is used as
        # a START button or is a CORRECT or INCORRECT response
        # object. When we do get one of these messages, set the
        # relevant flag.
        if "tap" in data.action:
            # objectName, position
            pass
        elif "press" in data.action:
            # objectName, position
            # check if START was in the message
            if "START" in data.message:
                self.start_response_received = True
                self.response_received = data.message
            # check if CORRECT was in the message
            if "CORRECT" in data.message:
                self.correct_incorrect_response_received = True
                self.response_received = data.message
            pass
        elif "release" in data.action:
            # no object
            pass
        elif "pancomplete" in data.action:
            # no object
            pass
        elif "pan" in data.action:
            # objectName, position
            pass
        elif "collideEnd" in data.action:
            # objectName, position, objectTwoName, positionTwo
            pass
        elif "collide" in data.action:
            # objectName, position, objectTwoName, positionTwo
            pass

    def on_robot_state_msg(self, data):
        """ Called when we receive RobotState messages """
        # When we get robot state messages, set a flag indicating
        # whether the robot is in motion or playing sound or not
        self.robot_speaking = data.is_playing_sound
        self.robot_doing_action = data.doing_action
        self.logger.info("Received RobotState message: doing_action="
                + str(data.doing_action) + ", playing_sound="
                + str(data.is_playing_sound))
        # TODO set flags for any other fields that we add to
        # RobotState messages later

    def wait_for_response(self, response, timeout):
        """ Wait for particular user or robot responses for the
        specified amount of time.
        """
        # check what response to wait for, set that response received
        # flag to false
        # valid responses are:
        # START, CORRECT_INCORRECT, ROBOT_NOT_SPEAKING
        if "START" in response:
            self.start_response_received = False
            self.waiting_for_start = True
            self.waiting_for_correct_incorrect = False
            self.waiting_for_robot_speaking = False
        elif "CORRECT" in response:
            self.correct_incorrect_response_received = False
            self.waiting_for_start = False
            self.waiting_for_correct_incorrect = True
            self.waiting_for_robot_speaking = False
        elif "ROBOT_NOT_SPEAKING" in response:
            self.robot_speaking = False
            self.waiting_for_start = False
            self.waiting_for_correct_incorrect = False
            self.waiting_for_robot_speaking = True
        else:
            self.logger.warning("Told to wait for "
                    + response + " but that isn't one of the allowed "
                    + "responses to wait for!")
            return

        self.logger.info("waiting for " + response + "...")
        start_time = datetime.datetime.now()
        while datetime.datetime.now() - start_time < timeout:
            time.sleep(0.1)
            # check periodically whether we've received the response we
            # were waiting for, and if so, we're done waiting
            if (self.waiting_for_start and self.start_response_received) \
                    or (self.waiting_for_correct_incorrect and \
                        self.correct_incorrect_response_received) \
                    or (self.waiting_for_robot_speaking \
                    and not self.robot_speaking \
                    and not self.robot_doing_action):
                self.logger.info("Got response! "
                        + str(self.response_received))
                # reset waiting flags
                self.waiting_for_start = False
                self.waiting_for_correct_incorrect = False
                self.waiting_for_robot_speaking = False
                return self.response_received
        # if we don't get the response we were waiting for, we're done
        # waiting and timed out
        self.waiting_for_start = False
        self.waiting_for_correct_incorrect = False
        self.waiting_for_robot_speaking = False
        self.logger.info("Timed out! Moving on...")
        return "TIMEOUT"

<commit_msg>Add interrupt field to RobotCommand msg, fix flags<commit_after># Jacqueline Kory Westlund
# May 2016
#
# The MIT License (MIT)
#
# Copyright (c) 2016 Personal Robots Group
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import rospy # ROS
import datetime # for header times and timeouts
import time # for sleep
import logging # log messages
import Queue # for queuing messages for the main game loop
from sar_opal_msgs.msg import OpalCommand # ROS msgs to talk to game
from sar_opal_msgs.msg import OpalAction # ROS msgs for game actions
from sar_robot_command_msgs.msg import RobotCommand # ROS msgs for robot cmd
from sar_robot_command_msgs.msg import RobotState # ROS msgs for robot state
from std_msgs.msg import Header # standard ROS msg header
from sar_game_command_msgs.msg import GameState # ROS msgs for game state
from sar_game_command_msgs.msg import GameCommand # ROS msgs for game commands

class ss_ros():
    # ROS node
    # set up rostopics we publish: commands to the game (on a tablet or on a
    # PC/touchscreen), commands to the robot, and game state messages
    game_pub = rospy.Publisher('/sar/opal_command', OpalCommand,
            queue_size = 10)
    robot_pub = rospy.Publisher('/sar/robot_command', RobotCommand,
            queue_size = 10)
    state_pub = rospy.Publisher('/sar/game_state', GameState, queue_size = 10)

    def __init__(self, ros_node, queue):
        """ Initialize ROS """
        # we get a reference to the main ROS node so we can do callbacks
        # to publish messages, and subscribe to stuff # TODO being used?
        self.ros_node = ros_node
        # we get a reference to the main game node's queue so we can
        # give it messages
        self.game_node_queue = queue

        # set up logger
        self.logger = logging.getLogger(__name__)
        self.logger.info("Subscribing to topics: /sar/opal_action, " +
            "/sar/robot_state, /sar/game_command")

        # initialize the flags we use to track responses from the robot
        # and from the user
        self.robot_doing_action = False
        self.robot_speaking = False
        self.response_received = None

        # subscribe to messages from opal game
        rospy.Subscriber('/sar/opal_action', OpalAction,
                self.on_opal_action_msg)
        # subscribe to messages about the robot's state
        rospy.Subscriber('/sar/robot_state', RobotState, self.on_robot_state_msg)
        # subscribe to game commands (commands we are sent to start, pause,
        # and stop the game)
        rospy.Subscriber('/sar/game_command', GameCommand,
                self.on_game_command_msg)

    def send_opal_command(self, command, properties=None):
        """ Publish opal command message """
        self.logger.info("Sending opal command: " + command)
        # build message
        msg = OpalCommand()
        # add header
        msg.header = Header()
        msg.header.stamp = rospy.Time.now()
        # Add appropriate command and properties if there are any.
        # We check properties for each command individually, since some
        # require properties, and if there are none, we shouldn't send
        # the message. We assume any properties provided are in the
        # correct format for the command.
        if "RESET" in command:
            msg.command = OpalCommand.RESET
        elif "DISABLE_TOUCH" in command:
            msg.command = OpalCommand.DISABLE_TOUCH
        elif "ENABLE_TOUCH" in command:
            msg.command = OpalCommand.ENABLE_TOUCH
        elif "SIDEKICK_DO" in command:
            msg.command = OpalCommand.SIDEKICK_DO
            # properties: a string with the name of action to do
        elif "SIDEKICK_SAY" in command:
            msg.command = OpalCommand.SIDEKICK_SAY
            # properties: a string with the name of audio file to play
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "SIDEKICK_SAY command! Not sending empty command.")
                return
        elif "LOAD_OBJECT" in command:
            msg.command = OpalCommand.LOAD_OBJECT
            # properties: JSON defining what object to load
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "LOAD_OBJECT command! Not sending empty command.")
                return
        elif "CLEAR" in command:
            msg.command = OpalCommand.CLEAR
            # properties: optionally, string defining what objects to
            # remove
            if properties:
                msg.properties = properties
        elif "MOVE_OBJECT" in command:
            msg.command = OpalCommand.MOVE_OBJECT
            # properties: JSON defining what object to move where
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "MOVE_OBJECT command! Not sending empty command.")
                return
        elif "HIGHLIGHT_OBJECT" in command:
            msg.command = OpalCommand.HIGHLIGHT_OBJECT
            # properties: a string with name of the object to highlight
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "HIGHLIGHT_OBJECT command! Not sending empty command.")
                return
        elif "REQUEST_KEYFRAME" in command:
            msg.command = OpalCommand.REQUEST_KEYFRAME
        elif "FADE_SCREEN" in command:
            msg.command = OpalCommand.FADE_SCREEN
        elif "UNFADE_SCREEN" in command:
            msg.command = OpalCommand.UNFADE_SCREEN
        elif "NEXT_PAGE" in command:
            msg.command = OpalCommand.NEXT_PAGE
        elif "PREV_PAGE" in command:
            msg.command = OpalCommand.PREV_PAGE
        elif "EXIT" in command:
            msg.command = OpalCommand.EXIT
        elif "SET_CORRECT" in command:
            msg.command = OpalCommand.SET_CORRECT
            # properties: JSON listing names of objects that are
            # correct or incorrect
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "SET_CORRECT command! Not sending empty command.")
                return
        elif "SHOW_CORRECT" in command:
            msg.command = OpalCommand.SHOW_CORRECT
        elif "HIDE_CORRECT" in command:
            msg.command = OpalCommand.HIDE_CORRECT
        elif "SETUP_STORY_SCENE" in command:
            msg.command = OpalCommand.SETUP_STORY_SCENE
            # properties: JSON listing scene attributes
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a "
                    + "SETUP_STORY_SCENE command! Not sending empty command.")
                return
        else:
            self.logger.warning("Not sending invalid OpalCommand: ", command)
            return
        # send message
        self.game_pub.publish(msg)
        self.logger.debug(msg)

    def send_opal_command_and_wait(self, command, response, timeout,
            properties=None):
        """ Publish opal command message and wait for a response """
        self.send_opal_command(command, properties)
        self.wait_for_response(response, timeout)

    def send_robot_command(self, command, properties=None):
        """ Publish robot command message """
        self.logger.info("Sending robot command: " + str(command))
        # build message
        msg = RobotCommand()
        # add header
        msg.header = Header()
        msg.header.stamp = rospy.Time.now()
        # add appropriate command
        if "SLEEP" in command:
            msg.command = RobotCommand.SLEEP
        elif "WAKEUP" in command:
            msg.command = RobotCommand.WAKEUP
        elif "DO" in command:
            msg.command = RobotCommand.DO
            # DO commands take properties in the form of a string
            # contianing text to say and/or actions to do. We assume
            # these are provided in the correct string format.
            if properties:
                msg.properties = properties
            else:
                self.logger.warning("Did not get properties for a DO command! "
                        + "Not sending empty command.")
                return
        msg.interrupt = False
        # send message
        self.robot_pub.publish(msg)
        self.logger.debug(msg)

    def send_robot_command_and_wait(self, command, response, timeout,
            properties=None):
        """ Publish robot command message and wait for a response """
        # timeout should be a datetime.timedelta object
        self.send_robot_command(command, properties)
        self.wait_for_response(response, timeout)

    def send_game_state(self, state):
        """ Publish a game state message """
        self.logger.info("Sending game state: " + str(state))
        # build message
        msg = GameState()
        # add header
        msg.header = Header()
        msg.header.stamp = rospy.Time.now()
        # add constant indicating which game we are
        msg.game = GameState.STORYTELLING
        # add appropriate state
        if "START" in state:
            msg.state = GameState.START
        if "IN_PROGRESS" in state:
            msg.state = GameState.IN_PROGRESS
        if "PAUSED" in state:
            msg.state = GameState.PAUSED
        if "TIMEOUT" in state:
            msg.state = GameState.TIMEOUT
        if "END" in state:
            msg.state = GameState.END
        # send message
        self.state_pub.publish(msg)
        self.logger.debug(msg)

    def on_game_command_msg(self, data):
        """ Called when we receive GameCommand messages """
        self.logger.info("Received GameCommand message: GAME=" +
                str(data.game) + ", COMMAND=" + str(data.command))
        # if the game field doesn't list the constant referring to this
        # game, we can ignore the message.
        if GameCommand.STORYTELLING is not data.game:
            self.logger.info("Not for us... ignoring.")
            return

        # We need to act based on the game commands - queue up the
        # command received so the main game loop can take action.
        if data.command is GameCommand.START:
            self.game_node_queue.put("START")
        if data.command is GameCommand.PAUSE:
            self.game_node_queue.put("PAUSE")
        if data.command is GameCommand.CONTINUE:
            self.game_node_queue.put("CONTINUE")
        if data.command is GameCommand.END:
            self.game_node_queue.put("END")

    def on_opal_action_msg(self, data):
        """ Called when we receive OpalAction messages """
        self.logger.info("Received OpalAction message: ACTION="
                + data.action + ", MESSAGE=" + data.message)

        # Currently, we are only using OpalAction messages to get
        # responses from the user. So we only care whether the action
        # was a PRESS and whether it was on an object that is used as
        # a START button or is a CORRECT or INCORRECT response
        # object. When we do get one of these messages, set the
        # relevant flag.
        if "tap" in data.action:
            # objectName, position
            pass
        elif "press" in data.action:
            # objectName, position
            # check if START was in the message
            if "START" in data.message:
                self.start_response_received = True
                self.response_received = data.message
            # check if CORRECT was in the message
            if "CORRECT" in data.message:
                self.correct_incorrect_response_received = True
                self.response_received = data.message
            pass
        elif "release" in data.action:
            # no object
            pass
        elif "pancomplete" in data.action:
            # no object
            pass
        elif "pan" in data.action:
            # objectName, position
            pass
        elif "collideEnd" in data.action:
            # objectName, position, objectTwoName, positionTwo
            pass
        elif "collide" in data.action:
            # objectName, position, objectTwoName, positionTwo
            pass

    def on_robot_state_msg(self, data):
        """ Called when we receive RobotState messages """
        # When we get robot state messages, set a flag indicating
        # whether the robot is in motion or playing sound or not
        self.robot_speaking = data.is_playing_sound
        self.robot_doing_action = data.doing_action
        self.logger.info("Received RobotState message: doing_action="
                + str(data.doing_action) + ", playing_sound="
                + str(data.is_playing_sound))
        # TODO set flags for any other fields that we add to
        # RobotState messages later

    def wait_for_response(self, response, timeout):
        """ Wait for particular user or robot responses for the
        specified amount of time.
        """
        # check what response to wait for, set that response received
        # flag to false
        # valid responses are:
        # START, CORRECT_INCORRECT, ROBOT_NOT_SPEAKING
        if "START" in response:
            self.start_response_received = False
            self.waiting_for_start = True
            self.waiting_for_correct_incorrect = False
            self.waiting_for_robot_speaking = False
        elif "CORRECT" in response:
            self.correct_incorrect_response_received = False
            self.waiting_for_start = False
            self.waiting_for_correct_incorrect = True
            self.waiting_for_robot_speaking = False
        elif "ROBOT_NOT_SPEAKING" in response:
            self.robot_speaking = True
            self.waiting_for_start = False
            self.waiting_for_correct_incorrect = False
            self.waiting_for_robot_speaking = True
        else:
            self.logger.warning("Told to wait for "
                    + response + " but that isn't one of the allowed "
                    + "responses to wait for!")
            return

        self.logger.info("waiting for " + response + "...")
        start_time = datetime.datetime.now()
        while datetime.datetime.now() - start_time < timeout:
            time.sleep(0.1)
            # check periodically whether we've received the response we
            # were waiting for, and if so, we're done waiting
            if (self.waiting_for_start and self.start_response_received) \
                    or (self.waiting_for_correct_incorrect and \
                        self.correct_incorrect_response_received) \
                    or (self.waiting_for_robot_speaking \
                    and not self.robot_speaking \
                    and not self.robot_doing_action):
                self.logger.info("Got " + response + " response!")
                # reset waiting flags
                self.waiting_for_start = False
                self.waiting_for_correct_incorrect = False
                self.waiting_for_robot_speaking = False
                return self.response_received
        # if we don't get the response we were waiting for, we're done
        # waiting and timed out
        self.waiting_for_start = False
        self.waiting_for_correct_incorrect = False
        self.waiting_for_robot_speaking = False
        self.logger.info("Timed out! Moving on...")
        return "TIMEOUT"

<|endoftext|>