diff --git a/README.MD b/README.MD
index b6f6bfa..9a5bb20 100644
--- a/README.MD
+++ b/README.MD
@@ -3,6 +3,10 @@
### What is this?
Justlog is an twitch irc bot. It focuses on logging and providing an api for the logs.
+### Optout
+
+Click the X icon on the web ui to find a explanation how to opt out.
+
### API
API documentation can be viewed via the justlog frontend by clicking the "docs" symbol:
diff --git a/api/optout.go b/api/optout.go
new file mode 100644
index 0000000..6f051f2
--- /dev/null
+++ b/api/optout.go
@@ -0,0 +1,45 @@
+package api
+
+import (
+ "math/rand"
+ "net/http"
+ "time"
+)
+
+func init() {
+ rand.Seed(time.Now().UnixNano())
+}
+
+// swagger:route POST /optout justlog
+//
+// Generates optout code to use in chat
+//
+// Produces:
+// - application/json
+//
+// Schemes: https
+//
+// Responses:
+// 200: string
+func (s *Server) writeOptOutCode(w http.ResponseWriter, r *http.Request) {
+
+ code := randomString(6)
+
+ s.bot.OptoutCodes.Store(code, true)
+ go func() {
+ time.Sleep(time.Second * 60)
+ s.bot.OptoutCodes.Delete(code)
+ }()
+
+ writeJSON(code, http.StatusOK, w, r)
+}
+
+var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890")
+
+func randomString(n int) string {
+ b := make([]rune, n)
+ for i := range b {
+ b[i] = letterRunes[rand.Intn(len(letterRunes))]
+ }
+ return string(b)
+}
diff --git a/api/server.go b/api/server.go
index 031e823..e64c295 100644
--- a/api/server.go
+++ b/api/server.go
@@ -131,6 +131,11 @@ func (s *Server) route(w http.ResponseWriter, r *http.Request) {
return
}
+ if url == "/optout" && r.Method == http.MethodPost {
+ s.writeOptOutCode(w, r)
+ return
+ }
+
if strings.HasPrefix(url, "/admin/channels") {
success := s.authenticateAdmin(w, r)
if success {
@@ -195,11 +200,11 @@ func (s *Server) routeLogs(w http.ResponseWriter, r *http.Request) bool {
var logs *chatLog
if request.time.random {
- if request.isUserRequest {
- logs, err = s.getRandomQuote(request)
- } else {
- logs, err = s.getChannelRandomQuote(request)
- }
+ if request.isUserRequest {
+ logs, err = s.getRandomQuote(request)
+ } else {
+ logs, err = s.getChannelRandomQuote(request)
+ }
} else if request.time.from != "" && request.time.to != "" {
if request.isUserRequest {
logs, err = s.getUserLogsRange(request)
diff --git a/bot/commands.go b/bot/commands.go
index 54c4d68..79ecaac 100644
--- a/bot/commands.go
+++ b/bot/commands.go
@@ -20,10 +20,6 @@ func (b *Bot) handlePrivateMessageCommands(message twitch.PrivateMessage) {
return
}
- if !contains(b.cfg.Admins, message.User.Name) {
- return
- }
-
args := strings.Fields(message.Message[len(commandPrefix):])
if len(args) < 1 {
return
@@ -33,19 +29,30 @@ func (b *Bot) handlePrivateMessageCommands(message twitch.PrivateMessage) {
switch commandName {
case "status":
+ if !contains(b.cfg.Admins, message.User.Name) {
+ return
+ }
uptime := humanize.TimeSince(b.startTime)
b.Say(message.Channel, fmt.Sprintf("%s, uptime: %s", message.User.DisplayName, uptime))
case "join":
+ if !contains(b.cfg.Admins, message.User.Name) {
+ return
+ }
b.handleJoin(message, args)
case "part":
+ if !contains(b.cfg.Admins, message.User.Name) {
+ return
+ }
b.handlePart(message, args)
case "optout":
b.handleOptOut(message, args)
-
case "optin":
+ if !contains(b.cfg.Admins, message.User.Name) {
+ return
+ }
b.handleOptIn(message, args)
}
}
@@ -100,6 +107,16 @@ func (b *Bot) handleOptOut(message twitch.PrivateMessage, args []string) {
return
}
+ if _, ok := b.OptoutCodes.LoadAndDelete(args[0]); ok {
+ b.cfg.OptOutUsers(message.User.ID)
+ b.Say(message.Channel, fmt.Sprintf("%s, opted you out", message.User.DisplayName))
+ return
+ }
+
+ if !contains(b.cfg.Admins, message.User.Name) {
+ return
+ }
+
users, err := b.helixClient.GetUsersByUsernames(args)
if err != nil {
log.Error(err)
diff --git a/bot/main.go b/bot/main.go
index 56eeeb3..9ec7520 100644
--- a/bot/main.go
+++ b/bot/main.go
@@ -23,6 +23,7 @@ type Bot struct {
worker []*worker
channels map[string]helix.UserData
clearchats sync.Map
+ OptoutCodes sync.Map
}
type worker struct {
@@ -43,6 +44,7 @@ func NewBot(cfg *config.Config, helixClient helix.TwitchApiClient, fileLogger *f
fileLogger: fileLogger,
channels: channels,
worker: []*worker{},
+ OptoutCodes: sync.Map{},
}
}
diff --git a/web/src/components/Filters.tsx b/web/src/components/Filters.tsx
index 5379c36..3ca0289 100644
--- a/web/src/components/Filters.tsx
+++ b/web/src/components/Filters.tsx
@@ -6,6 +6,7 @@ import styled from "styled-components";
import { useChannels } from "../hooks/useChannels";
import { store } from "../store";
import { Docs } from "./Docs";
+import { Optout } from "./Optout";
import { Settings } from "./Settings";
const FiltersContainer = styled.form`
@@ -66,6 +67,7 @@ export function Filters() {
+
}
\ No newline at end of file
diff --git a/web/src/components/Optout.tsx b/web/src/components/Optout.tsx
new file mode 100644
index 0000000..92d6df3
--- /dev/null
+++ b/web/src/components/Optout.tsx
@@ -0,0 +1,87 @@
+import { IconButton, Button } from "@material-ui/core";
+import { useContext, useState } from "react";
+import styled from "styled-components";
+import { store } from "../store";
+import CancelIcon from '@material-ui/icons/Cancel';
+
+const OptoutWrapper = styled.div`
+
+`;
+
+export function Optout() {
+ const { state, setShowOptout } = useContext(store);
+
+ const handleClick = () => {
+ setShowOptout(!state.showOptout);
+ }
+
+ return
+
+
+
+ ;
+}
+
+const OptoutPanelWrapper = styled.div`
+ background: var(--bg-bright);
+ color: var(--text);
+ margin: 3rem;
+ font-size: 1.5rem;
+ padding: 2rem;
+
+ code {
+ background: var(--bg);
+ padding: 1rem;
+ border-radius: 3px;
+ }
+
+ .generator {
+ margin-top: 2rem;
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+
+ input {
+ background: var(--bg);
+ border: none;
+ color: white;
+ padding: 0.6rem;
+ font-size: 1.5rem;
+ text-align: center;
+ border-radius: 3px;
+ }
+ }
+
+ .small {
+ font-size: 0.8rem;
+ font-family: monospace;
+ }
+`;
+
+export function OptoutPanel() {
+ const { state } = useContext(store);
+ const [code, setCode] = useState("");
+
+ const generateCode = () => {
+ fetch(state.apiBaseUrl + "/optout", { method: "POST" }).then(res => res.json()).then(setCode).catch(console.error);
+ };
+
+ return
+
+ You can opt out from being logged. This will also disable access to your previously logged data.
+ Opting out is permanent, there is not reverse action. So think twice if you want to opt out.
+
+
+ If you still want to optout generate a token here and paste the command into a logged chat.
+ You will receive a confirmation message from the bot "@username, opted you out".
+