Skip to content
Open
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/A3562BQV)
<a href='https://www.patreon.com/bePatron?u=6503887' target='_blank'><img height='46' style='border:0px;height:46px;' src='https://c5.patreon.com/external/logo/become_a_patron_button@2x.png' border='0' alt='Become a Patron!' /></a>

## Sponsor leaderboard ##
`/sponsors` shows the leaderboard. Once a day the bot refreshes it from the funding platforms, posts it to a channel and applies sponsor roles.

| Variable | Purpose |
|---|---|
| `GITHUB_SPONSORS_TOKEN` | PAT with `read:user`. Without it GitHub Sponsors is skipped |
| `GITHUB_SPONSORS_LOGIN` | maintainer login, defaults to `0Lucifer0` |
| `PATREON_ACCESS_TOKEN`, `PATREON_CAMPAIGN_ID` | creator token and campaign. Without them Patreon is skipped |
| `PATREON_NAMES_PUBLIC` | `true` to show Patreon names. They are anonymised otherwise, because the Patreon API exposes no privacy flag |
| `SPONSOR_GUILD_ID`, `SPONSOR_CHANNEL_ID` | where the report is posted and roles are applied |
| `SPONSOR_REPORT_HOUR` | UTC hour of the daily post, defaults to 9 |

The `Supporter`, `Gold Supporter`, `Legendary Supporter` and `Top Sponsor` roles are created on the first sync — the bot's own role has to sit above them for it to assign them. A sponsor only gets a role once someone links their accounts with `/link-sponsor <user> github <login>`, since no funding platform exposes a sponsor's Discord identity.

Sponsors who chose to stay private are listed as `Anonymous` with their amount. GitHub lifetime totals are estimated from tier price and start date, which is all its API exposes; Patreon reports exact lifetime totals. Ko-fi is not included — it has no read API, only webhooks.

## Warning! ##
We are not responsible of any damages caused by bad usage of our source. Please before asking questions or installing this source read this readme and also do a research, google is your friend. If you mess up when installing our source because you didnt follow it, we will laugh at you. A lot.

Expand Down
78 changes: 78 additions & 0 deletions src/NosCoreBot/Modules/SponsorModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NosCoreBot.Services;

namespace NosCoreBot.Modules;

public class SponsorModule : ModuleBase<SocketCommandContext>
{
private readonly SponsorService _sponsors;
private readonly SponsorSyncService _sync;

public SponsorModule(SponsorService sponsors, SponsorSyncService sync)
{
_sponsors = sponsors;
_sync = sync;
}

[Command("sponsors")]
[Name("sponsors")]
[Summary("show the sponsor leaderboard")]
public async Task ShowSponsors()
{
var state = await _sponsors.LoadStateAsync();
await ReplyAsync("", false, SponsorSyncService.BuildEmbed(state.Snapshot));
}

[Command("link-sponsor")]
[Name("link-sponsor <user> <platform> <id>")]
[Summary("link a discord member to a sponsor account so they get their role")]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task LinkSponsor(SocketGuildUser user, string platform, string id)
{
var key = $"{platform.ToLowerInvariant()}:{id}";
await _sponsors.MutateAsync(state => state.Links[key] = user.Id);
await ReplyAsync($"Linked {user.Mention} to `{key}`. Roles apply on the next sync.");
}

[Command("unlink-sponsor")]
[Name("unlink-sponsor <platform> <id>")]
[Summary("remove a sponsor account link")]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task UnlinkSponsor(string platform, string id)
{
var key = $"{platform.ToLowerInvariant()}:{id}";
var removed = false;
await _sponsors.MutateAsync(state => removed = state.Links.Remove(key));
await ReplyAsync(removed ? $"Unlinked `{key}`." : $"No link for `{key}`.");
}

[Command("sponsor-sync")]
[Name("sponsor-sync")]
[Summary("refresh sponsors, post the leaderboard and apply roles now")]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task SyncNow()
{
var state = await _sync.RunAsync(_ => true);
await ReplyAsync($"Synced {state.Snapshot.Count} sponsors.");
}

[Command("sponsor-links")]
[Name("sponsor-links")]
[Summary("list the sponsor account links")]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task ShowLinks()
{
var state = await _sponsors.LoadStateAsync();
if (state.Links.Count == 0)
{
await ReplyAsync("No sponsor links yet.");
return;
}

await ReplyAsync(string.Join('\n', state.Links.Select(link => $"`{link.Key}` -> <@{link.Value}>")));
}
}
2 changes: 2 additions & 0 deletions src/NosCoreBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public static IHostBuilder CreateHostBuilder(string[] args)
.AddSingleton<CommandService>()
.AddSingleton<CommandHandlingService>()
.AddSingleton<TimeHandlingService>()
.AddSingleton<SponsorService>()
.AddSingleton<SponsorSyncService>()
.AddSingleton<HttpClient>()
.BuildServiceProvider();
services.AddHostedService<Worker>();
Expand Down
Loading
Loading