فهرست منبع

Updated to prompt for access token information and URL

deadtom 2 هفته پیش
والد
کامیت
7688b15aad
4فایلهای تغییر یافته به همراه762 افزوده شده و 10 حذف شده
  1. 29 6
      install.sh
  2. 429 0
      ownchatbot/templates/mgmt.html.0.2.4
  3. 292 0
      ownchatbot/templates/userpanel.html.0.2.4
  4. 12 4
      update.sh

+ 29 - 6
install.sh

@@ -46,15 +46,42 @@ update_config() {  # Generate key for SECRET_KEY
     local key_value="$2"
     local bak_file="instance/config.py.bak"
 
+    echo "Go to Go to your Owncast Admin panel -> Integrations -> Access Tokens, and create an access token for OwnchatBot"
+
     # Update the config file
     if sed -i.bak "s|$key_name = ''|$key_name = '$key_value'|" "instance/config.py"; then
-        echo "Updated $key_name successfully."
+        echo "Set $key_name successfully."
+    else
+        echo "Failed to set $key_name."
+        exit 1  # Exit the script with a non-zero status
+    fi
+
+    read -p "Enter the name of the access token: " ACCESS_ID
+    if sed -i.bak "s|ACCESS_ID = ''|ACCESS_ID = '$ACCESS_ID'|" "instance/config.py"; then
+        echo "Set ACCESS_ID successfully."
+    else
+        echo "Failed to set ACCESS_ID."
+        exit 1  # Exit the script with a non-zero status
+    fi
+
+    read -p "Enter the access token: " ACCESS_TOKEN
+    if sed -i.bak "s|ACCESS_TOKEN = ''|ACCESS_TOKEN = '$ACCESS_TOKEN'|" "instance/config.py"; then
+        echo "Set ACCESS_TOKEN successfully."
+    else
+        echo "Failed to set ACCESS_TOKEN."
+        exit 1  # Exit the script with a non-zero status
+    fi
+
+    read -p "Enter the external URL of your Owncast instance, with \"https://\": " OWNCAST_URL
+    if sed -i.bak "s|OWNCAST_URL = ''|OWNCAST_URL = '$OWNCAST_URL'|" "instance/config.py"; then
+        echo "Set OWNCAST_URL successfully."
         rm "$bak_file"  # Remove the .bak file if the update was successful
     else
-        echo "Failed to update $key_name. Backup file remains."
+        echo "Failed to set ACCESS_ID."
         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
@@ -89,9 +116,5 @@ To configure your bot, go to:
 
 http://localhost:$OCB_PORT/mgmt
 
-Your Rewards Queue Management Panel is located at:
-
-http://localhost:$OCB_PORT/mgmt_queue
-
 Login to using your owncast admin name and password.
 "

+ 429 - 0
ownchatbot/templates/mgmt.html.0.2.4

@@ -0,0 +1,429 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <title>OCB Management Panel</title>
+    <link rel="icon" type="image/x-icon" href="/static/img/favicon.ico">
+    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
+</head>
+    <script src="/static/mgmtpanel.js"></script>
+    
+    <div class="navbar">
+        <div class="tab">
+            <button class="tablinks" onclick="window.open('{{ url_for('web_panels.mgmtqueue') }}', '_blank')">Rewards Queue Management</button>
+            <button class="tablinks" data-tab="managerewards" onclick="openTab(event, 'managerewards')">Rewards</button>
+            <button class="tablinks" data-tab="categories" onclick="openTab(event, 'categories')">Categories</button>
+            <button class="tablinks" data-tab="accounts" onclick="openTab(event, 'accounts')">Manage Accounts</button>
+            <button class="tablinks" data-tab="announcements" onclick="openTab(event, 'announcements')">Announcements</button>
+            <button class="tablinks" data-tab="settings" onclick="openTab(event, 'settings')">Settings</button>
+            {% if kofi_integration %}
+                <button class="tablinks" data-tab="kofi-settings" onclick="openTab(event, 'kofi-settings')">Kofi Settings</button>
+            {% endif %}
+        </div>
+        <img src="/static/img/ownchatbotwide.png">
+    </div>
+
+    <div id='managerewards' class="tabcontent">
+        <h3>Manage Rewards</h3>
+        <body>
+        <div>
+        <h4>Reward Types:</h4>
+        <ul>
+
+            <li><u>Redeems</u> are standard stream point redeems. They get added to the queue for the streamer to fulfill.</li>
+            <li><u>Specials</u> are redeems, but they run system commands and scripts. This enables you to integrate a lot of other fun things, such as letting your viewers control lighting, activate devices, trigger webhooks, etc. Specials do not go into the queue, because they happen automagically.</li>
+              <ul>
+                <li>Be careful with this one. It does require some more advanced scripting/command line knowledge. Make sure you test your specials before letting viewers use them.</li>
+              </ul>
+            <li><u>Votes</u> are just that, votes. Your viewers can vote on them.</li>
+            <li><u>Goals</u> are rewards that everyone in chat can contribute to. The streamer fulfills the reward when the goal is reached.</li>
+            <li>Add rewards to categories to enable and disable groups of rewards.</li>
+            <li>Rewards with no categories are inactive.</li>
+        </ul>
+
+        </div>
+            <table>
+                <tr>
+                    <td>
+                    </td>
+                    <td>
+                    </td>
+                    <td>
+                    </td>
+                    <td>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <a href="{{ url_for('web_panels.add', reward_type='redeem') }}"><button class="button button2" onclick="openTab(event, 'managerewards')">Create a Redeem</button></a><br>
+                    </td>
+                    <td>
+                        <a href="{{ url_for('web_panels.add', reward_type='special') }}"><button class="button button2" onclick="openTab(event, 'managerewards')">Create a Special</button></a><br>
+                    </td>
+                    <td>
+                        <a href="{{ url_for('web_panels.add', reward_type='vote') }}"><button class="button button2" onclick="openTab(event, 'managerewards')">Create a Vote</button></a><br>
+                    </td>
+                    <td>
+                        <a href="{{ url_for('web_panels.add', reward_type='goal') }}"><button class="button button2" onclick="openTab(event, 'managerewards')">Create a Goal</button></a><br>
+                    </td>
+                </tr>
+            </table>
+            <br>
+            {% if rewards %}
+            Rewards in <span style="color: red !important;">red</span> are inactive. To activate a reward, add it to an active category.
+            <table>
+                <thead>
+                    <tr>
+                        <th style="width:15%">Name</th>
+                        <th>Target</th>
+                        <th>Price</th>
+                        <th>Info</th>
+                        <th>Cool down</th>
+                        <th>Category</th>
+                        <th style="width:15%">Actions</th>
+                    </tr>
+                </thead>
+                <tbody>
+                {% for reward, reward_info in rewards.items() %}
+                    {% if reward in active_rewards %}
+                    <tr>
+                    {% else %}
+                    <tr style="color: red;">
+                    {% endif %}
+                        <td>{{ prefix }}{{ reward }}</td>
+                        {% if reward_info["type"] == "goal" %}
+                        <td>{{ reward_info["target"] }}</td>
+                        {% else %}
+                        <td>N/A</td>
+                        {% endif %}
+                        {% if reward_info["type"] == "goal" %}
+                        <td>N/A</td>
+                        {% else %}
+                        <td>{{ reward_info["price"] }}</td>
+                        {% endif %}
+                        <td>{{ reward_info["info"] }}</td>
+                        {% if reward_info["cooldown"] > 0 %}
+                        {% set minutes_label = 'minute' if reward_info["cooldown"] == 1 else 'minutes' %}
+                        <td>{{ reward_info["cooldown"] }} {{ minutes_label }}</td>
+                        {% else %}
+                        <td>None</td>
+                        {% endif %}
+                        <td>{{ reward_info["categories"] | join(', ') }}</td>
+                        <td>
+                            <a href="{{ url_for('web_panels.edit', reward_name=reward) }}"><button class="button button2" onclick="openTab(event, 'managerewards')"><span style="color: green;">Edit</span></button></a>&nbsp
+                            <a href="{{ url_for('web_panels.delete', reward_name=reward) }}"><button class="button button2" onclick="openTab(event, 'managerewards')"><span style="color: red;">Delete</span></button></a>&nbsp
+                            {% if reward_info["type"] == "goal" or reward_info["type"] == "vote"  %}
+                            <a href="{{ url_for('web_panels.reset', reward_name=reward, reward_type=reward_info["type"]) }}"><button class="button button2" onclick="openTab(event, 'managerewards')"><span style="color: orange;">Reset</span></button></a>
+                            {% endif %}
+                        </td>
+                    </tr>
+                {% endfor %}
+                </tbody>
+            </table>
+            {% endif %}
+        </body>
+    </div>
+    
+    <div id='accounts' class="tabcontent">
+
+        <body>
+            <h3>Manage Viewer Accounts</h3>
+            {% if users %}
+            <table>
+                <thead>
+                    <tr>
+                        <th>User</th>
+                        <th>Points balance</th>
+                        <th>Email</th>
+                        <th>Authed</th>
+                        <th></th>
+                    </tr>
+                </thead>
+                <tbody>
+                {% for user in users %}
+                    <tr>
+                        <td> {{ user[1] }} </td>
+                        {% set points_label = 'point' if user[2] == 1 else 'points' %}
+                        
+                        <td>{{ user[2] }} {{ points_label }}</td>
+                        {% if user[4] %}
+                            <td>{{ user[4] }}</td>
+                        {% else %}
+                            <td>none</td>
+                        {% endif %}
+                        {% if user[3] %}
+                            <td>Yes</td>
+                        {% else %}
+                            <td>No</td>
+                        {% endif %}
+                        <td> <a href="/mgmt/edit_account/{{ user[0] }}?name={{ user[1] }}&points={{ user[2] }}&email={{ user[4] }}"><button class="button button2" onclick="openTab(event, 'panel')">Edit</button></a> </td>
+                    </tr>
+                {% endfor %}
+                </tbody>
+            </table>
+            {% endif %}
+        </body>
+    </div>
+
+    <div id='categories' class="tabcontent">
+        <body>
+        <h3>Manage Categories</h3>
+        <div>
+            &nbsp;&nbsp;<a href="{{ url_for('web_panels.add', reward_type='category') }}"><button class="button button2" onclick="openTab(event, 'categories')">Create a new category</button></a><br>
+            <table>
+                <thead>
+                    <tr>
+                        <th style="width: 50%;"><h3>Active Categories</h3></th>
+                        <th style="width: 50%;"><h3>Inactive Categories</h3></th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr>
+                        <td>
+                            {% for cat in active_categories %}
+                            &nbsp;&nbsp;{{ cat }} - <a href="/mgmt/deactivate/{{ cat }}"><span style="color: green;">Deactivate</span></a>&nbsp;<a href="/mgmt/delcat/{{ cat }}/active"><span style="color: red;">Delete</span></a><br>
+                            {% endfor %}
+                        </td>
+                        <td>
+                            {% for cat in inactive_categories %}
+                            &nbsp;&nbsp;{{ cat }} - <a href="/mgmt/activate/{{ cat }}"><span style="color: green;">Activate</span></a>&nbsp;<a href="/mgmt/delcat/{{ cat }}/inactive"><span style="color: red;">Delete</span></a><br>
+                            {% endfor %}
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+        </body>
+    </div>
+
+    <div id='announcements' class="tabcontent">
+        <body>
+            <form method="POST" action="/mgmt/announcements">
+                <table>
+                <h3>Manage Announcements</h3>
+                <table>
+                    <thead>
+                        <tr style="border-bottom: none;">
+                            <th style="width: 20%;"></th>
+                            <th style="width: 50%;"></th>
+                            <th></th>
+                        </tr>
+                    </thead>
+                    <tr>
+                        <td> <label for="announce_enable">Enable:</label> </td>
+                        {% if settings_info[9] %}
+                        <td> <input type="checkbox" name="announce_enable" value="{{ settings_info[9] }}" checked> </td>
+                        {% else %}
+                        <td> <input type="checkbox" name="announce_enable" value="{{ settings_info[9] }}"> </td>
+                        {% endif %}
+                        <td>Enable periodic announcements</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="announce_interval">Interval:</label> </td>
+                        <td> <input type="number" name="announce_interval" value="{{ settings_info[10] }}" size="2" required> minutes</td>
+                        <td>How long between each announcement?</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="announcements">Announcements:</label> </td>
+                        <td>
+                            <textarea name="announcements" rows="5" cols="50">{{ announcements | join('\n') }}</textarea>
+                        </td>
+                        <td>Enter your announcements, one per line. May contain html <a href=https://www.w3schools.com/tags/tag_img.asp>IMG tags</a> to display images.</td>
+                    </tr>
+                </table>
+                <br><button class="button button2" type="submit">Save Changes</button><br>
+            </form>
+            <br><br>
+        </body>
+    </div>
+
+    <div id='settings' class="tabcontent">
+        <body style="text-align: left;">
+            <form method="POST" action="/mgmt/settings">
+                <table>
+                <h3>OwnchatBot Settings</h3>
+                    <thead>
+                        <tr style="border-bottom: none;">
+                            <th style="width: 20%;"></th>
+                            <th></th>
+                            <th style="width: 50%;"></th>
+                        </tr>
+                    </thead>
+                    <tr>
+                        <td> <label for="points_interval">Points Interval:</label> </td>
+                        <td> <input type="number" name="points_interval" value="{{ settings_info[1] }}" size="5" required> minutes</td>
+                        <td>How often do you want to award your viewers points?</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="points_award">Points Award:</label> </td>
+                        <td> <input type="number" name="points_award" value="{{ settings_info[2] }}" size="5"  required> points</td>
+                        <td>How many points do you want to award them?</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="follow_points">Follow Points:</label> </td>
+                        <td> <input type="number" name="follow_points" value="{{ settings_info[11] }}" size="5"  required> points</td>
+                        <td>How many points do you want to award viewers for following your stream?</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="gunicorn_logging">Gunicorn Logging:</label> </td>
+                        {% if settings_info[3] %}
+                        <td> <input type="checkbox" name="gunicorn_logging" value="{{ settings_info[3] }}" checked> </td>
+                        {% else %}
+                        <td> <input type="checkbox" name="gunicorn_logging" value="{{ settings_info[3] }}"> </td>
+                        {% endif %}
+                        <td>Enable Gunicorn logging integration.</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="prefix">Chat Command Prefix:</label> </td>
+                        <td> <input type="text" name="prefix" maxlength="1" size="1" value="{{ settings_info[4] }}" required> </td>
+                        <td>Character that preceeds chat commands, so OwnchatBot knows what to look for.<br>Example: "{{ settings_info[4] }}points"</td>
+                    </tr>
+                </table>
+                
+                <h3>Owncast Integration</h3>
+                <table>
+                    <thead>
+                        <tr style="border-bottom: none;">
+                            <th style="width: 20%;"></th>
+                            <th></th>
+                            <th style="width: 50%;"></th>
+                        </tr>
+                    </thead>
+                    <tr>
+                        <td> <label for="access_id">Access Token Name:</label> </td>
+                        <td style="padding: 5px;"> <input type="text" name="access_id" value="{{ settings_info[0] }}" size="40"> </td>
+                        <td>Create in Owncast Admin panel. Integrations -> Access Tokens (check all three boxes)</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="access_token">Access Token:</label> </td>
+                        <td style="padding: 5px;"> <input type="text" name="access_token" value="{{ settings_info[5] }}" size="40"> </td>
+                        <td>The token you created above.</td>
+                    </tr>
+                    <tr>
+                        <td> <label for="owncast_url">Your Owncast URL:</label> </td>
+                        <td> <input type="text" name="owncast_url" value="{{ settings_info[6] }}" size="40"> </td>
+                        <td>The external URL of your Owncast instance, with "http://" or "https://".</td>
+                    </tr>
+                </table>
+                
+                <h3>Kofi Integration</h3>
+                <table>
+                    <thead>
+                        <tr style="border-bottom: none;">
+                            <th style="width: 20%;"></th>
+                            <th></th>
+                            <th style="width: 50%;"></th>
+                        </tr>
+                    </thead>
+                    <tr>
+                        <td> <label for="kofi_integration">Enable:</label> </td>
+                        {% if settings_info[8] %}
+                        <td> <input type="checkbox" name="kofi_integration" value="{{ settings_info[8] }}" checked> </td>
+                        <td>Enable Ko-fi integration.</td>
+                        {% else %}
+                        <td> <input type="checkbox" name="kofi_integration" value="{{ settings_info[8] }}"> </td>
+                        <td>Enable Ko-fi integration. ("Kofi Settings" button will appear in the navigation bar when enabled.)</td>
+                        {% endif %}
+                    </tr>
+                    <tr>
+                        <td> <label for="kofi_token">Verification Token:</label> </td>
+                        <td style="padding: 5px;"> <input type="text" name="kofi_token" value="{{ settings_info[7] }}" size="40"> </td>
+                        <td>Get from Kofi -> More -> API -> Webhooks -> Advanced -> Verification Token.</td>
+                    </tr>
+                </table>
+                
+                <br><button class="button button2" type="submit">Save Changes</button><br>
+            </form>
+            <br><br>
+        </body>
+    </div>
+
+    <div id='kofi-settings' class="tabcontent">
+        <body style="text-align: left;">
+            <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>
+                            <th></th>
+                            <th style="width: 50%;"></th>
+                        </tr>
+                    </thead>
+                    <tr>
+                        <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_donations" value="{{ kofi_settings['donations'] }}"> </td>
+                        {% endif %}
+                        <td>Enable awarding points for donations</td>
+                    </tr>
+                    <tr>
+                        <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>
+                        <td style="padding: 5px;"> <input type="text" name="kofi_url" value="{{ kofi_settings['kofi_url'] }}" size="30"> </td>
+                        <td>What is your Kofi page URL?</td>
+                    </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?
+                                </tr>
+                            {% for kofi_logo in kofi_logos %}
+                                {% if loop.index0 % 8 == 0 %}
+                                    <tr style="border-bottom: none;">
+                                {% endif %}
+                                <td>
+                                    <label for="kofi_logo"><img src="/static/img/kofi/{{ kofi_logo }}"></label>
+                                    {% if kofi_logo == kofi_settings['kofi_logo'] %}
+                                        <input type="radio" name="kofi_logo" value="{{ kofi_logo }}" checked>
+                                    {% else %}
+                                        <input type="radio" name="kofi_logo" value="{{ kofi_logo }}">
+                                    {% endif %}
+                                </td>
+                                {% if loop.index0 % 8 == 7 or loop.last %}
+                                </tr> <!-- Close the row after 8 items or at the end -->
+                                {% endif %}
+                            {% endfor %}
+                            </tbody>
+                        </table>
+                    </tr>
+                </table>
+                <br><button class="button button2" type="submit">Save Changes</button><br>
+            </form>
+            <br><br>
+	    <i>Kofi subscription tier support coming soon.</i>
+        </body>
+    </div>
+    
+    
+</html>

+ 292 - 0
ownchatbot/templates/userpanel.html.0.2.4

@@ -0,0 +1,292 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <title>OCB - Stream Points and Rewards</title>
+    <link rel="icon" type="image/x-icon" href="/static/img/favicon.ico">
+    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
+</head>
+    <script src="/static/userpanel.js"></script>
+    
+    <div class="navbar">
+        <div class="tab">
+            <button class="tablinks" data-tab="ocbinfo" onclick="openTab(event, 'ocbinfo')">OwnchatBot Info</button>
+            <button class="tablinks" data-tab="rewards" onclick="openTab(event, 'rewards')">Points and Rewards</button>
+            <button class="tablinks" data-tab="queue" onclick="openTab(event, 'queue')">Redeems Queue</button>
+        </div>
+        <img src="/static/img/ownchatbotwide.png">
+    </div>
+
+    <div id='ocbinfo' class="tabcontent">
+        <h3>OwnchatBot Info</h3>
+        <h4>Reward types</h4>
+        <ul>
+            <li><u>Redeems</u> are standard stream point redeems. They get added to the queue for the streamer to fulfill.</li>
+            <li><u>Votes</u> are just that, votes. You vote on them.</li>
+            <li><u>Goals</u> are rewards that everyone in chat can contribute to. The streamer fulfills the reward when the goal is reached.</li>
+            <li><u>Milestones</u> are smaller goals inside a goal, to celebrate and/or reward progress toward the larger goal.</li>
+        </ul>
+        
+        <hr>
+        <h4>Chat commands</h4>
+        <ul>
+            <li>{{ prefix }}help - Shows the help message.</li>
+            <li>{{ prefix }}points - Shows your current points.</li>
+            <li>{{ prefix }}rewards - Shows you the list of active rewards.</li>
+        </ul>
+        
+        <hr>
+        {% if kofi_integration %}
+            <h4>Kofi Integration <a href="{{ kofi_settings['kofi_url'] }}/donate" target="new"><img src="/static/img/kofi/{{ kofi_settings['kofi_logo'] }}"></a></h4>
+            
+            {% 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' %}
+                Kofi subscribers get {{ kofi_settings['sub_points'] }} {{ s_points_label }} every month.<br>
+            {% endif %}
+            <i>You must be authenticated with Owncast to take advantage of Kofi integration.</i><br>
+            
+            {% for user in users %}
+            
+            <div>
+                {% if user['user_authed'] %}
+                    <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 %}
+                            {% for category, message in messages %}
+                                {% if category == 'failure' %}
+                                    <span style="color: red;">{{ message }}</span>
+                                {% endif %}
+                                {% if category == 'success' %}
+                                    <span style="color: green;">{{ message }}</span>
+                                {% endif %}
+                            {% endfor %}
+                        {% endif %}
+                    {% endwith %}
+                    
+                    <form method="POST" action="/set_viewer_email">
+                        <label for="code">Type !reg_mail into the chat, and enter the code it gives you here:</label>
+                        <input type="number" name="code" size="6" required><br>                        
+                        <label for="new_email">Enter the email address associated with your Kofi account:</label>
+                        <input type="text" name="new_email" value="{{ user[4] }}" size="40" required>
+                        <input type="hidden" name="instance" value="{{ instance }}">
+                        <input type="hidden" name="user_name" value="{{ username }}">
+                        <br>Email addresses are <b>ONLY</b> used for Kofi integration. They are not sent to any other individual or company, and will not be used to create or send mailing lists of any kind.<br>
+                        <input type="hidden" name="user_id" value="{{ user[0] }}"> <button class="button button2" type="submit">Save Email</button>
+                    </form><br>
+                {% endif %}
+                </div>
+            {% endfor %}
+        {% endif %}
+
+        <hr>
+        <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 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>
+        <br><br>
+	
+    </div>
+
+    
+    <div id='rewards' class="tabcontent">
+        <body>
+            <h3>Points and Rewards</h3>
+            {% for user in users %}
+            
+                {% set points_label = 'point' if points_award == 1 else 'points' %}
+                {% set minutes_label = 'minute' if points_interval == 1 else 'minutes' %}
+
+                &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 }}.<br>
+                {% set points_label = 'point' if follow_points == 1 else 'points' %}
+                &nbsp;&nbsp;You can get {{ follow_points }} {{ points_label }} for following my stream.
+                {% if kofi_integration %}
+                    {% 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 %}
+            <h3>Active Votes</h3>
+            {% if votes %}
+                <table>
+                    <thead>
+                        <tr>
+                            <th style="width: 20%;">Name</th>
+                            <th style="width: 40%;">Description</th>
+                            <th style="width: 20%;">Price</th>
+                            <th style="width: 20%;">Current Tally</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                    {% for vote in votes %}
+                        <tr>
+                            <td> {{ prefix }}{{ vote[0] }} </td>
+                            <td> {{ vote[2] }} </td>
+                            
+                                {% set points_label = 'point' if rewards[vote[0]]["price"] == 1 else 'points' %}
+                                <td> {{ rewards[vote[0]]["price"] }} {{ points_label }}</td>
+                            
+                            {% set votes_label = 'vote' if vote[1] == 1 else 'votes' %}
+                            
+                            <td> {{ vote[1] }} {{ votes_label }}</td>
+                        </tr>
+                    {% endfor %}
+                    </tbody>
+                </table>
+            {% else %}
+                &nbsp;&nbsp;There are currently no active votes
+                <br>
+            {% endif %}
+            <h3>Active Goals</h3>
+            {% if goals %}
+            <table>
+                <thead>
+                    <tr>
+                        <th style="width: 20%;">Name</th>
+                        <th style="width: 40%;">Description</th>
+                        <th style="width: 25%;">Progress</th>
+                        <th></th>
+                    </tr>
+                </thead>
+                <tbody>
+                {% for goal in goals %}
+                    <tr>
+                        <td> {{ prefix }}{{ goal[0] }} </td>
+                        <td> {{ goal[3] }} </td>
+                        {% set progress = goal[1] / goal[2] * 100 %}
+                        <td>
+                            <div class="bar-light-grey bar-tiny bar-round" style="position: relative;">
+                                <div class="bar-round bar-blue" style="text-align: center; width:{{ progress }}%;">{{ '%0.0f'| format(progress| float) }}%
+                                </div>
+                                {% set milestones = rewards[goal[0]]["milestones"] %}
+                                {% for milestone_key, milestone in milestones.items() %}
+                                    {% if milestones[milestone_key][0] and milestones[milestone_key][1] %}
+                                        {% if milestones[milestone_key][1] < goal[2] %}
+                                            {% set milestone_progress = milestones[milestone_key][1] / goal[2] * 100 %}
+                                                <div class="milestone-marker" style="position: absolute; left: {{ milestone_progress }}%; transform: translateX(-50%);">
+                                                    <img src="/static/img/milestone.png" style="width: 16px; height: 16px;" title="{{ milestones[milestone_key][1] }} points. {{ milestones[milestone_key][0] }}">
+                                                </div>
+                                            {% endif %}
+                                        {% endif %}
+                                    {% endfor %}
+                            </div>
+                        </td>
+                        {% if goal[1] == goal[2] %}
+                        <td>   {{ goal[1] }} / {{ goal[2] }} <img src=/static/img/tada.png style="width: 24px; height: 24px;"></td>
+                        {% else %}
+                        <td>   {{ goal[1] }} / {{ goal[2] }} </td>
+                        {% endif %}
+                    </tr>
+                {% endfor %}
+                    <tr style="border-bottom: none;">
+                        <td></td>
+                        <td></td>
+                        <td style="font-size: small;">
+                            (Mouse over flags for milestone details)
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+            {% else %}
+            &nbsp;&nbsp;There are currently no active goals
+            <br>
+            {% endif %}
+        </body>
+        <body>
+            <h3>Active Redeems</h3>
+            {% if rewards %}
+            <table>
+                <thead>
+                    <tr>
+                        <th style="width: 20%;">Name</th>
+                        <th style="width: 40%;">Description</th>
+                        <th>Cool down</th>
+                        <th style="width: 25%;">Price</th>
+                        <th></th>
+                    </tr>
+                </thead>
+                <tbody>
+                {% for reward, reward_info in rewards.items() %}
+                    {% if reward_info["type"] == "redeem" or reward_info["type"] == "special" %}
+                        <tr>
+                            <td>{{ prefix }}{{ reward }}</td>
+                            <td>{{ reward_info["info"] }}</td>
+                            {% if reward_info["cooldown"] > 0 %}
+                            
+                            {% set minutes_label = 'minute' if reward_info["cooldown"] == 1 else 'minutes' %}
+                            
+                            <td>{{ reward_info["cooldown"] }} {{ minutes_label }}</td>
+                            {% else %}
+                            <td>None</td>
+                            {% endif %}
+                            
+                            {% set points_label = 'point' if reward_info["price"] == 1 else 'points' %}
+                            
+                            <td>{{ reward_info["price"] }} {{ points_label }}</td>
+                            <td></td>
+                        </tr>
+                    {% endif %}
+                {% endfor %}
+                </tbody>
+            </table>
+            {% else %}
+            &nbsp;&nbsp;There are currently no active redeems
+            {% endif %}
+            <br><br>
+        </body>
+    </div>
+
+    <div id='queue' class="tabcontent">
+
+        <body>
+            <h3>Queue</h3>
+            {% if queue %}
+            <table>
+                <thead>
+                    <tr>
+                        <th>Time</th>
+                        <th>Name</th>
+                        <th>Description</th>
+                        <th>User</th>
+                        <th></th>
+                    </tr>
+                </thead>
+                <tbody>
+                {% for row in queue %}
+                    {% if not row[4] %}
+                    <tr>
+                        <td>{{ row[1].replace(tzinfo=utc_timezone).astimezone().strftime("%H:%M") }}</td>
+                        <td>{{ prefix }}{{ row[2] }}</td>
+                        <td>{{ all_rewards[row[2]]["info"] }}</td>
+                        {% if row[6] %}
+                        <td>{{ row[6] }}</td>
+                        {% else %}
+                        <td></td>
+                        {% endif %}
+                    </tr>
+                    {% endif %}
+                {% endfor %}
+                </tbody>
+            </table>
+            {% else %}
+            &nbsp;&nbsp;The queue is currently empty
+            {% endif %}
+        </body>
+        <br><br>
+    </div>
+    
+    <script>
+        setTimeout(refreshPage, 30 * 1000);
+    </script>
+
+</html>

+ 12 - 4
update.sh

@@ -5,8 +5,11 @@ set -e  # Exit immediately if any command exits with a non-zero status
 update_config() {
     CONFIG_FILE="instance/config.py"
 
+    # Prompt the user for the Owncast instance URL
+    read -p "Enter the name of the existing Owncast access token used by OwnchatBot: " TOKEN_NAME
+
     NEW_VARIABLES="\
-ACCESS_ID = ''  # The name of your access token
+ACCESS_ID = '$TOKEN_NAME'  # The name of your access token
 FOLLOW_POINTS = '50'  # How many points to award viewers for following
 "
 
@@ -23,14 +26,18 @@ FOLLOW_POINTS = '50'  # How many points to award viewers for following
 
     echo "$NEW_VARIABLES" >> "$TEMP_FILE" || { echo "Error writing new variables to temp file"; exit 1; }  # Append new variables to the temp file
 
-    mv "$TEMP_FILE" "$CONFIG_FILE" || { echo "Error replacing the original config file"; exit 1; }   # Move the temp file to the config file
+    mv "$TEMP_FILE" "$CONFIG_FILE" || { echo "Error replacing the original config file"; exit 1; }  # Move the temp file to the config file
 
     echo "Config file updated successfully."
 }
 
 activate_venv() {
     source env/bin/activate || { echo "Failed to activate virtual environment"; exit 1; }
-    pip install pkce || { echo "Failed to install pkce module"; exit 1; }
+}
+
+install_module() {
+    pip install --upgrade pip
+    pip install pkce || { echo "Failed to install pkce module"; exit 1; }  # Install pkce module
 }
 
 update_db() {  # Create the database. This also populates it with some goals and votes from the default rewards.py
@@ -45,9 +52,10 @@ update_db() {  # Create the database. This also populates it with some goals and
 
 update_config
 activate_venv
+install_module
 update_db
 deactivate
 
 echo -e "***IMPORTANT***\n"
-echo -e "Look for new \"Access Token Name\" option in the Settings menu of your OwnchatBot managemment panel.\n"
+echo -e "Look for new \"Access Token Name\" option in the Settings menu of your OwnchatBot management panel.\n"
 echo "The way you access your OwnchatBot management panel has changed. Authentication is now handled using your Owncast server as an IndieAuth server. You log in using the same credentials you use to log in to your Owncast Admin page."