nullinside is not a mod in your channel
+ @if (!waitingForModReply) {
+
+ } @else {
+
+ }
+
+} @else if (null === botIsMod) {
+
+ Checking nullinside mod status...
+
+}
+
+
+
Twitch Bot
+
+
+
+
+ Enabled
+
+
+
+
+ Ban Known Bots
+
+
+
+
+ @if (!waitingForSave) {
+
+ } @else {
+
+ }
+
+
+
+
diff --git a/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.scss b/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.scss
index e69de29..723f951 100644
--- a/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.scss
+++ b/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.scss
@@ -0,0 +1,21 @@
+@import "../../../common/constants";
+
+.text-box {
+ max-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+ border-color: $font-color;
+ border-width: 1px;
+ border-radius: 10px;
+ border-style: solid;
+ padding: 20px;
+}
+
+.save {
+ text-align: right;
+}
+
+.snack-bar-error {
+ --mdc-snackbar-container-color: green;
+ --mat-mdc-snack-bar-button-color: white;
+}
diff --git a/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.spec.ts b/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.spec.ts
index 831c546..e389d46 100644
--- a/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.spec.ts
+++ b/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.spec.ts
@@ -1,6 +1,9 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
-import { TwitchBotConfigComponent } from './twitch-bot-config.component';
+import {TwitchBotConfigComponent} from './twitch-bot-config.component';
+import {provideHttpClient, withInterceptorsFromDi} from "@angular/common/http";
+import {provideHttpClientTesting} from "@angular/common/http/testing";
+import {RouterModule} from "@angular/router";
describe('TwitchBotConfigComponent', () => {
let component: TwitchBotConfigComponent;
@@ -8,7 +11,8 @@ describe('TwitchBotConfigComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [TwitchBotConfigComponent]
+ imports: [TwitchBotConfigComponent, RouterModule.forRoot([])],
+ providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()]
})
.compileComponents();
diff --git a/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.ts b/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.ts
index ec19a13..0d356bf 100644
--- a/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.ts
+++ b/src/src/app/view/twitch/twitch-bot-config/twitch-bot-config.component.ts
@@ -1,17 +1,158 @@
-import { Component } from '@angular/core';
-import { LogoComponent } from '../../../common/components/logo/logo.component';
-import { MatButton } from '@angular/material/button';
+import {Component, OnDestroy, OnInit} from '@angular/core';
+import {LogoComponent} from '../../../common/components/logo/logo.component';
+import {MatButton} from '@angular/material/button';
+import {NullinsideTwitchBotService} from "../../../service/nullinside-twitch-bot.service";
+import {ActivatedRoute, ParamMap} from "@angular/router";
+import {Errors} from "../../login-landing/errors";
+import {HttpErrorResponse} from "@angular/common/http";
+import {NullinsideService} from "../../../service/nullinside.service";
+import {environment} from "../../../../environments/environment";
+import {LoadingIconComponent} from "../../../common/components/loading-icon/loading-icon.component";
+import {MatSlideToggle} from "@angular/material/slide-toggle";
+import {MatCheckbox} from "@angular/material/checkbox";
+import {TwitchBotFaqComponent} from "../twitch-bot-faq/twitch-bot-faq.component";
+import {MatSnackBar} from "@angular/material/snack-bar";
+import {FormsModule} from "@angular/forms";
@Component({
selector: 'app-twitch-bot-config',
standalone: true,
imports: [
LogoComponent,
- MatButton
+ MatButton,
+ LoadingIconComponent,
+ MatSlideToggle,
+ MatCheckbox,
+ TwitchBotFaqComponent,
+ FormsModule
],
templateUrl: './twitch-bot-config.component.html',
styleUrl: './twitch-bot-config.component.scss'
})
-export class TwitchBotConfigComponent {
+export class TwitchBotConfigComponent implements OnInit, OnDestroy {
+ public botIsMod: boolean | null = null;
+ public timerId: number = -1;
+ public error: string = '';
+ public waitingForModReply = false;
+ public botEnabled = true;
+ public banKnownBots = true;
+ public waitingForSave = false;
+ constructor(private twitchBotApi: NullinsideTwitchBotService,
+ private api: NullinsideService,
+ private snackBar: MatSnackBar,
+ private route: ActivatedRoute) {
+ }
+
+ ngOnDestroy(): void {
+ if (this.timerId !== -1) {
+ clearTimeout(this.timerId);
+ }
+ }
+
+ ngOnInit(): void {
+ this.route.queryParamMap.subscribe({
+ next: (params: ParamMap) => {
+ const error = params.get('error');
+ if (null !== error) {
+ const errorNum = +error;
+ if (Errors.TwitchAccountHasNoEmail === errorNum) {
+ this.onLoginFailed('Your Twitch account must have a valid e-mail address, please add one and try again', false)
+ } else if (Errors.TwitchErrorWithToken === errorNum) {
+ this.onLoginFailed('Twitch failed to give us a valid token, please add one and try again', false)
+ } else {
+ this.onLoginFailed('Sorry we did something wrong trying to log you in, please add one and try again', false)
+ }
+
+ return;
+ }
+
+ const token = params.get('token');
+ if (token) {
+ localStorage.setItem('auth-token', token);
+ this.api.validateToken(token).subscribe({
+ error: (_: HttpErrorResponse) => {
+ this.onLoginFailed();
+ }
+ });
+ }
+
+ this.twitchBotApi.getIsMod().subscribe({
+ next: response => {
+ this.botIsMod = response.isMod;
+ },
+ error: err => {
+ this.botIsMod = false;
+ this.error = 'Unable to determine if nullinside is a mod in your channel';
+ console.log(err);
+ }
+ });
+
+ this.twitchBotApi.getConfig().subscribe({
+ next: response => {
+ this.botEnabled = response.isEnabled;
+ this.banKnownBots = response.banKnownBots;
+ },
+ error: err => console.error(err)
+ });
+ },
+ error: (_: HttpErrorResponse) => {
+ this.onLoginFailed();
+ }
+ });
+ }
+
+ onLoginFailed(message = ':( Failed to login, please try again', redirect = true): void {
+ localStorage.removeItem('auth-token');
+ this.error = message;
+
+ if (redirect) {
+ this.timerId = setTimeout(() => {
+ // Need to use window.location here instead of the router because otherwise the external javascript from Google
+ // doesn't reload on the login page, and you can't retry your login until you refresh.
+ //
+ // @ts-expect-error: The expected usage of window.location is to set it directly as a string but due to typing
+ // issues that have changed over time the linting complains about it.
+ window.location = environment.siteUrl;
+ }, 5000);
+ }
+ }
+
+ modBot() {
+ this.waitingForModReply = true;
+ this.twitchBotApi.modBot().subscribe({
+ next: success => {
+ this.botIsMod = success;
+ },
+ error: err => {
+ console.error(err);
+ }
+ }).add(() => this.waitingForModReply = false);
+ }
+
+ saveConfig() {
+ this.waitingForSave = true;
+ console.log({
+ isEnabled: this.botEnabled,
+ banKnownBots: this.banKnownBots
+ })
+ this.twitchBotApi.setConfig({
+ isEnabled: this.botEnabled,
+ banKnownBots: this.banKnownBots
+ }).subscribe({
+ next: config => {
+ this.botEnabled = config.isEnabled;
+ this.banKnownBots = config.banKnownBots;
+ this.snackBar.open('Save successful', undefined, {
+ panelClass: ['snackbar-success']
+ });
+ },
+ error: err => {
+ console.error(err);
+ this.snackBar.open('Failed to save config, please try again...', undefined, {
+ panelClass: ['snackbar-failure']
+ });
+ }
+ }).add(() => this.waitingForSave = false);
+ }
}
diff --git a/src/src/app/view/twitch/twitch-bot-faq/twitch-bot-faq.component.html b/src/src/app/view/twitch/twitch-bot-faq/twitch-bot-faq.component.html
new file mode 100644
index 0000000..4a55aa4
--- /dev/null
+++ b/src/src/app/view/twitch/twitch-bot-faq/twitch-bot-faq.component.html
@@ -0,0 +1,58 @@
+
+
+ What is the nullinside bot? nullinside is a bot that joins your Twitch chat to automatically detect, block,
+ and ban other bots from your channel. These banned bots have a variety of goals including, but not limited to:
+
+
Typing spam messages
+
Advertising services in your channel
+
Posting links to malicious sites
+
Posting offensive messages
+
Executing Hate Raids
+
+
+
+ What features does nullinside support? nullinside has a variety of features that allow you to have more
+ control
+ over your channel and keep it safe from unwanted behaviours. She currently supports the following:
+
+
+ Ban Known Bots: Bans known bot accounts sourced from a variety of sources. These are bots
+ that enter a twitch chat uninvited. They may seek to gather information from you chat logs, post
+ messages and/or links, or advertise services.
+
+ So this is the only bot I need? No. nullinside is meant to help moderate your channel. Its always a
+ good idea to use a variety of tools to keep yourself and your community safe. Twitch has a variety of
+ moderation tools built-in that you should explore and configure. In addition, there are other bots available
+ that can also help secure your channel, such as Sery_Bot.
+
- What is the nullinside bot? nullinside is a bot that joins your Twitch chat to automatically detect, block,
- and ban other bots from your channel. These banned bots have a variety of goals including, but not limited to:
-
-
Typing spam messages
-
Advertising services in your channel
-
Posting links to malicious sites
-
Posting offensive messages
-
Executing Hate Raids
-
-
-
- What features does nullinside support? nullinside has a variety of features that allow you to have more
- control
- over your channel and keep it safe from unwanted behaviours. She currently supports the following:
-
-
- Ban Known Bots: Bans known bot accounts sourced from a variety of sources. These are bots
- that enter a twitch chat uninvited. They may seek to gather information from you chat logs, post
- messages and/or links, or advertise services.
-
-
- Ban Hate Follows: Hate Follows, or Hate Raids, are when hundreds or thousands of bot accounts
- follow you at the same time in order to overwhelm your stream notifications. Imagine your follower
- notification being backed up with 1000+ new follows, going off over and over again. Twitch does not
- give us integration points that allow us to prevent this behavior. However, we can reactive to the
- behavior after the fact. With this setting turned on nullinside will ban all Hate Follow/Hate Raid accounts
- after an attack.
-
-
- Ban Accounts Less than 24 Hours Old: Detects that age of accounts that follow you. If the
- account is less than 24 hours old, it bans the account from your channel. This setting can be desirable
- to prevent malicious individuals from creating new accounts in order to spam or type hateful messages
- in your chat.
-
-
- Put Chat in Emote Only When I Go Offline: A malicious method of getting accounts banned is to
- type hateful messages into a streamer's chat once they go offline when there are no moderators to take
- action. A way of preventing this is to automatically put your chat into emote-only mode when your
- channel goes offline. With this setting, nullinside will take care of putting your channel into emote-only
- mode when you go offline and removing emote-only mode when you go online. It typically takes ~1 minute
- for nullinside to notice you've gone online once you hit the start stream button.
-
-
- Put Chat in Sub Only When I Go Offline: This is the same as the above setting except it places
- your chat into Sub Only mode instead. This can be desirable if you and your subs like chatting in
- offline chat.
-
-
-
-
- So this is the only bot I need? No. nullinside is meant to help moderate your channel. Its always a
- good idea to use a variety of tools to keep yourself and your community safe. Twitch has a variety of
- moderation tools built-in that you should explore and configure. In addition, there are other bots available
- that can also help secure your channel, such as Sery_Bot.
-
-
+
diff --git a/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.scss b/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.scss
index b1b7a5a..e69de29 100644
--- a/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.scss
+++ b/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.scss
@@ -1,11 +0,0 @@
-@import "../../../common/constants";
-
-.faq {
- margin-top: 20px;
-}
-
-.text-box {
- max-width: 800px;
- margin-left: auto;
- margin-right: auto;
-}
diff --git a/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.ts b/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.ts
index 2b39266..bb25dba 100644
--- a/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.ts
+++ b/src/src/app/view/twitch/twitch-bot-index/twitch-bot-index.component.ts
@@ -1,9 +1,10 @@
-import { Component } from '@angular/core';
-import { NgOptimizedImage } from '@angular/common';
-import { LogoComponent } from '../../../common/components/logo/logo.component';
-import { MatButton } from '@angular/material/button';
-import { StandardBannerComponent } from '../../../common/components/standard-banner/standard-banner.component';
-import { TwitchLoginComponent } from '../../../common/components/twitch-login/twitch-login.component';
+import {Component} from '@angular/core';
+import {NgOptimizedImage} from '@angular/common';
+import {LogoComponent} from '../../../common/components/logo/logo.component';
+import {MatButton} from '@angular/material/button';
+import {StandardBannerComponent} from '../../../common/components/standard-banner/standard-banner.component';
+import {TwitchLoginComponent} from '../../../common/components/twitch-login/twitch-login.component';
+import {TwitchBotFaqComponent} from "../twitch-bot-faq/twitch-bot-faq.component";
@Component({
selector: 'app-twitch-bot-index',
@@ -13,7 +14,8 @@ import { TwitchLoginComponent } from '../../../common/components/twitch-login/tw
LogoComponent,
MatButton,
StandardBannerComponent,
- TwitchLoginComponent
+ TwitchLoginComponent,
+ TwitchBotFaqComponent
],
templateUrl: './twitch-bot-index.component.html',
styleUrl: './twitch-bot-index.component.scss'
diff --git a/src/src/environments/environment.development.ts b/src/src/environments/environment.development.ts
index d0ecd3c..81ca5b2 100644
--- a/src/src/environments/environment.development.ts
+++ b/src/src/environments/environment.development.ts
@@ -1,9 +1,10 @@
-import { allEnvironments } from "./environments-all";
+import {allEnvironments} from "./environments-all";
export const environment = {
siteUrl: 'http://localhost:4200',
apiUrl: 'http://localhost:5036/api/v1',
nullApiUrl: 'http://localhost:5219/null/v1',
+ twitchBotApiUrl: 'http://localhost:5941/twitch-bot/v1',
twitchClientId: 'cvipqhi9y6ri8yhv0w8ryxokxh0ebd',
...allEnvironments
};
diff --git a/src/src/environments/environment.ts b/src/src/environments/environment.ts
index 6d295c7..43d8404 100644
--- a/src/src/environments/environment.ts
+++ b/src/src/environments/environment.ts
@@ -1,9 +1,10 @@
-import { allEnvironments } from "./environments-all"
+import {allEnvironments} from "./environments-all"
export const environment = {
siteUrl: 'https://nullinside.com',
apiUrl: 'https://nullinside.com/api/v1',
nullApiUrl: 'https://nullinside.com/null/v1',
+ twitchBotApiUrl: 'https://nullinside.com/twitch-bot/v1',
twitchClientId: 'gi1eu8xu9tl6vkjqz4tjqkdzfmcq5h',
...allEnvironments
};
diff --git a/src/src/environments/environments-all.ts b/src/src/environments/environments-all.ts
index 46dc0b6..9506f08 100644
--- a/src/src/environments/environments-all.ts
+++ b/src/src/environments/environments-all.ts
@@ -8,7 +8,9 @@ export const allEnvironments = {
[
'user:read:email', // Get their email address (they have to have one associated to the account)
'moderator:read:chatters', // Reads your chat and the chats of those you moderate
- 'moderator:manage:banned_users' // Allow you to ban in your chat and those you moderate
+ 'moderator:manage:banned_users', // Allow you to ban in your chat and those you moderate
+ 'channel:manage:moderators', // Allows us to make the bot account a moderator for them
+ 'moderation:read' // Allows us to check who is a moderator
]
]
}
diff --git a/src/src/styles.scss b/src/src/styles.scss
index 7e7e265..9432695 100644
--- a/src/src/styles.scss
+++ b/src/src/styles.scss
@@ -84,6 +84,10 @@ h1 {
border-bottom: 1px solid $font-color;
}
+.mdc-form-field {
+ color: $font-color !important;
+}
+
.mat-body,
.mat-body-2,
.mat-typography .mat-body,
@@ -207,3 +211,19 @@ body {
margin-left: auto;
margin-right: auto;
}
+
+.no-select {
+ user-select: none;
+}
+
+.snackbar-success {
+ --mdc-snackbar-container-color: rgba(0, 109, 0, 0.48);
+ --mat-mdc-snack-bar-button-color: $font-color;
+ --mdc-snackbar-supporting-text-color: $font-color;
+}
+
+.snackbar-failure {
+ --mdc-snackbar-container-color: rgba(109, 0, 0, 0.48);
+ --mat-mdc-snack-bar-button-color: $font-color;
+ --mdc-snackbar-supporting-text-color: $font-color;
+}