| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- from flask import Flask, request, json, Blueprint, current_app, render_template, jsonify, request, g
- from ownchatbot.db import get_db, clear_fulfilled_rewards
- from ownchatbot.owncast_com import send_chat, send_private_chat
- from ownchatbot.user_handlers import add_user_to_points, change_name, get_users_points, remove_duplicates, get_email_code, set_email_code, award_chat_points, user_in_points, get_all_users_with_user_id
- from ownchatbot.bot_messages import do_reward, help_message, porps
- from ownchatbot.reward_handlers import all_active_goals, all_active_votes, all_active_rewards, save_alerts
- from ownchatbot.donation_handlers import accept_donation, accept_kofi_sub
- import json
- import random
- import hmac
- import hashlib
- ocb = Blueprint('webhooks', __name__)
- def format(rawjson): # Make data legible
- formatted_data = json.dumps(rawjson, indent=4)
- return formatted_data
- @ocb.route('/ocbHook', methods=['POST'])
- def chat_hook():
- prefix = current_app.config['PREFIX']
- data = request.json
- db = get_db()
- if data['type'] in ['CHAT', 'NAME_CHANGED', 'USER_JOINED']: # Check if the viewer is in the chatbot database
- user_id = data['eventData']['user']['id']
- authed = data['eventData']['user']['authenticated']
- display_name = data['eventData']['user']['displayName']
- if add_user_to_points(db, user_id, display_name, authed):
- current_app.logger.debug(f'Added/updated {user_id} database.')
- current_app.logger.debug(f'{display_name}/{user_id}: {data["eventData"]}') # Log all chat messages
- if data['type'] == 'STREAM_STARTED':
- current_app.logger.info('Starting a new stream.')
- if clear_fulfilled_rewards():
- current_app.logger.info('Cleared fulfilled rewards.')
- if data['type'] == 'USER_JOINED': # Do username house cleaning when a viewer joins
- if data['eventData']['user']['authenticated']:
- remove_duplicates(db, user_id, display_name)
- elif data['type'] == 'FEDIVERSE_ENGAGEMENT_FOLLOW':
- alerts_dict = current_app.config['ALERTS']
- data = request.json
- current_app.logger.debug(f'\n\n_______________\n/followHook triggered!\n_______________')
- alerts_dict['follower'] = data['eventData']['name']
- save_alerts(alerts_dict)
- return jsonify({'status': 'success'}), 200
- elif data['type'] == 'NAME_CHANGE':
- user_id = data['eventData']['user']['id']
- new_name = data['eventData']['newName']
- change_name(db, user_id, new_name)
- if data['eventData']['user']['authenticated']:
- remove_duplicates(db, user_id, new_name)
- elif data['type'] == 'CHAT': # If a chat message, sort out what command it is
- user_id = data['eventData']['user']['id']
- display_name = data['eventData']['user']['displayName']
- current_app.logger.info(f'{display_name}/{user_id}: {data["eventData"]["rawBody"]}') # Log all chat messages
- lowercase_msg = data['eventData']['rawBody'].lower() # Convert body to lower case to match reward case
- if lowercase_msg.startswith(f'{prefix}help'): # Send the help message
- help_message(user_id)
- elif lowercase_msg.startswith(f'{prefix}points'): # Get the viewer's current points
- points = get_users_points(db, user_id)
- if points is None:
- send_private_chat(user_id, f'{display_name}, couldn\'t get your points, for some highly technical reason.')
- else:
- send_private_chat(user_id, f'{display_name}, you have {porps(points)}.')
- elif lowercase_msg.startswith(f'{prefix}reg_mail'): # Generate a code to verify users account for email registration
- if current_app.config['KOFI_SETTINGS']['integration'] or current_app.config['GB_SETTINGS']['integration']:
- mail_reg_code = get_email_code(db, user_id)
- if mail_reg_code: # If the viewer already has a code waiting
- send_private_chat(user_id, f'{display_name}, your code is {mail_reg_code}. Enter it into the form on the Stream Rewards Info page, with your email address, to enable donation perks!')
- else: # if not
- mail_reg_code = random.randint(100000, 999999)
- if set_email_code(db, user_id, mail_reg_code):
- send_private_chat(user_id, f'{display_name}, your code is {mail_reg_code}. Enter it into the form on the Stream Rewards Info page, with your email address, to enable donation perks!')
- else:
- send_chat(f'{display_name}, donation integration is not enabled on this stream.')
- elif lowercase_msg.startswith(f'{prefix}rewards'): # Send rewards list
- if current_app.config['REWARDS']:
- rewards_msg = f'Currently active rewards:'
- for reward, details in current_app.config['REWARDS'].items():
- if details.get('categories'):
- if not (set(details['categories']) & set(current_app.config['ACTIVE_CAT'])): # If there are no common categories, continue
- continue
- if 'type' in details and details['type'] == 'goal':
- rewards_msg = f'{rewards_msg}<br>* {prefix}{reward} goal at {details["target"]} contributed {porps(details["target"])}.'
- elif 'type' in details and details['type'] == 'vote':
- rewards_msg = f'{rewards_msg}<br>* {prefix}{reward} vote for {details["price"]} {porps(details["price"])}.'
- else:
- rewards_msg = f'{rewards_msg}<br>* {prefix}{reward} for {details["price"]} {porps(details["price"])}.'
- if 'info' in details:
- rewards_msg = f'{rewards_msg}<br>{details["info"]}'
- else:
- rewards_msg = f'{rewards_msg}'
- else:
- rewards_msg = 'There are currently no active rewards.'
- send_private_chat(user_id, rewards_msg)
- elif lowercase_msg.startswith(f'{prefix}'): # Send to handle rewards
- do_reward(lowercase_msg, user_id)
- return data
- @ocb.route('/kofiHook', methods=["POST"])
- def kofi_hook():
- current_app.logger.info(f'----------------------------------------------------------------------------')
- current_app.logger.info(f'Kofi request')
- if request.content_type == 'application/x-www-form-urlencoded':
- raw_data = request.form.get('data') # Get the kofi data
- if current_app.config['KOFI_SETTINGS']['integration']:
- if raw_data:
- raw_data = json.loads(raw_data)
- is_authed = raw_data['verification_token']
- if is_authed == current_app.config['KOFI_SETTINGS']['token']:
- type = raw_data['type']
- is_public = raw_data['is_public']
- new_sub = raw_data['is_first_subscription_payment']
- message = raw_data['message']
- shop_items = raw_data['shop_items']
- from_name = raw_data['from_name']
- email = raw_data['email']
- amount = raw_data['amount']
- sub_payment = raw_data['is_subscription_payment']
- first_sub = raw_data['is_first_subscription_payment']
- tier_name = raw_data['tier_name']
- if type == 'Shop Order':
- current_app.logger.info(f'{from_name} purchased {format(shop_items)}\nMessage: {message}\n')
- if type == 'Donation':
- donation_info = [is_public, from_name, email, amount, message]
- donation_points = current_app.config['KOFI_SETTINGS']['donation_points']
- if accept_donation(donation_info, donation_points, 'Kofi'):
- current_app.logger.info('Donation processed.')
- if type == 'Subscription':
- if current_app.config['KOFI_SETTINGS']['subs']: # Check that subscriptions are enabled
- if first_sub:
- if tier_name:
- current_app.logger.info(f'{from_name} <{email}> subscribed as a {tier_name} tier member.')
- else:
- current_app.logger.info(f'{from_name} <{email}> subscribed.')
- else:
- if tier_name:
- current_app.logger.info(f'{from_name} <{email}> renewed their {tier_name} tier membership.')
- else:
- current_app.logger.info(f'{from_name} <{email}> renewed their membership.')
- sub_info = [is_public, from_name, email, amount, message, first_sub, tier_name]
- sub_points = current_app.config['KOFI_SETTINGS']['sub_points']
- if accept_kofi_sub(sub_info, sub_points):
- current_app.logger.info('Subscription processed.')
- else:
- current_app.logger.info(f'Kofi membership received, but subscriptions are not enabled. Doing nothing.')
- return jsonify({'status': 'success'}), 200
- else:
- current_app.logger.info(f'Token invalid. Rejecting.')
- return jsonify({'status': 'unauthorized'}), 401
- else:
- return jsonify({'status': 'Failed. No data'}), 400
- return jsonify({'status': 'success'}), 200
- else:
- current_app.logger.error(f'Kofi donation recieved, but Kofi integration is turned off. Doing nothing.')
- return jsonify({'status': 'Failed. Not currently accepting Kofi donations.'}), 400
- else:
- return jsonify({'status': 'Failed. Invalid content type'}), 400
- def sign_payload(payload, secret): # For TESTING purposes
- test_payload = hmac.new(
- secret.encode(),
- payload.encode(),
- hashlib.sha256
- ).hexdigest()
- current_app.logger.info(f'\n\nTest encoded payload output: {test_payload}\n\n')
- return test_payload
- # Not currently in use by GB, but it was. Maybe in the future?
- # Leaving it here just in case.
- def verify_gbhook_signature(payload, signature, secret):
- current_app.logger.info(f'\n\nRecieved Signature: {signature}\n\n')
- expected_signature = hmac.new(
- secret.encode(),
- payload.encode(),
- hashlib.sha256
- ).hexdigest()
- do_sig_check = hmac.compare_digest(signature, expected_signature)
- current_app.logger.debug(f'\n\nExpected Signature: {expected_signature}\nResult: {do_sig_check}\n\n')
- return do_sig_check
- @ocb.route('/gbHook', methods=['POST'])
- def gb_hook():
- current_app.logger.info(f'----------------------------------------------------------------------------')
- current_app.logger.info(f'GiveButter request')
- headers = request.headers
- current_app.logger.debug('Headers:')
- for header, value in headers.items():
- current_app.logger.debug(f'> {header}: {value}')
- signature = request.headers.get('Signature')
- gb_secret = current_app.config['GB_SETTINGS']['secret']
- raw_data = request.get_data(as_text=True)
- current_app.logger.debug(f'\n\n{raw_data}\n\n')
- event = request.json
- if signature == gb_secret:
- if current_app.config['GB_SETTINGS']['integration']:
- try:
- event_type = event['event']
- if event_type == 'transaction.succeeded':
- data = event['data']
- custom_fields = data["custom_fields"]
- if custom_fields:
- for field in custom_fields: # Get anonymous donation custom field, if it's there'
- if field['title'].lower() == 'anonymous':
- if field['value'].lower() == "yes":
- is_public = False
- current_app.logger.info(f'Donation is anonymous.')
- else:
- is_public = True
- current_app.logger.info(f'Donation is not anonymous.')
- else: # If it's not there, set to anonymous
- is_public = False
- current_app.logger.info(f'No anoymous donation custom field found. Assuming anonymous')
- else: # No custom fields found, set to anonymous
- is_public = False
- current_app.logger.info(f'No custom fields found. Assuming anonymous')
- from_name = f'{data["first_name"]} {data["last_name"]}'
- email = data['email']
- amount = data['amount']
- current_app.logger.debug(f'\nFrom: {from_name}\nEmail: {email}\nAmount: {amount}\nAnonymous: {is_public}\n\n')
- donation_info = [is_public, from_name, email, amount, '']
- donation_points = current_app.config['GB_SETTINGS']['donation_points']
- points_for_donations = current_app.config['GB_SETTINGS']['donations']
- if points_for_donations: # Are we giving points for donations?
- if accept_donation(donation_info, donation_points, 'GiveButter'):
- current_app.logger.info(f'Donation processed.')
- else:
- current_app.logger.info(f'Points for donations is disabled. Doing nothing.')
- else:
- current_app.logger.info(f'Unhandled event type: {event_type}')
- except Exception as pgberror:
- current_app.logger.error(f'General exception processing gbhook: {pgberror}')
- else:
- current_app.logger.error(f'GiveButter donation recieved, but GiveButter integration is turned off. Doing nothing.')
- return jsonify({'status': 'Failed. Not currently accepting GiveButter donations.'}), 400
- return jsonify({'status': 'Success'}), 200 # If signature matched
- else:
- return jsonify({'status': 'Signature invalid'}), 401 # If signature didn't match
- @ocb.route('/checkFollows') # Polled by follower.html template to check for new followers
- def check_follows():
- alerts_dict = current_app.config['ALERTS']
- follower = {'name': alerts_dict['follower']}
- if follower['name']:
- current_app.logger.info(f'New follower: \"{follower["name"]}\"')
- alerts_dict['follower'] = ''
- save_alerts(alerts_dict)
- return jsonify(follower)
- else:
- current_app.logger.debug(f'No new followers')
- return jsonify(None)
- @ocb.route('/checkGoals') # Polled by rgoal.html template to check for if a goal has been passed
- def check_goals():
- alerts_dict = current_app.config['ALERTS']
- rgoals = {'name': alerts_dict['g_name']}
- if rgoals['name']:
- current_app.logger.info(rgoals)
- alerts_dict['g_name'] = ''
- alerts_dict['g_reward'] = ''
- save_alerts(alerts_dict)
- return jsonify(rgoals)
- else:
- current_app.logger.debug(f'No new goals reached')
- return jsonify(None)
- @ocb.route('/checkMilestones') # Polled by rmilestone.html template to check if a milestone has been passed
- def check_milestones():
- alerts_dict = current_app.config['ALERTS']
- rmilestones = {'name': alerts_dict['m_name']}
- if rmilestones['name']:
- current_app.logger.info(rmilestones)
- alerts_dict['m_name'] = ''
- alerts_dict['m_reward'] = ''
- save_alerts(alerts_dict)
- return jsonify(rmilestones)
- else:
- current_app.logger.debug(f'No new milestones passed')
- return jsonify(None)
- @ocb.route('/checkGBs') # Polled by rgbdonation.html template to check for new givebutter donations
- def check_gbs():
- alerts_dict = current_app.config['ALERTS']
- rgbs = {'name': alerts_dict['gb_name']}
- if rgbs['name']:
- current_app.logger.info(rgbs)
- alerts_dict['gb_name'] = ''
- save_alerts(alerts_dict)
- return jsonify(rgbs)
- else:
- current_app.logger.debug(f'No GiveButter donation')
- return jsonify(None)
|