Esempio n. 1
0
    def api_wait(self, timeout=None, exit_any=False):
        """Wait until something happens at the remote endpoint.
        While waiting, the controller may interrupt to safely stop or
        interact with the bot.

        :param timeout: Number of seconds to timeout if nothing happens
        :type timeout: int, optional

        :param mine: Only wait for my notifications
        :type mine: bool, optional

        """
        start = utils.localtime()
        result, event = self._waiter.wait(timeout=1)

        while not result:
            # received signal from server to stop the bot
            if self._stop:
                raise BotStopException
            # received signal from server to enter interactive mode
            if self._interact:
                IPython.embed()
                self._interact = False

            result, event = self._waiter.wait(event_last=event, timeout=1)

            # break because *something* happened and exit_any=True
            if result and exit_any:
                break

            # break because our bot got a notification
            if result and self.api_call(
                    OPS['__notifier'],
                    variables={'id': self.id},
            )['notifier']['unseenCount']:
                self.api_call(
                    OPS['__notifier_update'],
                    variables={
                        'id': self.id,
                        'last_seen': str(utils.localtime()),
                    },
                )
                break

            # break if timeout
            if timeout and utils.localtime() - start < timedelta(
                    seconds=timeout):
                break

            result = False
            event = None
Esempio n. 2
0
 def update_intro(self, intro, page):
     scratch = json.loads(page['scratch'])
     intro_scratch = json.loads(intro['scratch'])
     page_info = {
         'link': self.get_app_link(page['id']),
         'created': page['created'],
         'contributors': [x['user'] for x in scratch['entries']],
     }
     if scratch['number'] > len(intro_scratch['pages']):
         intro_scratch['pages'].append(page_info)
     else:
         intro_scratch['pages'][scratch['number'] - 1] = page_info
     return self.activity_update(
         id=intro['id'],
         title=INTRO_TITLE,
         description=INTRO_DESCRIPTION.format(pages='\n'.join(
             '| [{}]({}) | {} | {} |'.format(
                 i + 1,
                 x['link'],
                 utils.localtime(x['created']).strftime('%m.%d.%y'),
                 ', '.join(
                     sorted(set(y['first_name']
                                for y in x['contributors']))),
             ) for i, x in enumerate(intro_scratch['pages']))),
         scratch=json.dumps(intro_scratch),
     )
Esempio n. 3
0
    def run(
        self,
        min_players,
        duration_hours,
        amount_cooperate,
        amount_defect,
        amount_win,
        amount_lose,
    ):

        start_description = ACTIVITY_DESCRIPTION.format(
            bot=self.node['name'],
            cooperate=utils.amount_to_string(amount_cooperate),
            defect=utils.amount_to_string(amount_defect),
            win=utils.amount_to_string(amount_win),
            lose=utils.amount_to_string(amount_lose),
            participants='_No one yet_',
        )

        assert min_players >= 2, 'Minimum 2 players'

        try:
            activity = self.activity_list(
                user=self.id,
                active=True,
                first=1,
            )[0]
        except (IndexError, ValueError):
            activity = self._new_activity(start_description)

        while True:
            defect, cooperate = self.comment_list(
                user=self.id,
                parent=activity['id'],
                order_by='created',
                first=2,
            )

            cooperators = {
                x['id']: x
                for x in self.person_list(like_for=cooperate['id'])
            }
            defectors = {
                x['id']: x
                for x in self.person_list(like_for=defect['id'])
            }

            players = {**cooperators, **defectors}

            description = ACTIVITY_DESCRIPTION.format(
                bot=self.node['name'],
                cooperate=utils.amount_to_string(amount_cooperate),
                defect=utils.amount_to_string(amount_defect),
                win=utils.amount_to_string(amount_win),
                lose=utils.amount_to_string(amount_lose),
                participants='{}{}'.format(
                    '* ',
                    '\n* '.join(players[x]['name'] for x in players),
                ) if players else '_No one yet_',
            ) if players else start_description

            # if the activity is closed, calculate earning and send rewards
            if not (len(players) >= min_players and
                    utils.localtime() > utils.localtime(activity['created']) +
                    timedelta(hours=duration_hours)):
                self.activity_update(
                    id=activity['id'],
                    description=description,
                )
            else:
                p1, p2 = random.sample(list(players.values()), 2)

                for x in [p1, p2]:
                    if x['id'] in cooperators and x['id'] in defectors:
                        x['_cooperate'] = random.random() < 0.5
                        x['_random'] = True
                    elif x['id'] in cooperators:
                        x['_cooperate'] = True
                        x['_random'] = False
                    elif x['id'] in defectors:
                        x['_cooperate'] = False
                        x['_random'] = False
                    else:
                        self.logger.error('Logical error in the decision code')
                        raise ValueError

                if p1['_cooperate'] and p2['_cooperate']:
                    p1['_amount'] = p2['_amount'] = amount_cooperate
                    p1['_desc'] = p2['_desc'] = DESC_COOPERATE
                    conclusion = CONCLUSION_COOPERATE.format(
                        name1=p1['name'],
                        name2=p2['name'],
                    )
                elif p1['_cooperate'] and not p2['_cooperate']:
                    p1['_amount'], p2['_amount'] = amount_lose, amount_win
                    p1['_desc'], p2['_desc'] = DESC_LOSE, DESC_WIN
                    conclusion = CONCLUSION_MIXED.format(
                        name1=p2['name'],
                        name2=p1['name'],
                    )
                elif not p1['_cooperate'] and p2['_cooperate']:
                    p1['_amount'], p2['_amount'] = amount_win, amount_lose
                    p1['_desc'], p2['_desc'] = DESC_WIN, DESC_LOSE
                    conclusion = CONCLUSION_MIXED.format(
                        name1=p1['name'],
                        name2=p2['name'],
                    )
                else:
                    p1['_amount'] = p2['_amount'] = amount_defect
                    p1['_desc'] = p2['_desc'] = DESC_LOSE
                    conclusion = CONCLUSION_DEFECT.format(
                        name1=p1['name'],
                        name2=p2['name'],
                    )

                for x in [p1, p2]:
                    self.reward_create(
                        target=x['id'],
                        amount=x['_amount'],
                        description=x['_desc'],
                        related_activity=activity['id'],
                    )

                self.activity_update(
                    id=activity['id'],
                    active=False,
                    description=description + ACTIVITY_CLOSE.format(
                        player1=p1['username'],
                        player2=p2['username'],
                        choice1='cooperate' if p1['_cooperate'] else 'defect',
                        choice2='cooperate' if p2['_cooperate'] else 'defect',
                        random1=' (random)' if p1['_random'] else '',
                        random2=' (random)' if p2['_random'] else '',
                        conclusion=conclusion,
                    ),
                )

                activity = self._new_activity(start_description)

            self.api_wait()
Esempio n. 4
0
    def run(
        self,
        rewards_per_week,
        reward_multiplier,
        minimum_streak,
        ubp_weekday,
        reward_weekday,
    ):
        self.ubp_weekday = ubp_weekday

        # retrieve the latest activity or create a new one if needed
        try:
            activity = self.activity_list(
                user=self.id,
                active=True,
                first=1,
            )[0]
        except (IndexError, ValueError):
            activity = self.activity_create(
                title=ACTIVITY_TITLE,
                description='',
                reward_min=0,
                active=True,
            )

        while True:
            now = utils.localtime()

            # only execute logic if Streak Bot hasn't made a reward this epoch
            if not self.reward_list(
                    user=self.id,
                    created_after=str(utils.epoch_start(reward_weekday, now))):
                leaderboard = []

                # go through all of last week's donations
                for user in [
                        dict(y) for y in set(
                            tuple(x['user'].items())
                            for x in self.donation_list(
                                created_after=str(
                                    utils.epoch_start(
                                        ubp_weekday,
                                        now,
                                        offset=-1,
                                    )),
                                created_before=str(
                                    utils.epoch_start(
                                        ubp_weekday,
                                        now,
                                    )),
                            ))
                ]:
                    streak, amount = self.check_streak(user, now)
                    if streak < minimum_streak:
                        continue

                    leaderboard.append({
                        'user': user,
                        'streak': streak,
                        'amount': amount,
                    })

                leaderboard.sort(
                    key=lambda x: (x['streak'], x['amount']),
                    reverse=True,
                )

                self.logger.debug('Leaderboard: ' + json.dumps(leaderboard))

                # update activity with new streak information
                activity = self.activity_update(
                    id=activity['id'],
                    title=ACTIVITY_TITLE,
                    description=ACTIVITY_DESCRIPTION.format(
                        minimum_streak=minimum_streak,
                        weekday=reward_weekday,
                        amount='{:.2f}'.format(reward_multiplier / 100),
                        streaks='\n'.join('| {} | ${:.2f} | @{} |'.format(
                            x['streak'],
                            x['amount'] / 100,
                            x['user']['username'],
                        ) for x in leaderboard),
                    ),
                    reward_min=reward_multiplier * minimum_streak,
                    reward_range=max(
                        0,
                        reward_multiplier *
                        (leaderboard[0]['streak'] - minimum_streak)
                        if leaderboard else 0,
                    ),
                )

                # send out the reward to a random user
                if leaderboard:
                    winner = random.choice(leaderboard)
                    self.reward_create(
                        target=winner['user']['id'],
                        amount=reward_multiplier * winner['streak'],
                        related_activity=activity['id'],
                        description=REWARD_DESCRIPTION.format(
                            name=winner['user']['first_name'],
                            streak=winner['streak'],
                        ),
                    )

            # wait until next reward day
            self.api_wait(timeout=(utils.epoch_start(
                reward_weekday,
                now,
                offset=1,
            ) - now).total_seconds() + 1)
Esempio n. 5
0
    def run(self, reward_amount, weekday, recalculate):
        self.reward_amount = reward_amount
        self.weekday = weekday

        # retrieve the latest activity or create a new one if needed
        try:
            activity = self.activity_list(
                user=self.id,
                active=True,
                first=1,
            )[0]
        except (IndexError, ValueError):
            activity = self.activity_create(
                title='',
                description='',
                active=True,
            )

        if recalculate or not activity['scratch']:
            if recalculate and activity['scratch']:
                epoch_start = utils.localtime(
                    json.loads(activity['scratch'])['epoch_start'])
            else:
                epoch_start = utils.epoch_start(
                    self.weekday,
                    utils.localtime(),
                )

            activity = self._update_activity(
                activity['id'], {
                    'epoch_start':
                    str(epoch_start),
                    'word_count':
                    self._word_count(x['description']
                                     for x in self.donation_list(
                                         created_before=str(epoch_start))),
                    'reward_recipients':
                    [{
                        'user_id': x['target']['id'],
                        'user_name': x['target']['name'],
                        'reward_id': x['id'],
                        'donation_id': json.loads(x['scratch'])['donation_id'],
                        'word': json.loads(x['scratch'])['word'],
                        'reward_created': x['created'],
                    } for x in self.reward_list(
                        user=self.id,
                        related_activity=activity['id'],
                    )],
                })

        scratch = json.loads(activity['scratch'])

        while True:
            epoch_start = utils.epoch_start(self.weekday, utils.localtime())

            # if we have moved on to a new epoch
            if epoch_start > utils.localtime(scratch['epoch_start']):

                # calculate word_counts of donations in the previous epoch
                word_counts = [(
                    x,
                    self._word_count([x['description']]),
                ) for x in self.donation_list(
                    created_after=str(scratch['epoch_start']),
                    created_before=str(epoch_start),
                )]

                # add new wordcounts and determine possible reward candidates
                reward_candidates = {}
                for donation, word_count in word_counts:
                    for word, count in word_count.items():
                        if word in scratch['word_count']:
                            scratch['word_count'][word] += word_count[word]
                        else:
                            scratch['word_count'][word] = word_count[word]
                            if donation['user']['id'] not in reward_candidates:
                                reward_candidates[donation['user']['id']] = []
                            reward_candidates[donation['user']['id']].append({
                                'user_id':
                                donation['user']['id'],
                                'user_name':
                                donation['user']['name'],
                                'donation_id':
                                donation['id'],
                                'word':
                                word,
                            })

                # send reward if we haven't already
                if reward_candidates and not self.reward_list(
                        user=self.id,
                        related_activity=activity['id'],
                        created_after=str(epoch_start),
                ):
                    winner = random.choice(
                        random.choice(list(reward_candidates.values())))
                    reward = self.reward_create(
                        target=winner['user_id'],
                        amount=reward_amount,
                        related_activity=activity['id'],
                        description=REWARD_DESCRIPTION.format(
                            name=winner['user_name'],
                            word=winner['word'],
                            link=self.get_app_link(winner['donation_id']),
                        ),
                        scratch=json.dumps({
                            'donation_id': winner['donation_id'],
                            'word': winner['word'],
                        }))
                    winner['reward_id'] = reward['id']
                    scratch['reward_recipients'].insert(0, winner)

                # update the activity
                scratch['epoch_start'] = str(epoch_start)
                activity = self._update_activity(activity['id'], scratch)
                scratch = json.loads(activity['scratch'])

            # wait until the start of the next epoch
            now = utils.localtime()
            self.api_wait(timeout=(utils.epoch_start(
                self.weekday,
                now,
                offset=1,
            ) - now).total_seconds())
Esempio n. 6
0
    def run(self, countdown_days, reward_min, reward_increment, reward_max):
        try:
            activity = self.activity_list(
                user=self.id,
                active=True,
                first=1,
            )[0]
            quote = json.loads(activity['scratch'])

        except (TypeError, IndexError, ValueError):
            activity = self._new_activity(
                reward_amount=reward_min,
                end=utils.localtime() + timedelta(days=countdown_days),
                countdown=countdown_days,
            )
            quote = json.loads(activity['scratch'])

        while True:

            comments = sorted(
                [
                    x for x in self._flatten(
                        self.comment_tree(root=activity['id']))
                    if x['user']['user_type'] == 'person'
                ],
                key=lambda x: utils.localtime(x['created']),
            )

            if not comments:
                self.api_wait(timeout=3600 * 24 * countdown_days)
                continue

            num_participants = len(set(x['user']['id'] for x in comments))
            last_time = utils.localtime(comments[-1]['created'])
            reward_amount = min(
                reward_min + reward_increment * num_participants +
                reward_increment * round(
                    (last_time - utils.localtime(comments[0]['created'])).days
                    / 7),
                reward_max,
            )

            description = ACTIVITY_DESCRIPTION.format(
                leader=comments[-1]['user']['name'],
                words=comments[-1]['description'],
                num_participants=num_participants,
                quote=quote['quote'],
                author=quote['author'],
                reward=utils.amount_to_string(reward_amount),
                end=(last_time + timedelta(days=countdown_days)).strftime(
                    self._time_format),
                countdown=countdown_days,
            )

            if utils.localtime() < last_time + timedelta(days=countdown_days):
                self.activity_update(
                    id=activity['id'],
                    description=description,
                    reward_min=reward_amount,
                )

            else:
                reward = self.reward_create(
                    target=comments[-1]['user']['id'],
                    amount=reward_amount,
                    description='Truly one for the ages:\n\n> "{}" \n\n_—{}_'.
                    format(
                        comments[-1]['description'],
                        comments[-1]['user']['name'],
                    ),
                    related_activity=activity['id'],
                )

                self.activity_update(
                    id=activity['id'],
                    description='{}\n\n—\n\n{}'.format(
                        description,
                        'Congratulations, @{}. Here is your well-earned [reward]({})!'
                        .format(
                            comments[-1]['user']['username'],
                            self.get_app_link(reward['id']),
                        )),
                    reward_min=reward_amount,
                    active=False,
                )

                activity = self._new_activity(
                    reward_min,
                    utils.localtime() + timedelta(days=countdown_days),
                    countdown=countdown_days,
                )

            self.api_wait(
                timeout=(
                    (last_time + timedelta(seconds=3600 * 24 * countdown_days)
                     ) - utils.localtime()).total_seconds())
Esempio n. 7
0
    def run(
        self,
        model,
        reward_amount,
        context_length,
        text_length,
        page_length,
    ):
        self.model = model
        self.text_length = text_length

        # retrieve the table of contents activity or create new one
        try:
            intro = self.activity_list(
                user=self.id,
                active=False,
                first=1,
                order_by='created',
            )[0]
        except (IndexError, ValueError):
            intro = self.activity_create(
                title=INTRO_TITLE,
                description='Generating initial story...',
                reward_min=0,
                active=False,
                scratch=json.dumps({
                    'type': 'toc',
                    'pages': []
                }),
            )

        intro_link = self.get_app_link(intro['id'])

        # retrieve the latest page activity or create a new one if needed
        try:
            page = list(
                reversed(
                    self.activity_list(
                        user=self.id,
                        order_by='-created',
                        first=2,
                    )))[1]
            bootstrapping = False
        except (IndexError, ValueError):
            page = self.activity_create(
                title=PAGE_TITLE.format(1),
                description='Generating initial story...',
                active=True,
                reward_min=reward_amount,
                scratch=json.dumps({
                    'type': 'page',
                    'number': 1,
                    'intro_link': intro_link,
                    'prev_link': '',
                    'next_link': '',
                    'entries': [],
                }))
            bootstrapping = True

        while True:
            now = utils.localtime()
            page, root, slots = self.initiate_page(
                now,
                page,
                bootstrapping=bootstrapping,
            )

            # if deadline for last slot has passed
            if bootstrapping or utils.localtime(
                    slots[-1]['created']).day != now.day:

                # if we haven't closed, then make sure to close
                if not self.comment_list(
                        parent=slots[-1]['id'],
                        user=self.id,
                        first=1,
                ):
                    self.comment_create(
                        parent=slots[-1]['id'],
                        description=CLOSE_DESCRIPTION,
                    )

                scratch = json.loads(page['scratch'])

                # if we haven't a chosen winner, then choose a winner
                if len(scratch['entries']) < len(slots):
                    submissions = [
                        x for x in self.comment_list(parent=slots[-1]['id'])
                        if x['user']['id'] != self.id
                    ]
                    if submissions:  # choose user submission
                        submissions.sort(key=lambda x: (
                            x['like_count'],
                            random.random(),
                        ))
                        user = submissions[-1]['user']
                        text = submissions[-1]['description']
                    else:  # use gpt2 to generate next entry
                        user = slots[-1]['user']

                        if bootstrapping:
                            context = START_CONTEXT
                        else:
                            # construct context from previous entries
                            context_list = []
                            current_page = page
                            current_scratch = json.loads(page['scratch'])
                            entry_number = len(current_scratch['entries']) - 1

                            while len(context_list) < context_length:
                                if entry_number < 0:
                                    current_page = self.activity_list(
                                        user=self.id,
                                        order_by='-created',
                                        created_before=current_page['created'],
                                        first=1,
                                    )[0]

                                    current_scratch = json.loads(
                                        current_page['scratch'])
                                    if not current_scratch['type'] == 'page':
                                        self.logger.info('Incomplete context.')
                                        break

                                    entry_number = len(current_scratch) - 1

                                context_list = list(
                                    nlp(current_scratch['entries']
                                        [entry_number]['text'] +
                                        '\n\n')) + context_list

                                entry_number -= 1

                            context = ''.join(
                                x.text_with_ws
                                for x in context_list[len(context_list) -
                                                      context_length:])

                        self.logger.debug('Starting gpt2 generation')

                        # call gpt2 using context and other parameters
                        while True:
                            try:
                                gpt2_text = subprocess.check_output([
                                    sys.executable,
                                    os.path.join(
                                        DIR,
                                        'gpt2',
                                        'generate_text.py',
                                    ),
                                    context,
                                    '--length',
                                    str(text_length),
                                    '--top_k',
                                    str(TOP_K),
                                    '--model_name',
                                    self.model,
                                ]).decode('utf-8').replace(
                                    '<|endoftext|>', ' ').strip()
                                break
                            except subprocess.CalledProcessError:
                                self.logger.info(
                                    'GPT-2 memory error; trying again with smaller context'
                                )
                                context = ' '.join(
                                    context.split(' ')
                                    [int(context.count(' ') / 4):])
                                continue

                        self.logger.debug('Done with gpt2 generation')

                        # use spacy to trim off dangling sentence
                        text = ''.join(
                            x.text_with_ws
                            for x in list(nlp(gpt2_text).sents)[:-1]).strip()

                        if bootstrapping:
                            text = START_CONTEXT + ' ' + text

                    scratch['entries'].append({
                        'user': user,
                        'text': text,
                    })

                    page = self.update_page(page, scratch)

                    if bootstrapping:
                        bootstrapping = False

                intro = self.update_intro(intro, page)

                # send out reward
                if scratch['entries'][-1]['user'][
                        'user_type'] == 'person' and scratch['entries'][-1][
                            'user']['id'] not in [
                                x['target']['id'] for x in self.reward_list(
                                    user=self.id,
                                    related_activity=page['id'],
                                )
                            ]:
                    self.reward_create(
                        target=scratch['entries'][-1]['user']['id'],
                        amount=reward_amount,
                        description=REWARD_DESCRIPTION,
                        related_activity=page['id'],
                    )

                # create new submission or new page
                if len(slots) < page_length:
                    self.comment_create(
                        parent=root['id'],
                        description=SLOT_DESCRIPTION.format(
                            now.strftime('%B %d, %Y')),
                    )
                else:
                    page = self.activity_update(id=page['id'], active=False)
                    page = self.activity_create(
                        title=PAGE_TITLE.format(scratch['number'] + 1),
                        description='Generating initial story...',
                        active=True,
                        reward_min=reward_amount,
                        scratch=json.dumps({
                            'type':
                            'page',
                            'number':
                            scratch['number'] + 1,
                            'entries': [],
                            'intro_link':
                            intro_link,
                            'prev_link':
                            self.get_app_link(page['id']),
                            'next_link':
                            '',
                        }))
                    page, root, slots = self.initiate_page(
                        utils.localtime(),
                        page,
                    )
                    intro = self.update_intro(intro, page)

            # wait until midnight
            now = utils.localtime()
            self.api_wait(timeout=(
                (now.astimezone(datetime.timezone.utc) +
                 datetime.timedelta(days=1)).astimezone(now.tzinfo).replace(
                     hour=0,
                     minute=0,
                     second=0,
                 ) - now).total_seconds())
Esempio n. 8
0
    def run(self, reward_amount, quantity):
        self.reward_amount = reward_amount
        self.quantity = quantity

        # load list of holidays
        with open(os.path.join(DIR, 'holidays.json')) as fd:
            self.holidays = json.load(fd)

        try:
            for i in range(len(self.holidays) - 1):
                assert (
                    self.holidays[i]['date'],
                    self.holidays[i]['id'],
                ) < (
                    self.holidays[i + 1]['date'],
                    self.holidays[i + 1]['id'],
                )
        except AssertionError:
            self.logger.warn('Holiday file is not sorted')
            self.holidays.sort(key=lambda x: (x['date'], x['id']))

        current = utils.localtime()

        # retrieve the latest activity or create a new one if needed
        try:
            activity = self.activity_list(
                user=self.id,
                active=True,
                first=1,
            )[0]
        except (IndexError, ValueError):
            activity = self.activity_create(
                title='',
                description='',
                active=True,
                scratch=json.dumps(
                    {
                        'upcoming': [],
                        'previous': []
                    },
                    indent=2,
                ),
            )
        activity = self.update_activity(activity, current)

        while True:
            holiday = json.loads(activity['scratch'])['upcoming'][0]
            start = utils.localtime(holiday['start'])

            if current >= utils.day_start(start, offset=1):

                reward_list = [
                    x for x in self.reward_list(
                        user=self.id,
                        related_activity=activity['id'],
                        created_after=str(utils.day_start(start, offset=1)),
                    ) if json.loads(x['scratch'])['holiday'] == holiday['id']
                ]

                # if haven't made a reward, then make a reward
                if reward_list:
                    assert len(reward_list) == 1
                    reward = reward_list[0]
                else:
                    reward_candidates = {}
                    for x in self.donation_list(
                            created_after=str(start),
                            created_before=str(
                                utils.day_start(
                                    start,
                                    offset=1,
                                )),
                    ):
                        if x['user']['id'] not in reward_candidates:
                            reward_candidates[x['user']['id']] = []
                        reward_candidates[x['user']['id']].append(x)

                    if reward_candidates:
                        donation = random.choice(
                            random.choice(list(reward_candidates.items()))[1])

                        reward = self.reward_create(
                            target=donation['user']['id'],
                            description=REWARD_DESCRIPTION.format(
                                holiday=holiday['name'],
                                user=donation['user']['first_name'],
                                donation=self.get_app_link(donation['id']),
                            ),
                            amount=reward_amount,
                            related_activity=activity['id'],
                            scratch=json.dumps(
                                {
                                    'holiday':
                                    holiday['id'],
                                    'donation':
                                    donation['id'],
                                    'user_name':
                                    donation['user']['first_name'],
                                    'target_name':
                                    donation['target']['first_name'],
                                },
                                indent=2,
                            ),
                        )
                    else:
                        activity = self.update_activity(activity, current, {})
                        continue

                reward_scratch = json.loads(reward['scratch'])
                comment_list = self.comment_list(
                    parent=reward_scratch['donation'],
                    user=self.id,
                )

                if not comment_list:
                    self.comment_create(
                        parent=reward_scratch['donation'],
                        description=COMMENT_DESCRIPTION.format(
                            user=reward_scratch['user_name'],
                            reward=self.get_app_link(reward['id']),
                            holiday=holiday['name'],
                            description=holiday['description'],
                            link=holiday['link'],
                        ),
                    )

                activity = self.update_activity(activity, current, reward)

            # wait until the end of the next upcoming holiday
            now = utils.localtime()
            self.api_wait(timeout=(utils.day_start(
                utils.localtime(
                    json.loads(activity['scratch'])['upcoming'][0]['start']),
                offset=1,
            ) - now).total_seconds())

            current = utils.localtime()
Esempio n. 9
0
    def update_activity(self, activity, time, reward=None):
        scratch = json.loads(activity['scratch'])
        if reward is not None and scratch['upcoming']:
            scratch['previous'].append(scratch['upcoming'].pop(0))
            scratch['previous'][-1]['reward_link'] = self.get_app_link(
                reward['id']) if reward else None
            scratch['previous'][-1]['donation_link'] = self.get_app_link(
                json.loads(reward['scratch'])['donation']) if reward else None

        while len(scratch['upcoming']) < UPCOMING_COUNT:

            # Ridiculous that bisect doesn't support custom keys
            if not scratch['upcoming']:
                index = 0
                for i, x in enumerate(self.holidays):
                    if x['date'] > '--{:02d}-{:02d}'.format(
                            time.month,
                            time.day,
                    ):
                        index = i
                        break
            else:
                for i, x in enumerate(self.holidays):
                    if scratch['upcoming'][-1]['id'] == x['id']:
                        index = i
                        break

            while True:
                index += 1
                if random.random() < 52 * self.quantity / len(self.holidays):
                    holiday = self.holidays[index % len(self.holidays)]
                    break

            def _next_date(date):
                candidate = utils.day_start(time).replace(
                    month=int(date[2:4]),
                    day=int(date[5:7]),
                )

                return candidate if candidate >= utils.day_start(
                    time) else candidate.replace(year=candidate.year + 1)

            scratch['upcoming'].append({
                'id':
                holiday['id'],
                'name':
                holiday['name'],
                'description':
                holiday['description'],
                'link':
                holiday['link'],
                'start':
                str(_next_date(holiday['date'])),
            })

        activity = self.activity_update(
            id=activity['id'],
            title=ACTIVITY_TITLE,
            description=ACTIVITY_DESCRIPTION.format(
                quantity=self.quantity,
                upcoming='\n'.join('|{}&nbsp;&nbsp;|[{}]({})|'.format(
                    utils.localtime(x['start']).strftime('%A, %B %d'),
                    x['name'],
                    x['link'],
                ) for x in scratch['upcoming']),
                previous='\n'.
                join('|{}&nbsp;&nbsp;|[{}]({})&nbsp;&nbsp;|{}|'.format(
                    str(utils.localtime(x['start']).date()).replace('-', '.'),
                    x['name'],
                    x['link'],
                    '[link]({})'.format(x['donation_link']
                                        ) if x['donation_link'] else 'none',
                ) for x in reversed(scratch['previous'])),
            ),
            reward_min=self.reward_amount,
            scratch=json.dumps(scratch, indent=2),
        )

        return activity
Esempio n. 10
0
    def run(
        self,
        referrer_amounts,
        referred_amount,
    ):

        # retrieve the latest activity or create a new one if needed
        try:
            activity = self.activity_list(
                user=self.id,
                active=True,
                first=1,
            )[0]
        except (IndexError, ValueError):
            activity = self.activity_create(
                title=ACTIVITY_TITLE,
                description='',
                active=True,
                scratch='{}',
            )

        while True:

            people = self.call_custom_gql(
                'ReferralBotPersonList',
                verified=True,
                order_by='date_joined',
            )

            id_lookup = {x['id']: x for x in people}

            # calculate the pyramid structure of the pyramid referral scheme
            referrals = {
                x['id']:
                [y for y in id_lookup if id_lookup[y]['referral'] == x['id']]
                for x in people
            }

            def _make_pyramid(nodes, refs, seen=set()):
                tree = []
                for node in nodes:
                    if node in seen:
                        continue
                    seen.add(node)
                    subtree = _make_pyramid(refs[node], refs, seen)
                    tree.append({'id': node, 'children': subtree})

                return tree

            pyramid = _make_pyramid([x['id'] for x in people], referrals)

            def _serialize_pyramid(children, depth=1):
                return ''.join([
                    '{} @{}\n\n{}'.format(
                        'o &nbsp;&nbsp;&nbsp;&nbsp;' * depth,
                        id_lookup[x['id']]['username'],
                        _serialize_pyramid(x['children'], depth=depth + 1),
                    ) for x in children
                ])

            # update the activity to show the latest pyramid structure
            activity = self.activity_update(
                id=activity['id'],
                title=ACTIVITY_TITLE,
                description=ACTIVITY_DESCRIPTION.format(
                    invite_link='https://{}/#/Person/PersonList'.format(
                        self._endpoint.split('.', 1)[1]),
                    referred_amount='${:.2f}'.format(referred_amount / 100),
                    referrer_amounts='\n'.join(
                        '* __Level {}__: ${:.2f}'.format(i + 1, x / 100)
                        for i, x in enumerate(referrer_amounts)),
                    pyramid=_serialize_pyramid(pyramid)),
                reward_min=min([referred_amount] + referrer_amounts),
                reward_range=max([referred_amount] + referrer_amounts) -
                min([referred_amount] + referrer_amounts),
            )

            # make unpaid payouts according the pyramid structure
            identifier_set = set(
                x['scratch']
                for x in self.reward_list(user=self.id, order_by='created'))

            def _get_ancestor_pairs(children, ancestors=[]):
                """returns a list of all combinations of (node,
                ancestor, depth) tuples """
                return [
                    x for c in children
                    for x in [(a, c['id'], d)
                              for a, d in ancestors] + _get_ancestor_pairs(
                                  c['children'],
                                  [(a2, d2 + 1)
                                   for a2, d2 in ancestors] + [(c['id'], 1)],
                              )
                ]

            for ancestor, node, depth in _get_ancestor_pairs(pyramid):
                if not id_lookup[node]['verified_original']:
                    continue
                if depth <= len(referrer_amounts) and 'referrer:{}:{}'.format(
                        ancestor,
                        node,
                ) not in identifier_set:
                    self.reward_create(
                        target=ancestor,
                        amount=referrer_amounts[depth - 1],
                        description=REWARD_DESCRIPTION_DIRECT.format(
                            referrer_name=id_lookup[ancestor]['first_name'],
                            referred_username=id_lookup[node]['username'],
                        )
                        if depth == 1 else REWARD_DESCRIPTION_INDIRECT.format(
                            referrer_name=id_lookup[ancestor]['first_name'],
                            referred_name=id_lookup[node]['name'],
                            referred_link=self.get_app_link(
                                id_lookup[node]['id']),
                        ),
                        scratch='referrer:{}:{}'.format(ancestor, node),
                    )
                if depth == 1 and 'referred:{}:{}'.format(
                        ancestor,
                        node,
                ) not in identifier_set:
                    self.reward_create(
                        target=node,
                        amount=referred_amount,
                        description=REWARD_DESCRIPTION_REFERRED.format(
                            referred_name=id_lookup[node]['first_name'],
                            referrer_username=id_lookup[ancestor]['username']),
                        scratch='referred:{}:{}'.format(ancestor, node),
                    )

            # wait until midnight
            now = utils.localtime()
            self.api_wait(timeout=(
                (now.astimezone(datetime.timezone.utc) +
                 datetime.timedelta(days=1)).astimezone(now.tzinfo).replace(
                     hour=0,
                     minute=0,
                     second=0,
                 ) - now).total_seconds())