Skip to content
Merged

Beta #547

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3890691
event flow relay someewhat fixed
steveseguin Jun 6, 2025
9e2eddf
fix(event-flow): Sync flow changes from editor to background
steveseguin Jun 6, 2025
1be728a
fix(zoom): Fix parsing of chat content including emotes
steveseguin Jun 6, 2025
d9abb4e
feat(games/rhythmpulse): Add Web Audio API integration and controls
steveseguin Jun 6, 2025
4a9f652
style(games/rhythmpulse): Improve instruction visibility and interaction
steveseguin Jun 6, 2025
e14919b
```
steveseguin Jun 6, 2025
6ba4335
Add files via upload
steveseguin Jun 7, 2025
2547a66
```
steveseguin Jun 7, 2025
5edd50d
Add files via upload
steveseguin Jun 7, 2025
5984a4a
Add files via upload
steveseguin Jun 7, 2025
88c51d3
feat(tiktok): Improve gift value parsing and event capture
steveseguin Jun 7, 2025
879bafb
refactor(tiktok): Refactor gift message parsing logic
steveseguin Jun 7, 2025
c07a92d
feat(chathistory): Add user ID search and export, improve database init
steveseguin Jun 8, 2025
7709882
feat(replay): Implement message replay
steveseguin Jun 8, 2025
20b695f
```
steveseguin Jun 8, 2025
f53ddd9
chore(config): Update default browser version strings
steveseguin Jun 9, 2025
e1feca9
feat(parti): Add configuration for Parti platform
steveseguin Jun 9, 2025
8f9d61c
feat(parti): Add sign-in configuration
steveseguin Jun 9, 2025
76562da
feat(parti): Update sign-in URL
steveseguin Jun 9, 2025
5437f87
chore(config): Update Twitch user agent version
steveseguin Jun 9, 2025
8bed7d8
fix(electron): Improve IPC and message handling
steveseguin Jun 9, 2025
a05b8c2
feat(poll): Add API for poll management
steveseguin Jun 10, 2025
d861021
chore(docs): update TOC
steveseguin Jun 10, 2025
8ae8999
feat(browser): Add legitimacy test page and update Twitch config
steveseguin Jun 11, 2025
20f276a
feat(twitch): Configure mock user agent data and window sizes
steveseguin Jun 12, 2025
87bf5f0
feat(chat): Implement smooth message buffering
steveseguin Jun 12, 2025
8cd3f83
feat(zoom): Add sign-in configuration to sites config
steveseguin Jun 13, 2025
20ebe0c
feat(display): Add option to show source name text
steveseguin Jun 14, 2025
e4e8085
optimzing youtube api usage
steveseguin Jun 14, 2025
f836246
chore(youtube): Optimize API usage and improve data handling
steveseguin Jun 14, 2025
6900986
feat(youtube): Dynamically disable slower polling on high activity
steveseguin Jun 14, 2025
4b6e278
feat(youtube): Add viewer count fallback on API errors
steveseguin Jun 14, 2025
b4af732
fix(twitch): Improve message body parsing for FFZ compatibility
steveseguin Jun 15, 2025
8a28347
feat(settings): Add user agent options for Facebook and Zoom
steveseguin Jun 16, 2025
6b10b53
feat(settings): Configure X and Zoom signin settings
steveseguin Jun 16, 2025
dff4a98
feat(settings): Add configuration for Facebook
steveseguin Jun 17, 2025
f6e0e5b
feat(tipjar): Connect tipjar to socket server for events
steveseguin Jun 18, 2025
1516835
style(tipjar): Improve file formatting
steveseguin Jun 18, 2025
574fa81
feat(vdoninja): Integrate VDO.Ninja SDKs for video
steveseguin Jun 18, 2025
efc46e7
feat(discord): Enable VDO.Ninja view links for streams
steveseguin Jun 18, 2025
0de4d3f
fix(twitch): Improve parsing for replied messages
steveseguin Jun 18, 2025
f7882f9
feat(sources): Add source channel name and image to message data
steveseguin Jun 18, 2025
7a556fd
feat: Implement channel name trigger and enhance Kick source
Jun 19, 2025
5d83b09
fix(popup): Fix handling of exclusive input parameters
Jun 19, 2025
f3163b6
fix: Fix name positioning for users without avatar
steveseguin Jun 19, 2025
0e84990
fix(popup): Fix copy link button and settings persistence
steveseguin Jun 19, 2025
84c9c54
feat(ui): Add option to remove colon after name
steveseguin Jun 19, 2025
f37b4fc
no emojis only tweak
steveseguin Jun 20, 2025
b8e22cd
feat: Add Facebook configuration and enhance leaderboard overlay
steveseguin Jun 25, 2025
9aa7280
feat: Add discord.gg compatibility and update facebook config
steveseguin Jun 25, 2025
77e1b12
feat(tiktok): Add user agent configuration
steveseguin Jun 25, 2025
d45b00a
fix(tiktok): Correct configuration structure
steveseguin Jun 25, 2025
d779494
fix(config): Fix trailing comma in config file
steveseguin Jun 25, 2025
89d545d
Merge branch 'main' into beta
steveseguin Jun 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ chatmessage | string | Chat message
chatimg | string | URL or DataBlob (under ~55KB) of the user's avatar image
type | lower-case string | the pre-qualified name of the source, eg: `twitch`, also used as the source png image
sourceImg | string | an alternative URL to the source image; relative or absolute
sourceName | string | the channel's name or the username of the host for the channel
textonly | boolean | Whether the chat message is only plain text; or does it contain HTML, etc.
hasDonation | string | The donation amount with its units. eg: "3 roses" or "$50 USD".
chatbadges | array | An array of URLs/Objects. If an object, it may define itself as an img/svg and other attributes
Expand Down
19 changes: 19 additions & 0 deletions actions/EventFlowEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class EventFlowEditor {
{ id: 'messageEquals', name: 'Message Equals' },
{ id: 'messageRegex', name: 'Message Regex' },
{ id: 'fromSource', name: 'From Source' },
{ id: 'fromChannelName', name: 'From Channel Name' },
{ id: 'fromUser', name: 'From User' },
{ id: 'userRole', name: 'User Role' },
{ id: 'hasDonation', name: 'Has Donation' },
Expand Down Expand Up @@ -502,6 +503,12 @@ class EventFlowEditor {
// alert('Flow saved successfully!');
await this.loadFlowList(); // Refresh list

// Notify background instance to reload flows
if (window.parent && window.parent.eventFlowSystem && window.parent.eventFlowSystem !== this.eventFlowSystem) {
console.log('[EventFlowEditor] Notifying parent window to reload flows after save');
window.parent.eventFlowSystem.reloadFlows();
}

// Re-select the current flow in the list
document.querySelectorAll('.flow-item').forEach(item => {
item.classList.toggle('selected-flow', item.dataset.id === this.currentFlow.id);
Expand Down Expand Up @@ -545,6 +552,12 @@ class EventFlowEditor {
}
this.loadFlowList();
// alert('Flow deleted successfully.');

// Notify background instance to reload flows
if (window.parent && window.parent.eventFlowSystem && window.parent.eventFlowSystem !== this.eventFlowSystem) {
console.log('[EventFlowEditor] Notifying parent window to reload flows');
window.parent.eventFlowSystem.reloadFlows();
}
} catch (error) {
console.error('Error deleting flow:', error);
alert('Failed to delete flow. Check console for details.');
Expand Down Expand Up @@ -675,6 +688,7 @@ class EventFlowEditor {
case 'messageEquals': return `Text: "${(node.config.text || '').substring(0,15)}${(node.config.text || '').length > 15 ? '...' : ''}"`;
case 'messageRegex': return `Pattern: "${(node.config.pattern || '').substring(0,15)}${(node.config.pattern || '').length > 15 ? '...' : ''}"`;
case 'fromSource': return `Source: ${node.config.source || 'Any'}`;
case 'fromChannelName': return `Channel: ${node.config.channelName || 'Any'}`;
case 'fromUser': return `User: ${node.config.username || 'Any'}`;
case 'userRole': return `Role: ${node.config.role || 'Any'}`;
case 'hasDonation': return 'Has donation';
Expand Down Expand Up @@ -946,6 +960,7 @@ class EventFlowEditor {
case 'messageEquals': node.config = { text: 'hello' }; break;
case 'messageRegex': node.config = { pattern: 'pattern', flags: 'i' }; break;
case 'fromSource': node.config = { source: '*' }; break;
case 'fromChannelName': node.config = { channelName: '' }; break;
case 'fromUser': node.config = { username: 'user' }; break;
case 'userRole': node.config = { role: 'mod' }; break;
case 'hasDonation': node.config = {}; break;
Expand Down Expand Up @@ -1171,6 +1186,10 @@ class EventFlowEditor {
${['twitch', 'youtube', 'facebook', 'kick', 'tiktok', 'instagram', 'discord', 'slack', 'other'].map(s => `<option value="${s}" ${node.config.source === s ? 'selected' : ''}>${s.charAt(0).toUpperCase() + s.slice(1)}</option>`).join('')}
</select></div>`;
break;
case 'fromChannelName':
html += `<div class="property-group"><label class="property-label">Channel Name</label><input type="text" class="property-input" id="prop-channelName" value="${node.config.channelName || ''}" placeholder="Enter channel name"></div>
<div class="property-help">Match messages from a specific channel name or host username</div>`;
break;
case 'fromUser':
html += `<div class="property-group"><label class="property-label">Username</label><input type="text" class="property-input" id="prop-username" value="${node.config.username || ''}"></div>`;
break;
Expand Down
94 changes: 74 additions & 20 deletions actions/EventFlowSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ class EventFlowSystem {
this.sendMessageToTabs = options.sendMessageToTabs || null;
this.sendToDestinations = options.sendToDestinations || null;
this.fetchWithTimeout = options.fetchWithTimeout || window.fetch; // Fallback to window.fetch if not provided

this.sanitizeRelay = options.sanitizeRelay || null;
this.checkExactDuplicateAlreadyRelayed = options.checkExactDuplicateAlreadyRelayed || null;

console.log('[EventFlowSystem Constructor] Initialized with:');
console.log(' - sendMessageToTabs:', this.sendMessageToTabs ? 'Function provided' : 'NULL - Relay will not work!');
console.log(' - sendToDestinations:', this.sendToDestinations ? 'Function provided' : 'NULL');
Expand Down Expand Up @@ -322,6 +324,13 @@ class EventFlowSystem {
return this.flows;
}

async reloadFlows() {
// Force reload flows from database
console.log('[EventFlowSystem] Reloading flows from database');
await this.loadFlows();
return this.flows;
}

async getFlowById(flowId) {
return this.flows.find(flow => flow.id === flowId) || null;
}
Expand Down Expand Up @@ -472,38 +481,71 @@ class EventFlowSystem {
return overallResult;
}

stripHtml(html) {
// Simple HTML stripping function that preserves emoji alt text
if (!html || typeof html !== 'string') return html;

// Create a temporary element to use browser's HTML parsing
const tmp = document.createElement('div');
tmp.innerHTML = html;

// Replace img tags with their alt text (especially for emojis)
tmp.querySelectorAll('img[alt]').forEach(img => {
const alt = img.getAttribute('alt');
img.replaceWith(document.createTextNode(alt));
});

// Get text content and clean up extra whitespace
const text = tmp.textContent || tmp.innerText || '';
return text.replace(/\s\s+/g, ' ').trim();
}

async evaluateTrigger(triggerNode, message) {
const { triggerType, config } = triggerNode;
// console.log(`[EvaluateTrigger] Node: ${triggerNode.id}, Type: ${triggerType}, Config: ${JSON.stringify(config)}, Message: ${message.chatmessage}`);
let match = false;

// Get the message text for comparison
let messageText = message.chatmessage;
if (message && messageText && typeof messageText === 'string') {
// If textonly flag is set, the message is already plain text
if (!message.textonly) {
// Check if we've already cleaned this message (cache the result)
if (!message.textContent) {
message.textContent = this.stripHtml(messageText);
}
messageText = message.textContent;
}
}

switch (triggerType) {
case 'messageContains':
// Ensure properties exist before trying to access them
match = message && message.chatmessage && typeof message.chatmessage === 'string' &&
match = message && messageText && typeof messageText === 'string' &&
config && typeof config.text === 'string' &&
message.chatmessage.includes(config.text);
//console.log(`[EvaluateTrigger - messageContains] Config Text: "${config.text}", Message Chatmessage: "${message.chatmessage}", Match: ${match}`);
messageText.includes(config.text);
//console.log(`[EvaluateTrigger - messageContains] Config Text: "${config.text}", Message Text: "${messageText}", Match: ${match}`);
return match;

case 'messageStartsWith':
match = message && message.chatmessage && typeof message.chatmessage === 'string' &&
match = message && messageText && typeof messageText === 'string' &&
config && typeof config.text === 'string' &&
message.chatmessage.startsWith(config.text);
//console.log(`[EvaluateTrigger - messageStartsWith] Config Text: "${config.text}", Message Chatmessage: "${message.chatmessage}", Match: ${match}`);
messageText.startsWith(config.text);
//console.log(`[EvaluateTrigger - messageStartsWith] Config Text: "${config.text}", Message Text: "${messageText}", Match: ${match}`);
return match;

case 'messageEquals':
match = message && typeof message.chatmessage === 'string' &&
match = message && typeof messageText === 'string' &&
config && typeof config.text === 'string' &&
message.chatmessage === config.text;
//console.log(`[EvaluateTrigger - messageEquals] Config Text: "${config.text}", Message Chatmessage: "${message.chatmessage}", Match: ${match}`);
messageText === config.text;
//console.log(`[EvaluateTrigger - messageEquals] Config Text: "${config.text}", Message Text: "${messageText}", Match: ${match}`);
return match;

case 'messageRegex':
try {
const regex = new RegExp(config.pattern, config.flags || '');
match = regex.test(message.chatmessage);
//console.log(`[EvaluateTrigger - messageRegex] Pattern: "${config.pattern}", Flags: "${config.flags}", Message: "${message.chatmessage}", Match: ${match}`);
match = regex.test(messageText);
//console.log(`[EvaluateTrigger - messageRegex] Pattern: "${config.pattern}", Flags: "${config.flags}", Message: "${messageText}", Match: ${match}`);
return match;
} catch (e) {
console.error('[EvaluateTrigger - messageRegex] Invalid regex:', e);
Expand All @@ -519,6 +561,16 @@ class EventFlowSystem {
console.log(`[RELAY DEBUG - fromSource Trigger] Config Source: "${config.source}", Message Type: "${message.type}", Match: ${match}`);
return match;

case 'fromChannelName':
if (!config.channelName || config.channelName.trim() === '') {
match = true; // Match any channel if no name specified
} else {
const channelName = (message.sourceName || '').toLowerCase();
match = channelName === config.channelName.toLowerCase();
}
console.log(`[RELAY DEBUG - fromChannelName Trigger] Config Channel: "${config.channelName}", Message Channel: "${message.sourceName}", Match: ${match}`);
return match;

case 'fromUser':
const identifier = (message.userid || message.chatname || '').toLowerCase();
match = config && typeof config.username === 'string' && identifier === config.username.toLowerCase();
Expand Down Expand Up @@ -641,7 +693,7 @@ class EventFlowSystem {
console.log('[RELAY DEBUG - Action] sendMessageToTabs available?', !!this.sendMessageToTabs);
console.log('[RELAY DEBUG - Action] sendMessageToTabs type:', typeof this.sendMessageToTabs);

if (this.sendMessageToTabs) {
if (this.sendMessageToTabs && message && !message.reflection) {
const relayMessage = {
response: config.template
.replace('{source}', message.type || '')
Expand All @@ -653,10 +705,6 @@ class EventFlowSystem {
relayMessage.tid = message.tid;
}

if (config.destination && config.destination.trim()) {
relayMessage.destination = config.destination.trim();
}

console.log('[RELAY DEBUG - Action] Relay message prepared:', relayMessage);
console.log('[RELAY DEBUG - Action] Config:', config);
console.log('[RELAY DEBUG - Action] Calling sendMessageToTabs with params:');
Expand All @@ -666,9 +714,15 @@ class EventFlowSystem {
console.log(' - relayMode:', true);
console.log(' - antispam:', false);
console.log(' - timeout:', config.timeout || 5100);

// Use relayMode=true to mark this as a relayed message and prevent circular relaying
const result = this.sendMessageToTabs(relayMessage, config.toAll === true, null, true, false, config.timeout || 5100);

let result = false;
relayMessage.response = this.sanitizeRelay(relayMessage.response, false).trim();
if (relayMessage.response) {
if (!this.checkExactDuplicateAlreadyRelayed(relayMessage.response, false, relayMessage.tid, false)){
result = this.sendMessageToTabs(relayMessage, true, message, true, false, 1000);
}
}

console.log('[RELAY DEBUG - Action] sendMessageToTabs returned:', result);
} else {
console.error('[RELAY DEBUG - Action] CRITICAL: sendMessageToTabs is not available!');
Expand Down
24 changes: 20 additions & 4 deletions actions/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
<div class="editor-container">
<div class="editor-header">
<div class="editor-title">Event Flow Editor</div>
<div style="font-size:66%;max-width:50%;">The Flow Editor lets you create custom and complex triggers and action workflows. Have fun with your stream!</div>
<div style="font-size:66%;max-width:50%;">The Flow Editor lets you create custom and complex triggers and action workflows. Have fun with your stream!<br>
<span style="margin-top: 5px; display: inline-block;">⚡ For media playback and OBS actions, add the <a href="../actions.html" target="_blank" style="color: #4a9eff;">Actions Overlay</a> to your stream</span></div>
<div class="editor-actions">
<button id="open-test-panel" class="btn">🧪 Test Flow</button>
</div>
Expand All @@ -22,9 +23,12 @@
</div>

<!-- Test Panel -->
<div class="test-overlay" id="test-overlay"></div>
<div class="test-panel" id="test-panel">
<h3>Test Flow</h3>
<div class="test-overlay" style="display:none;" id="test-overlay"></div>
<div class="test-panel" style="display:none;" id="test-panel">
<div class="test-panel-header">
<h3>Test Flow</h3>
</div>
<div class="test-panel-content">
<div class="test-group">
<label for="test-source">Source Platform:</label>
<select id="test-source">
Expand Down Expand Up @@ -76,9 +80,21 @@ <h3>Test Flow</h3>
<input type="text" id="test-donation-amount" value="$5.00">
</div>

<div class="test-options">
<label class="test-checkbox">
<input type="checkbox" id="test-all-active-flows">
Test against all active flows (not just current flow)
</label>
</div>

<div class="test-warning" id="unsaved-flow-warning" style="display: none; color: #e76f51; margin: 10px 0;">
<strong>Warning:</strong> The current flow has unsaved changes. Test results may not reflect the latest changes.
</div>

<div class="test-results" id="test-results">
<p>Test results will appear here after running a test.</p>
</div>
</div>

<div class="test-buttons">
<button id="close-test-btn" class="btn">Close</button>
Expand Down
Loading