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
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), )
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()
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)
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())
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())
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())
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()
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('|{} |[{}]({})|'.format( utils.localtime(x['start']).strftime('%A, %B %d'), x['name'], x['link'], ) for x in scratch['upcoming']), previous='\n'. join('|{} |[{}]({}) |{}|'.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
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 ' * 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())