Skip to content

Development

WolfwithSword edited this page Jan 27, 2026 · 9 revisions

Local Setup

Open the solution project in your IDE of choice and install the dependencies and resolve the project using dotnet restore SubathonManager.sln

When you build and run locally through your IDE, you should be running SubathonManager.UI as the main project.

Design

The design is documented in this drawio diagram. Right-click the link and select "save lined content as" to download.

You can view renders of the diagram here.

It contains the overall flow, flow per service, db organization, enums, config, and websocket data.

If you make any new changes that require updates to the design, detail them in your pull requests such that we can update it in the source.

Issues and Feature Requests

Please submit a github issue if you have a bug or feature request.

For bugs, if requested, you may be asked to attached a logfile as well, which you can get from opening the data folder from the settings page, and going to the logs directory.

Please check if an issue or feature request exists similar to yours before submitting!

Pull Requests

For all pull requests, they must tie to an issue or enhancement before they will be reviewed.

For fixes or new features, please describe in detail use cases or situations your changes will affect and, if possible, provide screenshots or recordings of said change.

Maintainers will review all PR's prior to merging.

New Widget Presets

If you are submitting a new preset to include, please make sure your PR only includes net-new files in the presets folder.

Ideally, if you make your own preset widget(s), that you would like to share freely, you can have them in your own repo and link to this project.

We would like to have a diverse set of presets in the base project, but also want to avoid bloating it for users. Eventually, we may create a list of overlays linked within the wiki here. So submit an issue or discussion for these! We'd love to see them.

External Services

We support external services that are not native to be integrated into the Subathon Manager.

Specifically, external Commands, ExternalDonation's, and ExternalSub's.

These can be pushed into SubathonManager either via POST API or Websocket messages.

External Commands

API: POST /api/data/control

WS: /ws

{
    "user": "name", // string, optional. Defaults to "External"
    "command": "CommandName", // string, must match a SubathonCommandType Enum Name. Required.
    "message": "", // string, command parameters or empty - required.
    "type": "Command", // required
    "ws_type": "Command" // required only if websocket
}

The commands will be processed through the CommandService, so formatting for message parameters will be the exact same as if it were from a Twitch or Youtube Chat command. See Usage - Commands for details.

ExternalDonation

API: POST /api/data/control

WS: /ws

{
    "user": "name", // string, optional. Defaults to "External"
    "currency": "USD", // string. any valid 3 char currency code. Required.
    "amount": "12.34", // float as string. Required.
    "type": "ExternalDonation", // required
    "ws_type": "IntegrationSource", // required only if websocket
    "id": "5fa57900-a69d-4944-9ff1-64c25801fb62" // guid. optional - if you want to persist a unique guid from your service.
}

External donations are accepted and will follow the configuration value for seconds/points per dollar unit of default currency after conversion. Exception to configuration value will be certain overrides, such as KoFiDonation.

user of "SYSTEM" will treat it as a Simulated event source instead of External.

ExternalSub

API: POST /api/data/control

WS: /ws

{
    "user": "name", // string, optional. Defaults to "External"
    "amount": 1, // int, optional. Default 1. This is number of "subs". Like if it were a mass gift of subs.
    "value": "Tier 1", // string, optional. Will be used for logging and informational display reasons. If empty, will be "External".
    "seconds": 60, // int. Required. Can be 0. How many seconds should this event add?
    "points": 1, // int. Required, Can be 0. How many points should this event add?
    "type": "ExternalSub", // required
    "ws_type": "IntegrationSource", // required only if websocket
    "id": "5fa57900-a69d-4944-9ff1-64c25801fb62" // guid. optional - if you want to persist a unique guid from your service.
}

External subscriptions or memberships are accepted. They will require (unless exception, such as KoFiSub override) seconds to add and points to add to be supplied, as no in-app configuration exists. It will be affected by active multipliers.

user of "SYSTEM" will treat it as a Simulated event source instead of External.

Config Management

We support a way to remotely fetch and reconfigure the seconds or points of most EventTypes.

API: GET /api/data/values

[
  {
    "eventType": "TwitchSub",
    "source": "Twitch",
    "meta": "1000",
    "seconds": 60,
    "points": 1
  },
  {
    "eventType": "TwitchSub",
    "source": "Twitch",
    "meta": "2000",
    "seconds": 120,
    "points": 2
  }
  //...
]

To get continuous updates via websocket, connect with or send the following message, and you will receive config values anytime they update.

{
  "ws_type": "ValueConfig"
}

and response will be

{
  "type": "value_config",
  "ws_type": "ValueConfig",
  "data": [
    {
      "eventType": "TwitchSub",
      "source": "Twitch",
      "meta": "1000",
      "seconds": 60,
      "points": 1
    },
    {
      "eventType": "TwitchSub",
      "source": "Twitch",
      "meta": "2000",
      "seconds": 120,
      "points": 2
    }
    //...
  ]
}

Will return all subathon values that have a points or seconds configuration, along with their source and meta data.

The format it comes in will also be used to send PATCH/POST/PUT requests to update it.


API: PATCH /api/data/values / POST / PUT

payload

[
  {
    "eventType": "TwitchSub",
    "source": "Twitch",
    "meta": "1000",
    "seconds": 20,
    "points": 1
  }
]

If using websockets, push the following format

{
  "type": "value_config",
  "ws_type": "ValueConfig",
  "data": [
    {
      "eventType": "TwitchSub",
      "source": "Twitch",
      "meta": "1000",
      "seconds": 20,
      "points": 1
    }
  ]
}

You can send any number of values to update, and it will only modify those provided. Seconds or Points can be absent (but one must be present) - absent on will not be updated.

Meta must be included, but for most will be an empty string. It is primarily for Subs/Memberships that have a tier name/value.

EventType and Source are required.

Any changes done via the remote PATCH will be logged (depending on configuration) to the Error log Webhook URL (ignore the name).

Other

Amounts

GET /api/data/amounts

View a summary of all event amounts that have come in, such as number of subs of X type, total dollars per currency, etc.

All events properly processed by the subathon. It will be split with "real" and "simulated"/"system" events.

Partial example - it only shows value things that have come in.

{
  "simulated": {
    "DonationAdjustment": {
      "CAD": -138990
    },
    "TwitchCheer": 15300,
    "TwitchCharityDonation": {
      "CAD": 310
    },
    "TwitchSub": {
      "T1": 100
    },
    "TwitchGiftSub": {
      "T1": 1595,
      "T2": 240,
      "T3": 50
    },
    "TwitchRaid": {
      "count": 2,
      "total_viewers": 50
    },
    "YouTubeSuperChat": {
      "CAD": 20
    },
    "YouTubeMembership": {
      "DEFAULT": 1
    },
    "YouTubeGiftMembership": {
      "DEFAULT": 199
    }
  },
  "real": {
    "TwitchFollow": 1,
    "ExternalDonation": {
      "USD": 119057.12
    }
  }
}

Status

GET /api/data/status

Get the current status of the subathon.

Ex

{
  "millis_cumulated": 2231441768,
  "millis_elapsed": 11652000,
  "millis_remaining": 2219789768,
  "total_seconds": 2219789,
  "days": 25,
  "hours": 16,
  "minutes": 36,
  "seconds": 29,
  "points": 2754,
  "is_paused": true,
  "is_locked": false,
  "is_reversed": false,
  "multiplier": {
    "running": false,
    "apply_points": false,
    "apply_time": false,
    "is_from_hypetrain": false,
    "started_at": "2025-12-21T18:05:06.9150335",
    "duration_seconds": 0,
    "duration_remaining_seconds": 0
  }
}

Websocket Notes

The websocket server can be connected to via /ws endpoint. For example, ws://localhost:14040/ws.

If you connect via WebSocket and send the following message

{"ws_type": "IntegrationConsumer"}

You will receive all the same Websocket data that overlay widgets receive except value config changes. See Widget Development for details ojn this data.

For value config changes, you can subscribe by sending the following as well

{"ws_type": "ValueConfig"}

By default, when websockets connect, they receive the Generic ClientMessageType. This is replaced once another valid type is sent, and a websocket client connection can have multiple types subscribed to by sending new messages.

Currently, you cannot unsubscribe from a message type without disconnecting.

Clone this wiki locally