jnfilter @ 5217357b4635fad76ca35e655517387f61ccbb2a

feat: Add view to select series
diff --git a/main.go b/main.go
index 22d1489b26af1ccd0d907ddec6974bc518acc0aa..7a8d2a67604cdc163da9c1c6549593d046605745 100644
--- a/main.go
+++ b/main.go
@@ -2,10 +2,12 @@ package main
 
 import (
 	"context"
+	"embed"
 	"errors"
 	"flag"
 	"fmt"
 	"io"
+	"log/slog"
 	"net/http"
 	"os"
 	"regexp"
@@ -20,15 +22,17 @@ 	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
 const (
-	FeedUrl = "https://api.jovemnerd.com.br/feed-nerdcast/"
+	feedUrl = "https://api.jovemnerd.com.br/feed-nerdcast/"
 )
 
 type (
-	ErrorRequestHandler func(w http.ResponseWriter, r *http.Request) error
+	errorRequestHandler func(w http.ResponseWriter, r *http.Request) error
 )
 
 var (
-	SerieRegex = regexp.MustCompile(`(?P<serie>.+) (?P<number>[0-9abc]+) \- (?P<title>.+)`)
+	//go:embed static/*
+	assets     embed.FS
+	serieRegex = regexp.MustCompile(`(?P<serie>.+) (?P<number>[0-9abc]+) \- (?P<title>.+)`)
 )
 
 var (
@@ -46,7 +50,6 @@ 		"catar":        "Vai te Catar [0-9]+",
 		"cloud":        "Nerd na Cloud [0-9]+",
 		"contar":       "Vou (T|t)e Contar [0-9]+",
 		"parceiro":     "Papo de Parceiro [0-9]+",
-		"cash":         "NerdCash [0-9]+",
 	}
 
 	feedRequest = promauto.NewHistogramVec(prometheus.HistogramOpts{
@@ -105,7 +108,7 @@ 		code := strconv.Itoa(c)
 		feedRequest.WithLabelValues(code).Observe(since)
 	}()
 
-	res, err := http.Get(FeedUrl)
+	res, err := http.Get(feedUrl)
 	if err != nil {
 		return nil, err
 	}
@@ -157,9 +160,10 @@
 	return doc.WriteToBytes()
 }
 
-func handleError(next ErrorRequestHandler) http.HandlerFunc {
+func handleError(next errorRequestHandler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		if err := next(w, r); err != nil {
+			slog.ErrorContext(r.Context(), "Error", "error", err.Error())
 			w.WriteHeader(http.StatusInternalServerError)
 		}
 	}
@@ -217,6 +221,20 @@
 	return nil
 }
 
+func view(w http.ResponseWriter, r *http.Request) error {
+	data, err := assets.ReadFile("static/index.html")
+	if err != nil {
+		return err
+	}
+
+	_, err = w.Write(data)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func podcast(w http.ResponseWriter, r *http.Request) error {
 	xml, err := fetchXML(r.Context())
 	if err != nil {
@@ -253,7 +271,7 @@ 	unique := make(map[string]any)
 	els := doc.FindElements("//channel/item")
 	for _, e := range els {
 		txt := e.FindElement("title").Text()
-		res := SerieRegex.FindStringSubmatch(txt)
+		res := serieRegex.FindStringSubmatch(txt)
 		if len(res) > 1 {
 			unique[res[1]] = nil
 		}
@@ -284,6 +302,7 @@
 	mux := http.NewServeMux()
 	mux.Handle("/metrics", promhttp.Handler())
 	mux.HandleFunc("/titles", wrap(handleError(titles)))
+	mux.HandleFunc("/view", wrap(handleError(view)))
 	mux.HandleFunc("/", wrap(observe(handleError(podcast))))
 
 	server := http.Server{
diff --git a/static/index.html b/static/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..25341fe7617bd568b168e24d97365c8930cdaed8
--- /dev/null
+++ b/static/index.html
@@ -0,0 +1,212 @@
+<!DOCTYPE html>
+<html>
+    <head lang="pt">
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
+        <style type="text/css" media="screen">
+/* Resettings some html properties */
+html, body, div, h1, header,section{
+	margin: 0;
+	padding: 0;
+	border: 0;
+    font-family: monospace;
+}
+
+h1 {
+    font-size: 1.25rem;
+    color: #fff;
+    text-transform: uppercase;
+}
+
+ul {
+    margin: 0;
+}
+
+body {
+    font-family: sans-serif;
+    background-color: #f4f4f4;
+}
+
+header {
+    display: flex;
+    justify-content: space-between;
+    margin: auto;
+    margin-bottom: 1em;
+    background: #0062cc;
+    padding: .75em;
+    max-width: 960px;
+}
+
+nav {
+    top: .75em;
+    right: .75em;
+}
+
+.warning {
+  font-size: small;
+  color: red;
+  visibility: hidden;
+}
+
+.btn {
+  display: inline-block;
+  padding: .1rem .75rem;
+  background: #e9ecef;
+  border: #343a40 1px solid;
+  font-size: .9rem;
+  font-weight: 400;
+  line-height: 1.5;
+  cursor: pointer;
+  color: #000;
+  border-radius: 0;
+  text-decoration: none;
+  transition: 0.5s all;
+}
+
+.btn:hover {
+    background-color: #fff;
+}
+
+nav li {
+    display: inline;
+    margin: 0 0 0 0;
+}
+
+.section {
+    justify-content: left;
+    margin-bottom: 1em;
+    display: flex;
+}
+
+.section input {
+    max-width: 10em;
+}
+
+.form-ctl {
+    width: 1.3em;
+    height: 1.3em;
+    background-color: white;
+    border-radius: 0;
+    vertical-align: middle;
+    border: 1px solid #ddd;
+    appearance: none;
+    -webkit-appearance: none;
+    outline: none;
+}
+
+.form-ctl:checked {
+    background-color: gray;
+}
+
+a {
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  flex-direction: column;
+  padding-left: 5px;
+}
+
+main {
+    width: 40%;
+    margin: 0 auto;
+}
+
+#feedUrl {
+    text-overflow: ellipsis;
+    max-width: 100%;
+    white-space: normal;
+    word-break: break-all;
+}
+
+@media (width <= 600px) {
+    main {
+        width: 90%;
+    }
+}
+
+        </style>
+    </head>
+    <body>
+        <header>
+            <h1>Filtro para o Nerdcast</h1>
+            <nav>
+                <ul>
+                    <li>
+                        <a class="btn" href="https://git.gabrielgio.me/jnfilter/">Código fonte ➤</a>
+                    </li>
+                </ul>
+            </nav>
+        </header>
+        <main>
+            <div class="section">
+                Selecione os quadros:
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="nerdcast" /><a>NerdCast</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="empreendedor" /><a>Empreendedor</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="mamicas" /><a>Canecas de Mamicas</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="english" /><a>Speak English</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="nerdcash" /><a>NerdCash</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="bunker" /><a>Lá do Bunker</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="tech" /><a>NerdTech</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="genera" /><a>Generacast</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="rpg" /><a>NerdCast RPG</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="catar" /><a>Vai te Catar</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="cloud" /><a>Nerd na Cloud</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="contar" /><a>Vou te Contar</a>
+            </div>
+            <div class="section">
+                <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="parceiro" /><a>Papo de Parceiro</a>
+            </div>
+            <div class="section">
+                <a id="feedUrl" href="https://jnfilter.gabrielgio.me?q=mamicas" type="textbox">https://jnfilter.gabrielgio.me</a>
+            </div>
+        </main>
+        <script>
+var feedUrl = document.getElementById("feedUrl")
+var fields = new Set()
+
+function updateFeedUrl() {
+    if (fields.size == 0) {
+        url = "https://jnfilter.gabrielgio.me"
+    } else {
+        url = "https://jnfilter.gabrielgio.me?q="+[...fields].join(',')
+    }
+    feedUrl.textContent = url
+    feedUrl.href = url
+}
+
+function updateList(elem) {
+    if (elem.checked){
+        fields.add(elem.id)
+    } else {
+        fields.delete(elem.id)
+    }
+    updateFeedUrl()
+}
+        </script>
+    </body>
+</html>