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
6 changes: 3 additions & 3 deletions src/Nullinside.Api.TwitchBot/Bots/ABotRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public abstract class ABotRule : IBotRule {
/// <param name="db">The database.</param>
/// <param name="stoppingToken">The cancellation token.</param>
/// <returns>An asynchronous task.</returns>
public abstract Task Handle(User user, TwitchUserConfig config, TwitchApiProxy botProxy,
public abstract Task Handle(User user, TwitchUserConfig config, ITwitchApiProxy botProxy,
INullinsideContext db, CancellationToken stoppingToken = new());

/// <summary>
Expand All @@ -44,7 +44,7 @@ public abstract Task Handle(User user, TwitchUserConfig config, TwitchApiProxy b
/// <param name="reason">The reason for the ban.</param>
/// <param name="stoppingToken">The cancellation token.</param>
/// <returns>A collection of confirmed banned users.</returns>
protected virtual async Task<IEnumerable<BannedUser>?> BanOnce(TwitchApiProxy botProxy, INullinsideContext db,
protected virtual async Task<IEnumerable<BannedUser>?> BanOnce(ITwitchApiProxy botProxy, INullinsideContext db,
string channelId, IEnumerable<(string Id, string Username)> users, string reason,
CancellationToken stoppingToken = new()) {
// Get the list of everyone to ban
Expand All @@ -68,7 +68,7 @@ where string.Equals(bannedUsers.ChannelId, channelId) &&

// Perform the ban and get the list of people actually banned
IEnumerable<BannedUser> confirmedBans =
await botProxy.BanUsers(channelId, Constants.BotId, bansToTry, reason, stoppingToken);
await botProxy.BanChannelUsers(channelId, Constants.BotId, bansToTry, reason, stoppingToken);

await db.SaveTwitchBans(channelId, users, reason, stoppingToken);
return confirmedBans;
Expand Down
4 changes: 2 additions & 2 deletions src/Nullinside.Api.TwitchBot/Bots/BanKnownBots.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
/// <param name="botProxy">The twitch api authenticated as the bot user.</param>
/// <param name="db">The database.</param>
/// <param name="stoppingToken">The cancellation token.</param>
public override async Task Handle(User user, TwitchUserConfig config, TwitchApiProxy botProxy,
public override async Task Handle(User user, TwitchUserConfig config, ITwitchApiProxy botProxy,
INullinsideContext db, CancellationToken stoppingToken = new()) {
if (null == user.TwitchId) {
return;
Expand All @@ -79,7 +79,7 @@ public override async Task Handle(User user, TwitchUserConfig config, TwitchApiP

// Get the list of people in the chat.
List<Chatter>? chatters =
(await botProxy.GetChattersInChannel(user.TwitchId, Constants.BotId, stoppingToken))?.ToList();
(await botProxy.GetChannelUsers(user.TwitchId, Constants.BotId, stoppingToken))?.ToList();
if (null == chatters || chatters.Count == 0) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/Bots/IBotRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public interface IBotRule {
/// <param name="db">The database.</param>
/// <param name="stoppingToken">The cancellation token.</param>
/// <returns>An asynchronous task.</returns>
public Task Handle(User user, TwitchUserConfig config, TwitchApiProxy botProxy,
public Task Handle(User user, TwitchUserConfig config, ITwitchApiProxy botProxy,
INullinsideContext db, CancellationToken stoppingToken = new());
}
6 changes: 3 additions & 3 deletions src/Nullinside.Api.TwitchBot/ChatRules/AChatRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public abstract class AChatRule : IChatRule {
public abstract bool ShouldRun(TwitchUserConfig config);

/// <inheritdoc />
public abstract Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public abstract Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new());

/// <summary>
Expand All @@ -28,10 +28,10 @@ public abstract Task<bool> Handle(string channelId, TwitchApiProxy botProxy, Cha
/// <param name="reason">The ban reason.</param>
/// <param name="db">The database.</param>
/// <param name="stoppingToken">The cancellation token.</param>
public async Task BanAndLog(string channelId, TwitchApiProxy botProxy,
public async Task BanAndLog(string channelId, ITwitchApiProxy botProxy,
IEnumerable<(string Id, string Username)> users, string reason, INullinsideContext db,
CancellationToken stoppingToken = new()) {
await botProxy.BanUsers(channelId, Constants.BotId, users, reason, stoppingToken);
await botProxy.BanChannelUsers(channelId, Constants.BotId, users, reason, stoppingToken);
await db.SaveTwitchBans(channelId, users, reason, stoppingToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
if (!message.IsFirstMessage) {
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/ChatRules/Discord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
if (!message.IsFirstMessage) {
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/ChatRules/Dogehype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
// The number of spaces per message may chance, so normalize that and lowercase it for comparison.
string normalized = string.Concat(message.Message.Split(" ").Where(s => !string.IsNullOrWhiteSpace(s)))
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/ChatRules/HostHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
// The number of spaces per message may change, so normalize that and lowercase it for comparison.
string normalized = string.Concat(message.Message.Split(" ").Where(s => !string.IsNullOrWhiteSpace(s)))
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/ChatRules/IChatRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public interface IChatRule {
/// <param name="db">The database.</param>
/// <param name="stoppingToken">The cancellation token.</param>
/// <returns>An asynchronous task.</returns>
public Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message, INullinsideContext db,
public Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message, INullinsideContext db,
CancellationToken stoppingToken = new());
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
if (!message.IsFirstMessage) {
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/ChatRules/Naked.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
if (message.IsFirstMessage &&
(message.Message.TrimStart().StartsWith(_spam, StringComparison.InvariantCultureIgnoreCase) ||
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/ChatRules/StreamRise.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
if (message.IsFirstMessage && _spam.Equals(message.Message, StringComparison.InvariantCultureIgnoreCase)) {
await BanAndLog(channelId, botProxy, new[] { (message.UserId, message.Username) },
Expand Down
2 changes: 1 addition & 1 deletion src/Nullinside.Api.TwitchBot/ChatRules/StreamViewers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public override bool ShouldRun(TwitchUserConfig config) {
}

/// <inheritdoc />
public override async Task<bool> Handle(string channelId, TwitchApiProxy botProxy, ChatMessage message,
public override async Task<bool> Handle(string channelId, ITwitchApiProxy botProxy, ChatMessage message,
INullinsideContext db, CancellationToken stoppingToken = new()) {
if (!message.IsFirstMessage) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,21 @@ public class TwitchChatMessageMonitorConsumer : IDisposable {
/// </summary>
private bool _poisonPill;

/// <summary>
/// The twitch api.
/// </summary>
private readonly ITwitchApiProxy _api;

/// <summary>
/// Initializes a new instance of the <see cref="TwitchChatMessageMonitorConsumer" /> class.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="api">The twitch api.</param>
/// <param name="queue">The non-priority queue to scan messages from.</param>
public TwitchChatMessageMonitorConsumer(INullinsideContext db, BlockingCollection<ChatMessage> queue) {
public TwitchChatMessageMonitorConsumer(INullinsideContext db, ITwitchApiProxy api, BlockingCollection<ChatMessage> queue) {
_db = db;
_queue = queue;
_api = api;

_thread = new Thread(MainLoop) {
IsBackground = true,
Expand Down Expand Up @@ -138,7 +145,7 @@ private async void MainLoop() {
}

// Get the bot proxy
TwitchApiProxy? botProxy = await _db.GetBotApiAndRefreshToken();
ITwitchApiProxy? botProxy = await _db.ConfigureBotApiAndRefreshToken(this._api);
if (null == botProxy) {
continue;
}
Expand Down
19 changes: 11 additions & 8 deletions src/Nullinside.Api.TwitchBot/Controllers/BotController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Nullinside.Api.Common.Twitch;
using Nullinside.Api.Model;
using Nullinside.Api.Model.Ddl;
using Nullinside.Api.TwitchBot.Model;

using TwitchLib.Api.Helix.Models.Moderation.GetModerators;

Expand Down Expand Up @@ -49,11 +50,12 @@ public BotController(INullinsideContext dbContext, IConfiguration configuration)
/// <summary>
/// Checks if the bot account is a moderator.
/// </summary>
/// <param name="api">The twitch api.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>True if they are a mod, false otherwise.</returns>
[HttpGet]
[Route("mod")]
public async Task<IActionResult> IsMod(CancellationToken token) {
public async Task<IActionResult> IsMod([FromServices] ITwitchApiProxy api, CancellationToken token = new()) {
Claim? userId = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.UserData);
if (null == userId) {
return Unauthorized();
Expand All @@ -64,9 +66,9 @@ public async Task<IActionResult> IsMod(CancellationToken token) {
null == user.TwitchTokenExpiration || null == user.TwitchId) {
return Unauthorized();
}

var api = new TwitchApiProxy(user.TwitchToken, user.TwitchRefreshToken, user.TwitchTokenExpiration.Value);
IEnumerable<Moderator> mods = await api.GetMods(user.TwitchId, token);
api.Configure(user);
IEnumerable<Moderator> mods = await api.GetChannelMods(user.TwitchId, token);
return Ok(new {
isMod = null != mods.FirstOrDefault(m =>
string.Equals(m.UserId, Constants.BotId, StringComparison.InvariantCultureIgnoreCase))
Expand All @@ -76,11 +78,12 @@ public async Task<IActionResult> IsMod(CancellationToken token) {
/// <summary>
/// Mods the bot account.
/// </summary>
/// <param name="api">The twitch api.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>True if they are a mod, false otherwise.</returns>
[HttpPost]
[Route("mod")]
public async Task<IActionResult> ModBotAccount(CancellationToken token) {
public async Task<IActionResult> ModBotAccount([FromServices] ITwitchApiProxy api, CancellationToken token) {
Claim? userId = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.UserData);
if (null == userId) {
return Unauthorized();
Expand All @@ -91,9 +94,9 @@ public async Task<IActionResult> ModBotAccount(CancellationToken token) {
null == user.TwitchTokenExpiration || null == user.TwitchId) {
return Unauthorized();
}

var api = new TwitchApiProxy(user.TwitchToken, user.TwitchRefreshToken, user.TwitchTokenExpiration.Value);
bool success = await api.ModAccount(user.TwitchId, Constants.BotId, token);
api.Configure(user);
bool success = await api.AddChannelMod(user.TwitchId, Constants.BotId, token);
return Ok(success);
}

Expand Down
6 changes: 3 additions & 3 deletions src/Nullinside.Api.TwitchBot/Controllers/LoginController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public LoginController(INullinsideContext dbContext, IConfiguration configuratio
/// redirects users back to the nullinside website.
/// </summary>
/// <param name="code">The credentials provided by twitch.</param>
/// <param name="api">The twitch api.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>
/// A redirect to the nullinside website.
Expand All @@ -56,10 +57,9 @@ public LoginController(INullinsideContext dbContext, IConfiguration configuratio
[AllowAnonymous]
[HttpGet]
[Route("twitch-login")]
public async Task<IActionResult> TwitchLogin([FromQuery] string code, CancellationToken token) {
public async Task<IActionResult> TwitchLogin([FromQuery] string code, [FromServices] ITwitchApiProxy api, CancellationToken token) {
string? siteUrl = _configuration.GetValue<string>("Api:SiteUrl");
var api = new TwitchApiProxy();
if (!await api.GetAccessToken(code, token)) {
if (null == await api.CreateAccessToken(code, token)) {
return Redirect($"{siteUrl}/twitch-bot/login?error=3");
}

Expand Down
56 changes: 35 additions & 21 deletions src/Nullinside.Api.TwitchBot/Model/NullinsideContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ public static class NullinsideContextExtensions {
/// Gets a twitch api proxy.
/// </summary>
/// <param name="user">The user to configure the proxy as.</param>
/// <param name="api">The twitch api object currently in use.</param>
/// <returns>The twitch api.</returns>
public static TwitchApiProxy GetApi(this User user) {
return new TwitchApiProxy {
public static void Configure(this ITwitchApiProxy api, User user) {
api.OAuth = new() {
AccessToken = user.TwitchToken,
RefreshToken = user.TwitchRefreshToken,
ExpiresUtc = user.TwitchTokenExpiration
Expand All @@ -28,56 +29,69 @@ public static TwitchApiProxy GetApi(this User user) {
/// </summary>
/// <param name="db">The database.</param>
/// <param name="user">The user to configure the twitch api as.</param>
/// <param name="api">The twitch api.</param>
/// <param name="stoppingToken">The stopping token.</param>
/// <returns>The twitch api.</returns>
public static async Task<TwitchApiProxy?> GetApiAndRefreshToken(this INullinsideContext db, User user,
CancellationToken stoppingToken = new()) {
// Get the API
TwitchApiProxy api = GetApi(user);
public static async Task<ITwitchApiProxy?> ConfigureApiAndRefreshToken(this INullinsideContext db, User user,
ITwitchApiProxy api, CancellationToken stoppingToken = new()) {
api.Configure(user);

// Refresh its token if necessary.
if (!(DateTime.UtcNow + TimeSpan.FromHours(1) > user.TwitchTokenExpiration)) {
return api;
}

if (!await api.RefreshTokenAsync(stoppingToken)) {
if (null == await api.RefreshAccessToken(stoppingToken) || null == api.OAuth) {
return api;
}

User? row = await db.Users.FirstOrDefaultAsync(u => u.Id == user.Id, stoppingToken);
if (null == row) {
return null;
}

row.TwitchToken = api.AccessToken;
row.TwitchRefreshToken = api.RefreshToken;
row.TwitchTokenExpiration = api.ExpiresUtc;
await db.SaveChangesAsync(stoppingToken);

await db.UpdateOAuthInDatabase(user.Id, api.OAuth, stoppingToken);
if (Constants.BotId.Equals(user.TwitchId, StringComparison.InvariantCultureIgnoreCase)) {
TwitchClientProxy.Instance.TwitchUsername = Constants.BotUsername;
TwitchClientProxy.Instance.TwitchOAuthToken = api.AccessToken;
TwitchClientProxy.Instance.TwitchOAuthToken = api.OAuth.AccessToken;
}

return api;
}

/// <summary>
/// Updates the OAuth of a user in the database.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="userId">The user whose OAuth should be updated.</param>
/// <param name="oAuth">The OAuth information.</param>
/// <param name="stoppingToken">The stopping token.</param>
/// <returns>The number of state entries written to the database.</returns>
public static async Task<int> UpdateOAuthInDatabase(this INullinsideContext db, int userId,
TwitchAccessToken oAuth, CancellationToken stoppingToken = new()) {
User? row = await db.Users.FirstOrDefaultAsync(u => u.Id == userId, stoppingToken);
if (null == row) {
return -1;
}

row.TwitchToken = oAuth.AccessToken;
row.TwitchRefreshToken = oAuth.RefreshToken;
row.TwitchTokenExpiration = oAuth.ExpiresUtc;
return await db.SaveChangesAsync(stoppingToken);
}

/// <summary>
/// Gets a twitch api proxy for the bot user and refreshes its token if necessary.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="api">The twitch api.</param>
/// <param name="stoppingToken">The stopping token.</param>
/// <returns>The twitch api.</returns>
public static async Task<TwitchApiProxy?> GetBotApiAndRefreshToken(this INullinsideContext db,
CancellationToken stoppingToken = new()) {
public static async Task<ITwitchApiProxy?> ConfigureBotApiAndRefreshToken(this INullinsideContext db,
ITwitchApiProxy api, CancellationToken stoppingToken = new()) {
// Get the bot user's information.
User? botUser = await db.Users.AsNoTracking()
.FirstOrDefaultAsync(u => u.TwitchId == Constants.BotId, stoppingToken);
if (null == botUser) {
throw new Exception("No bot user in database");
}

return await GetApiAndRefreshToken(db, botUser, stoppingToken);
return await ConfigureApiAndRefreshToken(db, botUser, api, stoppingToken);
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Nullinside.Api.TwitchBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using Nullinside.Api.Common;
using Nullinside.Api.Common.AspNetCore.Middleware;
using Nullinside.Api.Common.Twitch;
using Nullinside.Api.Model;
using Nullinside.Api.TwitchBot.Services;

Expand All @@ -28,6 +29,7 @@
builder.EnableRetryOnFailure(3);
}), ServiceLifetime.Transient);
builder.Services.AddScoped<IAuthorizationHandler, BasicAuthorizationHandler>();
builder.Services.AddScoped<ITwitchApiProxy, TwitchApiProxy>();
builder.Services.AddHostedService<MainService>();
builder.Services.AddAuthentication()
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Bearer", _ => { });
Expand Down
Loading
Loading