Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: Build SubathonManager (Windows)

on:
push:
branches: [ main ]
branches: [ main, dev ]
tags:
- 'v*.*.*'
pull_request:
branches: [ main ]
branches: [ main, dev ]
workflow_dispatch:
inputs:
upload_target:
Expand Down Expand Up @@ -114,7 +114,7 @@ jobs:
RUNTIME=${{ env.RUNTIME }}
if [ "${GITHUB_REF_NAME}" = "main" ]; then
echo "version=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT
elif [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
elif [ "${GITHUB_EVENT_NAME}" = "pull_request" ] || [ "${GITHUB_REF_NAME}" = "dev" ]; then
echo "version=${GITHUB_SHA}" >> $GITHUB_OUTPUT
else
SAFE_REF=${GITHUB_REF_NAME//\//-}
Expand All @@ -123,7 +123,7 @@ jobs:

if [ "${GITHUB_REF_NAME}" = "main" ]; then
echo "filename=SubathonManager_${RUNTIME}_nightly.zip" >> $GITHUB_OUTPUT
elif [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
elif [ "${GITHUB_EVENT_NAME}" = "pull_request" ] || [ "${GITHUB_REF_NAME}" = "dev" ]; then
echo "filename=SubathonManager_${RUNTIME}_${GITHUB_SHA}.zip" >> $GITHUB_OUTPUT
else
SAFE_REF=${GITHUB_REF_NAME//\//-}
Expand Down Expand Up @@ -166,7 +166,7 @@ jobs:

- name: Upload build artifact (GitHub)
if: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.upload_target == 'github' || github.event.inputs.upload_target == 'default')) ||
((github.ref_type == 'tag' && github.ref != 'refs/tags/nightly') || github.ref_name == 'main') }}
((github.ref_type == 'tag' && github.ref != 'refs/tags/nightly') || github.ref_name == 'main') && github.ref_name != 'dev' }}
uses: actions/upload-artifact@v6
id: artifact-upload
continue-on-error: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.upload_target == 'github') }}
Expand Down
108 changes: 76 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<p style="font-size: 14px;">
<strong>Subathon Manager</strong><br/>
An all-in-one Subathon/Donothon Manager for Twitch and YouTube. Manage your timer,
goals, overlays, settings, and more all locally.
goals, overlays, settings, and more - all locally.
</p>

<br clear="left" />
Expand All @@ -28,37 +28,81 @@


## Supported Integrations
- **Twitch**
- Cheers/Bits
- Including Combos & Powerups
- Follows, Raids
- Subs & Gift Subs
- Charity Donations
- Chat Commands
- Auto lock/pause on stream end
- Auto unlock/resume on stream start
- Hype Trains
- Events and optional multiplier trigger
- **Youtube**
- SuperChats
- Memberships & Gift Memberships
- Due to limitations, we treat all memberships as a single level/tier
- Chat Commands
- **Picarto**
- Follows
- Subs & Gift Subs
- Kudos Tips
- Chat Commands
- **KoFi**
- Tips & Memberships (via StreamerBot)
- **StreamElements**
- SE Pay Tips
- **StreamLabs**
- Donations
- **Blerp**
- Twitch & YouTube, (Bits & Beets)
- **External**
- Custom commands, donations, and subscriptions via POST API or WebSocket

<details> <summary><strong>Platforms</strong></summary>

<details> <summary><strong>Twitch</strong></summary>

- Cheers/Bits (Including Combos & Powerups!)
- Follows, Raids
- Subs, Gift Subs
- Charity Donations
- Chat Commands
- Automations
- Lock/Pause on Stream End
- Unlock/Resume on Stream Start
- Hype Trains with optional automated multiplier mode!
</details>

<details> <summary><strong>YouTube</strong></summary>

- SuperChats
- Memberships (Configurable Levels) & Gift Memberships
- Chat Commands
</details>

<details> <summary><strong>Picarto</strong></summary>

- Follows
- Subs & Gift Subs
- Kudos Tips
- Chat Commands
</details>

</details>


<details> <summary><strong>Integrations</strong></summary>

<details> <summary><strong>GoAffPro Affiliate Stores</strong></summary>

- UwuMarket
- GamerSupps
</details>
<details> <summary><strong>KoFi</strong></summary>

Requires StreamerBot

- Tips
- Memberships

</details>

<details> <summary><strong>StreamElements</strong></summary>

- SE Pay Tips

</details>

<details> <summary><strong>StreamLabs</strong></summary>

- Donations

</details>
<details> <summary><strong>Blerp</strong></summary>

- Bits & Beets
- On both YouTube and Twitch

</details>

<details> <summary><strong>External</strong></summary>

Custom commands, donations and subscriptions via POST API or WebSocket

</details>

</details>

## Features

Expand Down
2 changes: 1 addition & 1 deletion SubathonManager.Core/AppServices.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Updatum;
using System.Reflection;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace SubathonManager.Core {

Expand Down Expand Up @@ -86,7 +87,6 @@ private static Version GetVersion()
}
return null;
}

public static async Task<bool> InstallUpdate(UpdatumDownloadedAsset? asset, ILogger? logger)
{
if (asset == null)
Expand Down
39 changes: 37 additions & 2 deletions SubathonManager.Core/Config.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using IniParser;
using IniParser.Model;
using System.Diagnostics.CodeAnalysis;
using SubathonManager.Core.Interfaces;

namespace SubathonManager.Core
{
Expand All @@ -22,6 +23,8 @@ public class Config : IConfig
private static IniData Data { get; set; } = new();

public static string TwitchClientId { get; } = "jsykjc9k0yqkbqg4ttsfgnwwqmoxfh";

public bool PendingChanges { get; private set; }

public virtual IniParser.Model.KeyDataCollection GetSection(string section)
{
Expand Down Expand Up @@ -111,6 +114,7 @@ private void CreateDefault()
public virtual void Save()
{
Parser.WriteFile(ConfigPath, Data);
PendingChanges = false;
}

public virtual string GetDatabasePath()
Expand All @@ -123,9 +127,40 @@ public virtual string GetDatabasePath()
return Data[section][key] ?? defaultValue;
}

public virtual void Set(string section, string key, string? value)
public virtual bool GetBool(string section, string key, bool defaultValue = false)
{
bool val = Boolean.TryParse(Get(section, key, defaultValue.ToString()), out bool b) ? b : defaultValue;
return val;
}

public string? GetFromEncoded(string section, string key, string? defaultValue = "")
{
string val = Get(section, key, defaultValue) ?? string.Empty;
var fromBase64String = Convert.FromBase64String(val);
return System.Text.Encoding.UTF8.GetString(fromBase64String);
}

public virtual bool Set(string section, string key, string? value)
{
if (Data[section][key] != value)
{
Data[section][key] = value ?? string.Empty;
PendingChanges = true;
return true;
}
return false;
}

public virtual bool SetBool(string section, string key, bool? value)
{
value ??= false;
return Set(section, key, ((bool)value).ToString());
}

public bool SetEncoded(string section, string key, string value)
{
Data[section][key] = value ?? string.Empty;
var bytes = System.Text.Encoding.UTF8.GetBytes(value);
return Set(section, key, Convert.ToBase64String(bytes));
}
}
}
31 changes: 31 additions & 0 deletions SubathonManager.Core/Enums/GoAffProSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Diagnostics.CodeAnalysis;

namespace SubathonManager.Core.Enums;

public enum GoAffProSource
{
Unknown,
GamerSupps,
UwUMarket
}

public enum GoAffProModes
{
Item,
Order,
Dollar
}

[ExcludeFromCodeCoverage]
public static class GoAffProSourceeHelper
{
public static SubathonEventType GetOrderEvent(this GoAffProSource source)
{
return source switch
{
GoAffProSource.GamerSupps => SubathonEventType.GamerSuppsOrder,
GoAffProSource.UwUMarket => SubathonEventType.UwUMarketOrder,
_ => SubathonEventType.Unknown
};
}
}
3 changes: 2 additions & 1 deletion SubathonManager.Core/Enums/SubathonEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public enum SubathonEventSource
StreamLabs,
External,
Blerp,
Picarto
Picarto,
GoAffPro
}
3 changes: 2 additions & 1 deletion SubathonManager.Core/Enums/SubathonEventSubType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public enum SubathonEventSubType
FollowLike,
RaidLike,
TrainLike,
CommandLike
CommandLike,
OrderLike
}

[ExcludeFromCodeCoverage]
Expand Down
40 changes: 36 additions & 4 deletions SubathonManager.Core/Enums/SubathonEventType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,21 @@ public enum SubathonEventType
PicartoFollow,
PicartoSub,
PicartoGiftSub,
PicartoTip
PicartoTip,
GamerSuppsOrder,
UwUMarketOrder
// any new must be added after the last
}

[ExcludeFromCodeCoverage]
public static class SubathonEventTypeHelper
{
private static readonly SubathonEventType[] DisabledEvents =
[
//SubathonEventType.GamerSuppsOrder,
//SubathonEventType.UwUMarketOrder
];

private static readonly SubathonEventType[] CurrencyDonationEvents = new[]
{
SubathonEventType.YouTubeSuperChat,
Expand Down Expand Up @@ -99,6 +107,18 @@ public static class SubathonEventTypeHelper
SubathonEventType.BlerpBeets,
SubathonEventType.BlerpBits
};

private static readonly SubathonEventType[] FollowTypes = new[]
{
SubathonEventType.PicartoFollow,
SubathonEventType.TwitchFollow
};

private static readonly SubathonEventType[] OrderTypes = new[]
{
SubathonEventType.UwUMarketOrder,
SubathonEventType.GamerSuppsOrder
};

public static SubathonEventSubType GetSubType(this SubathonEventType? eventType)
{
Expand All @@ -107,18 +127,27 @@ public static SubathonEventSubType GetSubType(this SubathonEventType? eventType)
if (eventType.IsSubOrMembershipType()) return SubathonEventSubType.SubLike;
if (eventType.IsCheerType()) return SubathonEventSubType.TokenLike;
if (eventType.IsCurrencyDonation()) return SubathonEventSubType.DonationLike;

if (eventType.IsOrderType()) return SubathonEventSubType.OrderLike;
if (eventType.IsFollowType()) return SubathonEventSubType.FollowLike;

return eventType.Value switch
{
SubathonEventType.TwitchRaid => SubathonEventSubType.RaidLike,
SubathonEventType.TwitchFollow => SubathonEventSubType.FollowLike,
SubathonEventType.PicartoFollow => SubathonEventSubType.FollowLike,
SubathonEventType.TwitchHypeTrain => SubathonEventSubType.TrainLike,
SubathonEventType.Command => SubathonEventSubType.CommandLike,
_ => SubathonEventSubType.Unknown
};
}

public static bool IsFollowType(this SubathonEventType? eventType) =>
eventType.HasValue && FollowTypes.Contains(eventType.Value);

public static bool IsOrderType(this SubathonEventType? eventType) =>
eventType.HasValue && OrderTypes.Contains(eventType.Value);

public static bool IsEnabled(this SubathonEventType? eventType) =>
eventType.HasValue && !DisabledEvents.Contains(eventType.Value);

public static bool IsExtensionType(this SubathonEventType? eventType) =>
eventType.HasValue && ExtensionType.Contains(eventType.Value);

Expand Down Expand Up @@ -184,6 +213,9 @@ public static SubathonEventSource GetSource(this SubathonEventType? eventType) {

SubathonEventType.DonationAdjustment => SubathonEventSource.Command,

SubathonEventType.GamerSuppsOrder => SubathonEventSource.GoAffPro,
SubathonEventType.UwUMarketOrder => SubathonEventSource.GoAffPro,

_ => SubathonEventSource.Unknown
};
}
Expand Down
15 changes: 0 additions & 15 deletions SubathonManager.Core/IConfig.cs

This file was deleted.

Loading
Loading