def stripe_cancel(): session.user.refresh() logger.info(f'Canceling Stripe subscription {session.user.stripe_subscription_id}') stripe.Subscription.modify( session.user.stripe_subscription_id, cancel_at_period_end=True ) logger.info('Updating SubscriptionDetails record') try: sub = SubscriptionDetails.subscription_id_index.get(session.user.stripe_subscription_id) sub.canceled_timestamp = time.time() sub.canceled_internally = True sub.save() except: logger.exception('Failed to update SubscriptionDetails for canceled subscription') metrics.record('subscription.stripe.canceled_internally') metrics.event( 'Stripe Subscription Canceled', f'User: {session.user_id} / {session.username}\n' f'Subscription ID: {session.user.stripe_subscription_id}\n', tags={ 'module': 'subscribe', 'function': 'stripe_cancel', } ) return redirect(url_for('subscribe.subscribe'))
def paypal_cancel(): session.user.refresh() logger.info(f'Canceling PayPal subscription {session.user.paypal_subscr_id}') paypal_client.cancel_subscription(session.user.paypal_subscr_id) logger.info('Updating SubscriptionDetails record') try: sub = SubscriptionDetails.subscription_id_index.get(session.user.paypal_subscr_id) sub.canceled_timestamp = time.time() sub.canceled_internally = True sub.save() except: logger.exception('Failed to update SubscriptionDetails for canceled subscription') metrics.record('subscription.paypal.canceled_internally') metrics.event( 'PayPal Subscription Canceled', f'User: {session.user_id} / {session.username}\n' f'Subscription ID: {session.user.paypal_subscr_id}\n', tags={ 'module': 'subscribe', 'function': 'paypal_cancel', } ) return redirect(url_for('subscribe.subscribe'))
def paypal_approved(): logger.info(f'Paypal subscription approved: {request.form}') try: sub = paypal_client.get_subscription_details( request.form['subscriptionID']) except: logger.exception('Failed to get PayPal subscription details') return Response(status=400, response='Subscription not found') logger.info('Creating SubscriptionDetails record') SubscriptionDetails(user_id=session.user_id, version=2, type='paypal', subscription_id=request.form['subscriptionID'], full_data=request.form).save() logger.info('Updating User model') # TODO: use set actions session.user.refresh() session.user.subscription_active = True session.user.subscription_type = 'v2.paypal' session.user.paypal_subscr_id = request.form['subscriptionID'] session.user.paypal_payer_email = None session.user.paypal_payer_id = None session.user.paypal_subscr_date = None session.user.paypal_cancel_at_period_end = None plan_name = 'UNKNOWN' try: session.user.paypal_payer_email = sub['subscriber']['email_address'] session.user.paypal_subscr_date = sub['create_time'] session.user.paypal_cancel_at_period_end = not sub['auto_renewal'] logger.info(f'Checking PayPal plan details') try: plan = paypal_client.get_plan_details(sub['plan_id']) except: logger.exception('Failed to fetch PayPal plan details') else: plan_name = plan['description'] except: logger.exception('Failed to get PayPal subscription details') session.user.save() metrics.record('subscription.paypal.approved') metrics.event('PayPal Subscription Created', f'User: {session.user_id} / {session.username}\n' f'Subscription ID: {session.user.paypal_subscr_id}\n' f'Payer Email: {session.user.paypal_payer_email}\n' f'Plan: {plan_name}', tags={'type': 'user-subscription'}) return Response(status=204)
def delete_integration(): logger.info(f'Decoding JWT payload') try: args = jwt.decode(request.form.get('args', ''), HMAC_KEY, algorithms=['HS256'])['args'] except InvalidTokenError as e: logger.error(f'Failed to decode JWT token') return Response(json.dumps({'error': f'Invalid token'}), status=403) logger.info(f'JWT payload: {args}') if args['action'] != 'delete': logger.error( f'Can\'t handle delete_integration request - unknown type {args["type"]}' ) return Response(json.dumps({'error': 'Unknown request type'}), status=400) if args['type'] == 'discord': notification = DiscordBotNotification.get(args['key']) logger.info(f'Deleting {notification}') notification.delete() try: logger.info(f'Updating announce message') get_message_r = discord_bot.get( CHANNEL_GET_MESSAGE % (notification.channel_id, notification.announce_message_id)) get_message_r.raise_for_status() print(get_message_r.json()) content = get_message_r.json()['content'] embed = get_message_r.json()['embeds'][0] embed.update({ 'description': 'Integration deleted', 'color': 0xff0000 }) update_message_r = discord_bot.patch( CHANNEL_EDIT_MESSAGE % (notification.channel_id, notification.announce_message_id), json={ 'content': f'~~{content}~~', 'embed': embed, }) update_message_r.raise_for_status() except Exception as e: logger.warning(f'Failed to update announce message: {e}') elif args['type'] == 'twitch': notification = TwitchBotNotification.get(args['key']) logger.info(f'Deleting {notification}') notification.delete() else: raise ValueError('Unknown type') metrics.event(f'{args["type"].title()} Bot Removed', f'User: {session.username} ({session.user_id})\n' + '\n'.join(f'{k}: {v}' for k, v in notification.asdict().items()), tags={ 'module': 'notification_bot', 'function': 'delete_integration', 'game': game_name, }) return redirect(url_for(blueprint.name + '.root'))
def add_to_channel(): logger.info(f'Got add_to_channel with form {request.form}') if 'create_args' not in request.form and 'existing_args' not in request.form: return redirect(url_for(blueprint.name + '.root')) # Decode JWT `payload` # This ensures that the arguments (channel_id for existing channel, guild_id for creating channel) comes from the actual ouath'd user logger.info(f'Decoding JWT payload') try: args = jwt.decode( # try get create_args first, as this is on the "create" button # fall back to existing_args which is always present, but should only be used if "add" button was used request.form.get('create_args', request.form.get('existing_args')), HMAC_KEY, algorithms=['HS256'])['args'] except InvalidTokenError as e: logger.error(f'Failed to decode JWT token') return Response(json.dumps({'error': f'Invalid token'}), status=403) logger.info(f'JWT payload: {args}') if args['action'] == 'add_to_existing_channel': # using an existing channel - attempt to add SEND_MESSAGE permission for ourselves... don't mind if this errors channel_id = args['channel_id'] logger.info(f'Using existing channel {channel_id} to post games') logger.info(f'Checking for post permission') added_post_permission = _add_post_permission(channel_id) elif args['action'] == 'create_channel': # creating a new channel - create this channel with SEND_MESSAGE for ourselves and view only for everyone else logger.info( f'Creating new channel {request.form["channel_name"]} to post games to' ) channel_id = _create_channel(args['guild_id'], request.form['channel_name'], f'{game_title} Games | OverTrack.gg', restrict_posting=request.form.get( 'restrict_post_messages', '') == 'on') if not channel_id: # if we didn't create the channel, complain return Response( f'<html><head></head><body>' f'<p>Did not have permission to create channel - ' f'please verify that the OverTrack bot has permission create a channel then <a href="{request.url}">retry</a></p>' f'</body></html>', content_type='text/html') else: logger.error( f'Can\'t handle add_to_channel request - unknown type {args["type"]}' ) return Response(json.dumps({'error': 'Unknown integration type'}), status=400) # Get the channel and guild name # This is used simply for recording in the database, so we can have channel names in the delete list channel_info, guild_info = _get_channel_and_guild_info(channel_id) parent_key = args.get('parent_key') is_parent = parent_key is None logger.info(f'Creating new discord bot (parent={parent_key})') # Try to send the greetings message logger.info(f'Sending intro message') content = f'OverTrack is now posting <@{args["discord_user_id"]}>\'s {game_title} games to this channel!\n\n' if parent_key is not None: description = f'Permission was granted because <@{args["discord_user_id"]}> has permissions to post messages in this channel.\n' else: description = f'Other users with permissions to post messages here will now be able to connect their account to this bot.\n' description += ( f'To stop posting <@{args["discord_user_id"]}>\'s games here a channel admin can delete this message, ' f'or <@{args["discord_user_id"]}> can remove their integration.\n') create_message_r = discord_bot.post( CHANNEL_CREATE_MESSAGE % (channel_id, ), json={ 'content': content, 'embed': { 'description': description, 'url': 'https://overtrack.gg', 'color': 0xf79c15, 'author': { 'name': 'OverTrack.gg', 'url': 'https://overtrack.gg', 'icon_url': f'https://cdn.overtrack.gg/static/images/{game_name}.png' } }, }) if create_message_r.status_code == 403: logger.warning( f'Failed to send intro message: {create_message_r.status_code}' ) return Response( f'<html><head></head><body>' f'<p>Could not post to channel - ' f'please verify that the OverTrack bot has permission to post (or to modify channel permissions) then <a href="{request.url}">retry</a></p>' f'</body></html>', content_type='text/html') create_message_r.raise_for_status() create_message = create_message_r.json() print('Create message: ') print(create_message) logger.info( f'Intro message sent: {create_message_r.status_code} - id: {create_message["id"]}' ) key = f'{session.user_id}.{game_name}.{channel_id}' if parent_key == key: logger.warning(f'Integration is overriding existing integration') is_parent = True # TODO: update parent message? # Save the notification settings notification = DiscordBotNotification.create( user_id=session.user_id, discord_user_id=args['discord_user_id'], announce_message_id=create_message['id'], game=game_name, channel_id=channel_id, guild_id=channel_info['guild_id'], guild_name=guild_info['name'], channel_name=channel_info['name'], notification_data={ o.name: o.parse(request.form.getlist(o.name)) for o in bot_options }, is_parent=is_parent, autoapprove_children=is_parent, parent_key=parent_key, ) logger.info(f'Created {notification}') notification_cache[notification.guild_id].append(notification) notification.save() metrics.event('Discord Bot Added', f'User: {session.username} ({session.user_id})\n' + '\n'.join(f'{k}: {v}' for k, v in notification.asdict().items()), tags={ 'module': 'discord_bot', 'function': 'add_to_channel', 'game': game_name, }) return redirect(url_for(blueprint.name + '.root'))