6 Commits 0560fb2eed ... 329bee9825

Auteur SHA1 Bericht Datum
  allens 329bee9825 Removed some stuff, added some stuff. 2 weken geleden
  allens a5c018e804 Added agreement blurb 2 weken geleden
  allens 03493f13c0 Updated install instructions, after renaming init-ocb.sh to install.sh 2 weken geleden
  allens 26ce094ca4 Updated with information on new Kofi options 2 weken geleden
  allens f2fe35abe9 Incremented version number 2 weken geleden
  allens 76e158e0a0 Got basic Kofi membership functional. No tier support yet. 2 weken geleden

+ 6 - 9
README.md

@@ -32,8 +32,7 @@ A chatbot for Owncast, enabling viewers to earn points which can be used to purc
  * Viewer management for manually adjusting viewers' points.
  * A queue manager, where the streamer can mark rewards fulfilled as they go, or refund rewards if needed.
 * Kofi integration.
- * Your viewers can get points for donating via kofi.
- * *Monthly dontation/subscription and shop integration coming next*
+ * Your viewers can get points for donating via Kofi, and as part of their monthly Kofi membership.
 
 ## Screenshots
 #### Votes and Goals Overlays
@@ -54,11 +53,9 @@ A chatbot for Owncast, enabling viewers to earn points which can be used to purc
 
 ## Roadmap
 *Not necessarily in this order*
-* Kofi monthly subscription benefits
- * Subscriber-only emojis, monthly points awards, etc.
+* Streak tracker, to track how many consecutive streams a viewer has watched, and reward accordingly
 * Get emojis in OwnchatBot panels working correctly, because my partner insists on it
 * Audible sound when a goal is reached
-* Streak tracker, to track how many consecutive streams a viewer has watched, and reward accordingly
 * Option to only allow authenticated users, followers, or Kofi subscribers to redeem certain rewards.
 * !Timer - Set a timer that will sound an audible alarm. A command only available to the streamer or mods.
 * A "watch" function that watches chat for certain words or phrases, and responds with preset messages.
@@ -74,7 +71,7 @@ A chatbot for Owncast, enabling viewers to earn points which can be used to purc
 
 3. To start the install, run:
     ```bash
-    bash init-ocb.sh
+    bash install.sh
     ```
 4. The last lines of init-ocb's output tell you how to proceed to the management panel, where you can set up Owncast integraion, and other odds and ends.
 
@@ -147,7 +144,7 @@ You need to create a button on your Owncast page, so your viewers can access the
 
 #### Make sure you don't lose your viewers' benefits! It is extremely important that you back up your instance folder daily, if not hourly.
 
-Integration is accomplished via a webhook triggered by Kofi every time a donation is made. Again, this webhook is your external OwnchatBot server address with `/kofiHook` appended.
+Integration is accomplished via a webhook triggered by Kofi every time a donation or subscription is made. Again, this webhook is your external OwnchatBot server address with `/kofiHook` appended.
 
 Paste the webhook address into Kofi -> More -> API -> Webhooks -> Webhook URL.
   ```
@@ -160,9 +157,9 @@ You need the verification token the Kofi to send when it triggers the webhook.
 Below "Webhook URL", click on "Advanced".
 A pre-generated token will already be there. Copy that token, and paste it into the OCB Management Panel -> Settings -> Kofi Integration -> Verification Token. Then click "Save Changes".
 
-OwnchatBot associates viewer accounts with Kofi accounts using their email address. So in order for viewers to get Kofi benefits in-stream, they must enter their email address in OwnchatBot viewer panel -> OwnchatBot Info -> Kofi. It **must** be the same email address associated with their Kofi account.
+OwnchatBot associates viewer accounts with Kofi accounts using the viewer's email address. So in order for viewers to get Kofi benefits in-stream, they must enter their email address in OwnchatBot viewer panel -> OwnchatBot Info -> Kofi. It **must** be the same email address associated with their Kofi account.
 
-If a viewer donates before entering their email address, OwnchatBot creates a temporary entry with their email and donation amount, and then applies it once the viewer enters their email address into the viewer panel. If there is any sort of mix-up here, the streamer can manually connect the viewer's email and account via the management panel by entering the email into the viewers account. OwnchatBot will do the merge automatically.
+If a viewer donates/subscribes before entering their email address, OwnchatBot creates a temporary entry with their email and points award, and then applies it once the viewer enters their email address into the viewer panel. If there is any sort of mix-up here, the streamer can manually connect the viewer's email and account via the management panel by entering the email into the viewers account. OwnchatBot will do the merge automatically.
 
 Email addresses are **ONLY** used for Kofi integration. They are not sent to any other individual or company, will not be used to create or send mailing lists of any kind, or for any other purposes. Ever.
 

+ 2 - 3
TODO.md

@@ -1,3 +1,2 @@
-* Get Kofi subscriptions and tiers sorted out.
-* Add acknowledgement to install script, that I am not responsible for any loss of viewers data, and the streamer is solely responsible for backing it up and maintaining it.
-* Add celebration emojis in messages for things like goals and milestones.
+* Add option to set up Kofi subscriber-only awards
+* Add customizable celebration emojis in messages for things like goals and milestones.

+ 6 - 0
init-ocb.sh → install.sh

@@ -55,6 +55,12 @@ update_config() {  # Generate keys for SECRET_KEY and MGMT_AUTH
         exit 1  # Exit the script with a non-zero status
     fi
 }
+read -p "This app includes an option to integrate Kofi donations and membership rewards into your stream. If you use this feature, you acknowledge that you alone are responsible for backing up and securing the OwnchatBot folder, so as not to lose any data related to rewards your viewiers earned by spending money on Kofi. The developer(s) of OwnchatBot assume no responsibility for any loss of such data. Do you agree to these terms? Type \"yes\" or \"no\": " agreement  # Prompt the user to accept the agreement
+
+if [[ "$agreement" != "yes" ]]; then
+    echo -e "\n\nAgreement declined by user. OwnchatBot not installed.\n\n"
+    exit 0
+fi
 
 if check_venv; then
     create_venv

+ 12 - 10
ownchatbot/__init__.py

@@ -44,14 +44,11 @@ def create_app(test_config=None):
     db.init_app(app)
 
     def announce():
-        if app.config['ANNOUNCE_ENABLE']:  # If announcements are enabled
-            global current_index
-            announcements = app.config['ANNOUNCEMENTS']
-            message = announcements[current_index]
-            send_system_chat(message)
-            current_index = (current_index + 1) % len(announcements)
-        else:
-            app.logger.info(f'Not live, so not sending announcement.')
+        global current_index
+        announcements = app.config['ANNOUNCEMENTS']
+        message = announcements[current_index]
+        send_system_chat(message)
+        current_index = (current_index + 1) % len(announcements)
                 
     def award_job():
         with app.app_context():
@@ -60,8 +57,13 @@ def create_app(test_config=None):
                 
     def announce_job():
         with app.app_context():
-            if live_now():  # If stream is live
-                announce()
+            if app.config['ANNOUNCE_ENABLE']:  # If announcements are enabled
+                if live_now():  # If stream is live
+                    announce()
+                else:
+                    app.logger.debug(f'Not live, so not sending announcement.')
+            else:
+                app.logger.debug(f'Announcements not enabled.')
 
     jorel_master_of_scheduling = BackgroundScheduler()
     points_seconds = app.config['POINTS_INTERVAL'] * 60

+ 15 - 3
ownchatbot/bot_messages.py

@@ -136,11 +136,23 @@ def help_message(user_id):
             <b>{prefix}rewards</b> to see a list of currently active rewards.'
     if kofi_integration:
         message = f'{message}<br><br>\
-            Kofi is enabled! <br>\
+            <b><u>Kofi is enabled!</b></u><br>\
             Authenticate with Owncast, and enter your email address into the Stream Rewards Info page to get Kofi perks.'
-        if kofi_settings['tips']:
+        if kofi_settings['donations']:
+            if kofi_settings["donation_points"] == 1:
+                d_points_label = 'point'
+            else:
+                d_points_label = 'points'
+            if kofi_settings["sub_points"] == 1:
+                s_points_label = 'point'
+            else:
+                s_points_label = 'points'
             message = f'{message}<br>\
-            You\'ll recieve {kofi_settings["tip_points"]} points for every dollar you tip on Kofi.'
+            You\'ll recieve {kofi_settings["donation_points"]} {d_points_label} for every dollar you donate on Kofi'
+        if kofi_settings['subs']:
+            message = f'{message}, and {kofi_settings["sub_points"]} {s_points_label} every month for subscribing to my Kofi page.'
+        else:
+            message = f'{message}.'
     send_private_chat(user_id, message)
 
 

+ 17 - 16
ownchatbot/db.py

@@ -5,6 +5,7 @@ from flask.cli import with_appcontext
 import click
 from time import time
 import os
+import logging
 
 
 def rem_from_queue(reward_name):  # Remove a reward from the queue
@@ -16,7 +17,7 @@ def rem_from_queue(reward_name):  # Remove a reward from the queue
         )
         db.commit()
     except sqlite3.Error as rfqerror:
-        print(f'Couldn\'t remove {reward_name} from reward queue: {rfqerror.args[0]}')
+        current_app.logger.error(f'Couldn\'t remove {reward_name} from reward queue: {rfqerror.args[0]}')
         return False
     return True
 
@@ -95,7 +96,7 @@ def clear_reward_queue():  # Completely clear the reward queue
         )
         db.commit()
     except sqlite3.Error as serror:
-        print(f'Couldn\'t clear reward queue: {serror.args[0]}')
+        current_app.logger.error(f'Couldn\'t clear reward queue: {serror.args[0]}')
         return False
     return True
 
@@ -109,7 +110,7 @@ def clear_fulfilled_rewards():  # Clears only fulfilled rewards from the queue
         )
         db.commit()
     except sqlite3.Error as serror:
-        print(f'Couldn\'t clear fulfilled rewards: {serror.args[0]}')
+        current_app.logger.error(f'Couldn\'t clear fulfilled rewards: {serror.args[0]}')
         return False
     return True
 
@@ -121,7 +122,7 @@ def rem_all_votes():  # USED TO BE "clear_votes" Clear all votes from the databa
         db.execute("DELETE FROM votes")
         db.commit()
     except sqlite3.Error as cverror:
-        print(f'Couldn\'t clear all votes: {cverror.args[0]}')
+        current_app.logger.error(f'Couldn\'t clear all votes: {cverror.args[0]}')
         return False
     if put_votes(db):
         return True
@@ -134,7 +135,7 @@ def rem_vote():  # Remove a single vote from the database
         db.execute("DELETE FROM votes WHERE name = ?", ('vote',))
         db.commit()
     except sqlite3.Error as rverror:
-        print(f'Couldn\'t remove \"{vote}\" from database: {rverror.args[0]}')
+        current_app.logger.error(f'Couldn\'t remove \"{vote}\" from database: {rverror.args[0]}')
         return False
     if put_votes(db):
         return True
@@ -152,7 +153,7 @@ def is_cool(reward_name):  # Check if a reward is cooling down.
                 )
             current_cds = cursor.fetchall()
         except sqlite3.Error as icerror:
-            print(f'Couldn\'t get \"{reward_name}\" from database: {icerror.args[0]}')
+            current_app.logger.error(f'Couldn\'t get \"{reward_name}\" from database: {icerror.args[0]}')
         if current_cds:
             last_time = current_cds[0][0]
             hot_time = current_time - last_time
@@ -168,7 +169,7 @@ def is_cool(reward_name):  # Check if a reward is cooling down.
                     db.commit()
                     return True, 0
                 except sqlite3.Error as scerror:
-                    print(f'Couldn\'t update \"{reward_name}\"\'s cooldown time in the database: {scerror.args[0]}')
+                    current_app.logger.error(f'Couldn\'t update \"{reward_name}\"\'s cooldown time in the database: {scerror.args[0]}')
         else:  # If it is not in the database, add it and return True
             try:
                 db.execute(
@@ -178,7 +179,7 @@ def is_cool(reward_name):  # Check if a reward is cooling down.
                 db.commit()
                 return True, 0
             except sqlite3.Error as scerror:
-                print(f'Couldn\'t add \"{reward_name}\" to database: {scerror.args[0]}')
+                current_app.logger.error(f'Couldn\'t add \"{reward_name}\" to database: {scerror.args[0]}')
     else:  # If the redeem has no cooldown
         return True, 0
 
@@ -191,7 +192,7 @@ def rem_cool(reward_name):  # Remove a reward from the database
             )
         current_cds = cursor.fetchall()
     except sqlite3.Error as icerror:
-        print(f'Couldn\'t remove \"{reward_name}\" from database: {icerror.args[0]}')
+        current_app.logger.error(f'Couldn\'t remove \"{reward_name}\" from database: {icerror.args[0]}')
         return False
     return True
 
@@ -207,7 +208,7 @@ def put_votes(db):  # Reread votes from rewards.py, and sync with database
                 )
                 db.commit()
             except sqlite3.Error as serror:
-                print(f'Couldn\'t insert \"{vote}\" into database: {serror.args[0]}')
+                current_app.logger.error(f'Couldn\'t insert \"{vote}\" into database: {serror.args[0]}')
                 return False
     return True
 
@@ -229,7 +230,7 @@ def reread_votes():  # Reread votes from rewards.py, and sync with database
             cursor.execute("DELETE FROM votes WHERE name = ?", (vote,))
         db.commit()
     except sqlite3.Error as serror:
-        print(f'Couldn\'t clear deleted votes from database: {serror.args[0]}')
+        current_app.logger.error(f'Couldn\'t clear deleted votes from database: {serror.args[0]}')
         return False
 
     try:  # Add new votes found in rewards.py
@@ -254,7 +255,7 @@ def reread_votes():  # Reread votes from rewards.py, and sync with database
                     )
                 db.commit()
     except sqlite3.Error as serror:
-        print(f'Couldn\'t insert \"{vote}\" into database: {serror.args[0]}')
+        current_app.logger.error(f'Couldn\'t insert \"{vote}\" into database: {serror.args[0]}')
         return False
     return True
 
@@ -276,7 +277,7 @@ def reread_goals():  # Reread goals from rewards.py, and sync with database
             cursor.execute("DELETE FROM goals WHERE name = ?", (goal,))
         db.commit()
     except sqlite3.Error as serror:
-        print(f'Couldn\'t clear removed goals from database: {serror.args[0]}')
+        current_app.logger.error(f'Couldn\'t clear removed goals from database: {serror.args[0]}')
         return False
 
     try:  # Add new goals found in rewards.py
@@ -299,14 +300,14 @@ def reread_goals():  # Reread goals from rewards.py, and sync with database
                     )
         db.commit()
     except sqlite3.Error as serror:
-        print(f'Couldn\'t insert \"{reward}\" into database: {serror.args[0]}')
+        current_app.logger.error(f'Couldn\'t insert \"{reward}\" into database: {serror.args[0]}')
         return False
     return True
 
 
 def reset_goal(goal):  # Set goal progress back to zero
     if goal not in current_app.config['REWARDS']:  # If it doesn't exist in rewards.py
-        print(f'Couldn\'t reset goal, {goal} not in rewards file.')
+        current_app.logger.error(f'Couldn\'t reset goal, {goal} not in rewards file.')
         return False
     try:
         db = get_db()
@@ -326,7 +327,7 @@ def reset_goal(goal):  # Set goal progress back to zero
 
 def reset_vote(vote):
     if vote not in current_app.config['REWARDS']:  # Check if it exists in rewards.py
-        print(f'Couldn\'t reset vote, {vote} not in rewards file.')
+        current_app.logger.error(f'Couldn\'t reset vote, {vote} not in rewards file.')
         return False
     else:
         try:

+ 4 - 2
ownchatbot/defaults/kofi.py

@@ -1,6 +1,8 @@
 KOFI_SETTINGS = {
-    "tips": True,  # Reward tips with points
-    "tip_points": 100,  # How many points per dollar tipped?
+    "donations": True,  # Reward donations with points
+    "donation_points": 100,  # How many points per dollar donated?
+    "subs": False,  # Reward subscriptions with points
+    "sub_points": 1000,  # How many points per month?
     "kofi_url": "https://",  # What is the URL of your Kofi page?
     "kofi_logo": "kofi_symbol.png"  # Which Kofi logo are we using in the viewer panel?
 }

+ 50 - 5
ownchatbot/kofi_handlers.py

@@ -2,19 +2,20 @@ from flask import current_app
 from sqlite3 import Error
 from ownchatbot.db import get_db
 from ownchatbot.user_handlers import get_id_by_email, award_chat_points, add_email_to_points, get_all_users_with_user_id
-from ownchatbot.owncast_com import send_chat
+from ownchatbot.owncast_com import send_chat, send_private_chat
+from ownchatbot.bot_messages import porps
 import json
 import os
 
 
-def accept_donation(donation_info, tip_points):
+def accept_donation(donation_info, donation_points):
     db = get_db()
     is_public = donation_info[0]
     email = donation_info[2]
     amount = donation_info[3]
     amount = int(float(amount))  # Convert from str to int
     message = donation_info[4]
-    points = amount * tip_points  # Multiply by streamers tip point award
+    points = amount * donation_points  # Multiply by streamers donation points award
     ids = get_id_by_email(db, email)
     if not ids:  # If no id found with that email address
         if add_email_to_points(db, email, points):  # Create empty account with email and points
@@ -25,9 +26,9 @@ def accept_donation(donation_info, tip_points):
             if award_chat_points(db, id[0], points):  # Grant points
                 for user in get_all_users_with_user_id(db, id[0]):
                     name = user[1]
-                current_app.logger.info(f'Granted user id {id[0]} {points} points for their ${amount} donation.')
+                current_app.logger.info(f'Granted user id {id[0]} {porps(points)} for their ${amount} donation.')
     if is_public:
-        message = f'{name} got {points} points for tipping ${amount} on Kofi!'
+        message = f'{name} got {porps(points)} for donating ${amount} on Kofi!'
         current_app.logger.info(f'Public donation of ${amount} received from {name}')
     else:
         message = None
@@ -36,6 +37,50 @@ def accept_donation(donation_info, tip_points):
         send_chat(message)
 
 
+def accept_sub(sub_info, sub_points):
+    db = get_db()
+    is_public = sub_info[0]
+    name = sub_info[1]
+    email = sub_info[2]
+    amount = sub_info[3]
+    amount = int(float(amount))  # Convert from str to int
+    message = sub_info[4]
+    first_sub = sub_info[5]
+    tier_name = sub_info[6]
+    points = sub_points
+    ids = get_id_by_email(db, email)
+    if not ids:  # If no id found with that email address
+        if add_email_to_points(db, email, points):  # Create empty account with email and points
+            name = 'Someone'
+            current_app.logger.info(f'No user with email \"{email}\" found in database, created empty account.')
+    else:  # Grant points to the corresponding id
+        for id in ids:
+            if award_chat_points(db, id[0], points):  # Grant points
+                for user in get_all_users_with_user_id(db, id[0]):
+                    name = user[1]  # Assign name from points table
+                current_app.logger.info(f'Awarded user id {id[0]} {porps(points)} for their subscription.')                
+    if is_public:
+        if not name:  # If no name in points table
+            name = 'Someone'
+        if first_sub:
+            message = f'{name} got {porps(points)} for their one month membership on Kofi!'
+            current_app.logger.info(f'Public subscription received from {name}')
+        else:
+            message = f'{name} got {porps(points)} for renewing their membership on Kofi!'
+            current_app.logger.info(f'Public subscription renewal received from {name}')
+        send_chat(message)  # Send message publicly if a public membership
+    else:
+        if not name:  # If no name in points table
+            name = sub_info[1]  # Assign name from Kofi response
+        if first_sub:
+            message = f'Thanks so much for your subscribing to my Kofi! You\'ve been awarded {porps(points)}!'
+            current_app.logger.info(f'Private subscription received from {name}')
+        else:
+            message = f'Thanks so much for renewing your membership to my Kofi! You\'ve been awarded {porps(points)}!'
+            current_app.logger.info(f'Private subscription renewal received from {name}')
+        send_private_chat(id[0], message)
+            
+
 def save_kofi_settings(ksettings_info):  # Write rewards to kofi.py
     settings_file = os.path.join(current_app.instance_path, 'kofi.py')
     try:

+ 26 - 19
ownchatbot/owncast_com.py

@@ -13,11 +13,16 @@ def get_client_id(user_id):
     try:
         response = requests.get(url, headers=headers)
         response = response.json()
-        client_id = response['connectedClients'][0]['id']
+        if response["connectedClients"]:  # Make sure this is an actual, connected client
+            client_id = response['connectedClients'][0]['id']
+            current_app.logger.debug(f'Got client id {client_id}')
+            return client_id
+        else:
+            current_app.logger.info('Not a connected client. Can\'t get client ID.')
+            return False
     except requests.exceptions.RequestException as gcierror:
         current_app.logger.error(f'Couldn\'t get client id from Owncast: {gcierror.args[0]}')
-    current_app.logger.debug(f'Got client id {client_id}')
-    return client_id
+        return False
 
 
 def live_now():  # Check if stream is live
@@ -62,30 +67,32 @@ def send_chat(message):  # Send message to owncast chat
     try:
         response = requests.post(url, headers=headers, json={'body': message})
     except requests.exceptions.RequestException as scerror:
-        current_app.logger.error(f'Couldn\'t send {message} to Owncast: {scerror.args[0]}')
+        current_app.logger.error(f'Couldn\'t send \"{message}\" to Owncast: {scerror.args[0]}')
         return
     if response.status_code != 200:
-        current_app.logger.error(f'Couldn\'t send {message} to Owncast: {response.status_code}.')
+        current_app.logger.error(f'Couldn\'t send \"{message}\" to Owncast: {response.status_code}.')
         return
     return response.json()
 
 
 def send_private_chat(user_id, message):
     client_id = get_client_id(user_id)
-    owncast_url = current_app.config['OWNCAST_URL']
-    access_token = current_app.config['ACCESS_TOKEN']
-    url = f'{owncast_url}/api/integrations/chat/system/client/{client_id}'
-    auth_bearer = f'Bearer {access_token}'
-    headers = {'Authorization': auth_bearer}
-    try:
-        response = requests.post(url, headers=headers, json={'body': message})
-    except requests.exceptions.RequestException as swerror:
-        current_app.logger.error(f'Couldn\'t send {message} to Owncast: {swerror.args[0]}')
-        sys.exit()
-    if response.status_code != 200:
-        current_app.logger.error(f'Couldn\'t send {message} to Owncast: {response.status_code}.')
-        sys.exit()
-    response = response.json()
+    if client_id or client_id == 0:  # If no client ID is found, don't try to send
+        owncast_url = current_app.config['OWNCAST_URL']
+        access_token = current_app.config['ACCESS_TOKEN']
+        url = f'{owncast_url}/api/integrations/chat/system/client/{client_id}'
+        auth_bearer = f'Bearer {access_token}'
+        headers = {'Authorization': auth_bearer}
+        try:
+            response = requests.post(url, headers=headers, json={'body': message})
+        except requests.exceptions.RequestException as swerror:
+            current_app.logger.error(f'Couldn\'t send \"{message}\" to Owncast: {swerror.args[0]}')
+        except Exception as swerror:
+            current_app.logger.error(f'Couldn\'t send \"{message}\" to Owncast: {swerror.args[0]}')
+        if response.status_code != 200:
+            current_app.logger.error(f'Couldn\'t send \"{message}\" to Owncast: {response.status_code}.')
+    else:
+        current_app.logger.info(f'Couldn\'t send \"{message}\" to Owncast. Not a connected client.')
 
 
 def send_system_chat(message):

+ 33 - 9
ownchatbot/templates/mgmt.html

@@ -463,6 +463,8 @@
             <form method="POST" action="/mgmt/ksettings">
                 <table>
                 <h3>Kofi Settings</h3>
+                
+                <h4> Donations </h4>
                     <thead>
                         <tr style="border-bottom: none;">
                             <th style="width: 20%;"></th>
@@ -471,18 +473,18 @@
                         </tr>
                     </thead>
                     <tr>
-                        <td> <label for="enable_tips">Enable points for tips:</label> </td>
-                        {% if kofi_settings['tips'] %}
-                        <td> <input type="checkbox" name="enable_tips" value="{{ kofi_settings['tips'] }}" checked> </td>
+                        <td> <label for="enable_donations">Enable points for donations:</label> </td>
+                        {% if kofi_settings['donations'] %}
+                        <td> <input type="checkbox" name="enable_donations" value="{{ kofi_settings['donations'] }}" checked> </td>
                         {% else %}
-                        <td> <input type="checkbox" name="enable_tips" value="{{ kofi_settings['tips'] }}"> </td>
+                        <td> <input type="checkbox" name="enable_donations" value="{{ kofi_settings['donations'] }}"> </td>
                         {% endif %}
-                        <td>Enable awarding points for tips</td>
+                        <td>Enable awarding points for donations</td>
                     </tr>
                     <tr>
-                        <td> <label for="set_tip_points">Points per dollar:</label> </td>
-                        <td> <input type="number" name="set_tip_points" value="{{ kofi_settings['tip_points'] }}" size="5" required> points</td>
-                        <td>How many points should viewers recieve, for every dollar they tip?</td>
+                        <td> <label for="set_donation_points">Points per dollar:</label> </td>
+                        <td> <input type="number" name="set_donation_points" value="{{ kofi_settings['donation_points'] }}" size="5" required> points</td>
+                        <td>How many points should viewers recieve, for every dollar they donate?</td>
                     </tr>
                     <tr>
                         <td> <label for="kofi_url">Kofi Page:</label> </td>
@@ -491,6 +493,28 @@
                     </tr>
                     <tr>
                         <table>
+                        <h4> Subscriptions </h4>
+                            <tbody>
+                                <tr>
+                                    <td> <label for="enable_subs">Enable points for subscriptions:</label> </td>
+                                    {% if kofi_settings['subs'] %}
+                                    <td> <input type="checkbox" name="enable_subs" value="{{ kofi_settings['subs'] }}" checked> </td>
+                                    {% else %}
+                                    <td> <input type="checkbox" name="enable_subs" value="{{ kofi_settings['subs'] }}"> </td>
+                                    {% endif %}
+                                    <td>Enable awarding points for monthly subscriptions</td>
+                                </tr>
+                                <tr>
+                                    <td> <label for="sub_points">Points per month:</label> </td>
+                                    <td> <input type="number" name="sub_points" value="{{ kofi_settings['sub_points'] }}" size="6" required> points</td>
+                                    <td>How many points should subscribers recieve every month?</td>
+                                </tr>
+                                        </tbody>
+                                    </table>
+                                </tr>
+                                <tr>
+                        <table>
+                        <h4> Logo </h4>
                             <tbody>
                                 <tr style="border-bottom: none;">
                                 Which logo would you like to use?
@@ -518,7 +542,7 @@
                 <br><button class="button button2" type="submit">Save Changes</button><br>
             </form>
             <br><br>
-	    <i>Kofi subscription support coming soon.</i>
+	    <i>Kofi subscription tier support coming soon.</i>
         </body>
     </div>
     

+ 17 - 9
ownchatbot/templates/userpanel.html

@@ -35,16 +35,23 @@
         {% if kofi_integration %}
             <h4>Kofi Integration</h4>
             You must be authenticated with Owncast to take advantage of Kofi integration.<br>
-            {% if kofi_settings['tips'] %}
-                {% set points_label = 'point' if tip_points == 1 else 'points' %}
-                You are awarded {{ kofi_settings['tip_points'] }} {{ points_label }} for every dollar you tip on Kofi.<br>
+            
+            {% if kofi_settings['donations'] %}
+                {% set d_points_label = 'point' if donation_points == 1 else 'points' %}
+                You are awarded {{ kofi_settings['donation_points'] }} {{ d_points_label }} for every dollar you donate on Kofi.<br>
+            {% endif %}
+            
+            {% if kofi_settings['subs'] %}
+                {% set s_points_label = 'point' if sub_points == 1 else 'points' %}
+                Subscribers get {{ kofi_settings['sub_points'] }} {{ s_points_label }} every month.<br>
             {% endif %}
+            
             {% for user in users %}
             
             <div>
                 {% if user['user_authed'] %}
-                    <a href="{{ kofi_settings['kofi_url'] }}/tip" target="new"><img src="/static/img/kofi/{{ kofi_settings['kofi_logo'] }}"></a><br>
-                    OwnchatBot recognizes your Kofi account by your email address. In order for OwnchatBot to award your tip points, you must enter the email address associated with your Kofi account here.<br><br>
+                    <a href="{{ kofi_settings['kofi_url'] }}/donate" target="new"><img src="/static/img/kofi/{{ kofi_settings['kofi_logo'] }}"></a><br>
+                    OwnchatBot recognizes your Kofi account by your email address. In order for OwnchatBot to award your donation points, you must enter the email address associated with your Kofi account here.<br><br>
                     
                     {% with messages = get_flashed_messages(with_categories=true) %}
                         {% if messages %}
@@ -79,7 +86,7 @@
         <h4>Other stuff</h4>
         <div>
             OwnchatBot can be downloaded from <a href=https://git.deadtom.me/deadtom/OwnchatBot>https://git.deadtom.me/deadtom/OwnchatBot</a>.<br>
-            If you are thrilled to death with OwnchatBot, and want to throw a little monetary love his way, <a href=https://ko-fi.com/deadtom>he's on Kofi</a>.<br>
+            If you are thrilled to death with OwnchatBot, and want to throw a little monetary love the developer's way, <a href=https://ko-fi.com/deadtom>he's on Kofi</a>.<br>
             OwnchatBot © 2025 by <a href=https://www.deadtom.me>DeadTOm</a> is licensed under <a href=https://creativecommons.org/licenses/by-sa/4.0/>Creative Commons Attribution-ShareAlike 4.0 International</a>.
         </div>
 	
@@ -97,9 +104,10 @@
                 &nbsp;&nbsp;{{ user[1] }}, you currently have {{ user[2] }} {{ points_label }}.<br>
                 &nbsp;&nbsp;You are accruing {{ points_award }} {{ points_label }} every {{ points_interval }} {{ minutes_label }}.
                 {% if kofi_integration %}
-                    {% if kofi_settings['tips'] %}
-                        {% set points_label = 'point' if tip_points == 1 else 'points' %}
-                        <br>&nbsp;&nbsp;You can also get {{ kofi_settings['tip_points'] }} {{ points_label }} for every dollar you tip me on Kofi. 🤑
+                    {% if kofi_settings['donations'] %}
+                        {% set d_points_label = 'point' if donation_points == 1 else 'points' %}
+                        {% set s_points_label = 'point' if sub_points == 1 else 'points' %}
+                        <br>&nbsp;&nbsp;You can also get {{ kofi_settings['donation_points'] }} {{ d_points_label }} for every dollar you donate on Kofi{% if kofi_settings['subs'] %}, and {{ kofi_settings['sub_points'] }} {{ s_points_label }} every month for subscribing to my Kofi page{% endif %}. 🤑
                     {% endif %}
                 {% endif %}
             {% endfor %}

+ 7 - 4
ownchatbot/web_panels.py

@@ -277,13 +277,16 @@ def announcements():
 def ksettings():
     kofi_settings_dict = current_app.config['KOFI_SETTINGS']
     if request.method == 'POST':
-        enable_tips = 'enable_tips' in request.form
-        set_tip_points = request.form['set_tip_points']
+        enable_donations = 'enable_donations' in request.form
+        set_donation_points = request.form['set_donation_points']
+        enable_subs = 'enable_subs' in request.form
+        sub_points = int(request.form['sub_points'])
         kofi_url = request.form['kofi_url']
         kofi_logo = request.form.get('kofi_logo')
         
-        kofi_settings_dict['tips'] = enable_tips
-        kofi_settings_dict['tip_points'] = int(set_tip_points)
+        kofi_settings_dict['donations'] = enable_donations
+        kofi_settings_dict['subs'] = enable_subs
+        kofi_settings_dict['sub_points'] = sub_points
         kofi_settings_dict['kofi_url'] = kofi_url
         kofi_settings_dict['kofi_logo'] = kofi_logo
         if save_kofi_settings(kofi_settings_dict):

+ 21 - 16
ownchatbot/webhooks.py

@@ -4,7 +4,7 @@ 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
 from ownchatbot.bot_messages import do_reward, help_message
 from ownchatbot.reward_handlers import all_active_goals, all_active_votes, all_active_rewards
-from ownchatbot.kofi_handlers import accept_donation
+from ownchatbot.kofi_handlers import accept_donation, accept_sub
 import json
 import random
 
@@ -29,34 +29,39 @@ def kofiHook():
             if is_authed == current_app.config['KOFI_TOKEN']:
                 type = raw_data['type']
                 is_public = raw_data['is_public']
-                if is_public:
-                    from_name = raw_data['from_name']
                 new_sub = raw_data['is_first_subscription_payment']
                 message = raw_data['message']
                 shop_items = raw_data['shop_items']
-                name = raw_data['from_name']
+                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'\n{name} purchased {format(shop_items)}\nMessage: {message}\n')
+                    current_app.logger.info(f'{from_name} purchased {format(shop_items)}\nMessage: {message}\n')
                 if type == 'Donation':
-                    donation_info = [is_public, name, email, amount, message]
-                    tip_points = current_app.config['KOFI_SETTINGS']['tip_points']
-                    accept_donation(donation_info, tip_points)
+                    donation_info = [is_public, from_name, email, amount, message]
+                    donation_points = current_app.config['KOFI_SETTINGS']['donation_points']
+                    accept_donation(donation_info, donation_points)
                 if type == 'Subscription':
-                    if first_sub:
-                        if tier_name:
-                            current_app.logger.info(f'\n{name} <{email}> subscribed as a {tier_name} tier member.')
+                    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:
-                            current_app.logger.info(f'\n{name} <{email}> subscribed.')
+                            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']
+                        accept_sub(sub_info, sub_points)
                     else:
-                        if tier_name:
-                            current_app.logger.info(f'\n{name} <{email}> renewed their {tier_name} tier membership.')
-                        else:
-                            current_app.logger.info(f'\n{name} <{email}> renewed their membership.')
+                        current_app.logger.info(f'Kofi membership received, but subscriptions are not enabled. Doing nothing.')
                         
                 return jsonify({'status': 'success'}), 200
             else:

+ 1 - 1
pyproject.toml

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "ownchatbot"
-version = "1.0.3"
+version = "1.0.4"
 authors = [
     {name = "DeadTOm", email = "deadtom@deadtom.me"},
 ]

+ 1 - 1
setup.py

@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
 
 setup(
     name='ownchatbot',
-    version='1.0.3',
+    version='1.0.4',
     packages=find_packages(),
     include_package_data=True,
     install_requires=[