1diff --git a/main.go b/main.go
2index 22d1489b26af1ccd0d907ddec6974bc518acc0aa..7a8d2a67604cdc163da9c1c6549593d046605745 100644
3--- a/main.go
4+++ b/main.go
5@@ -2,10 +2,12 @@ package main
6
7 import (
8 "context"
9+ "embed"
10 "errors"
11 "flag"
12 "fmt"
13 "io"
14+ "log/slog"
15 "net/http"
16 "os"
17 "regexp"
18@@ -20,15 +22,17 @@ "github.com/prometheus/client_golang/prometheus/promhttp"
19 )
20
21 const (
22- FeedUrl = "https://api.jovemnerd.com.br/feed-nerdcast/"
23+ feedUrl = "https://api.jovemnerd.com.br/feed-nerdcast/"
24 )
25
26 type (
27- ErrorRequestHandler func(w http.ResponseWriter, r *http.Request) error
28+ errorRequestHandler func(w http.ResponseWriter, r *http.Request) error
29 )
30
31 var (
32- SerieRegex = regexp.MustCompile(`(?P<serie>.+) (?P<number>[0-9abc]+) \- (?P<title>.+)`)
33+ //go:embed static/*
34+ assets embed.FS
35+ serieRegex = regexp.MustCompile(`(?P<serie>.+) (?P<number>[0-9abc]+) \- (?P<title>.+)`)
36 )
37
38 var (
39@@ -46,7 +50,6 @@ "catar": "Vai te Catar [0-9]+",
40 "cloud": "Nerd na Cloud [0-9]+",
41 "contar": "Vou (T|t)e Contar [0-9]+",
42 "parceiro": "Papo de Parceiro [0-9]+",
43- "cash": "NerdCash [0-9]+",
44 }
45
46 feedRequest = promauto.NewHistogramVec(prometheus.HistogramOpts{
47@@ -105,7 +108,7 @@ code := strconv.Itoa(c)
48 feedRequest.WithLabelValues(code).Observe(since)
49 }()
50
51- res, err := http.Get(FeedUrl)
52+ res, err := http.Get(feedUrl)
53 if err != nil {
54 return nil, err
55 }
56@@ -157,9 +160,10 @@
57 return doc.WriteToBytes()
58 }
59
60-func handleError(next ErrorRequestHandler) http.HandlerFunc {
61+func handleError(next errorRequestHandler) http.HandlerFunc {
62 return func(w http.ResponseWriter, r *http.Request) {
63 if err := next(w, r); err != nil {
64+ slog.ErrorContext(r.Context(), "Error", "error", err.Error())
65 w.WriteHeader(http.StatusInternalServerError)
66 }
67 }
68@@ -217,6 +221,20 @@
69 return nil
70 }
71
72+func view(w http.ResponseWriter, r *http.Request) error {
73+ data, err := assets.ReadFile("static/index.html")
74+ if err != nil {
75+ return err
76+ }
77+
78+ _, err = w.Write(data)
79+ if err != nil {
80+ return err
81+ }
82+
83+ return nil
84+}
85+
86 func podcast(w http.ResponseWriter, r *http.Request) error {
87 xml, err := fetchXML(r.Context())
88 if err != nil {
89@@ -253,7 +271,7 @@ unique := make(map[string]any)
90 els := doc.FindElements("//channel/item")
91 for _, e := range els {
92 txt := e.FindElement("title").Text()
93- res := SerieRegex.FindStringSubmatch(txt)
94+ res := serieRegex.FindStringSubmatch(txt)
95 if len(res) > 1 {
96 unique[res[1]] = nil
97 }
98@@ -284,6 +302,7 @@
99 mux := http.NewServeMux()
100 mux.Handle("/metrics", promhttp.Handler())
101 mux.HandleFunc("/titles", wrap(handleError(titles)))
102+ mux.HandleFunc("/view", wrap(handleError(view)))
103 mux.HandleFunc("/", wrap(observe(handleError(podcast))))
104
105 server := http.Server{
106diff --git a/static/index.html b/static/index.html
107new file mode 100644
108index 0000000000000000000000000000000000000000..25341fe7617bd568b168e24d97365c8930cdaed8
109--- /dev/null
110+++ b/static/index.html
111@@ -0,0 +1,212 @@
112+<!DOCTYPE html>
113+<html>
114+ <head lang="pt">
115+ <meta charset="utf-8">
116+ <meta name="viewport" content="width=device-width, initial-scale=1">
117+ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
118+ <style type="text/css" media="screen">
119+/* Resettings some html properties */
120+html, body, div, h1, header,section{
121+ margin: 0;
122+ padding: 0;
123+ border: 0;
124+ font-family: monospace;
125+}
126+
127+h1 {
128+ font-size: 1.25rem;
129+ color: #fff;
130+ text-transform: uppercase;
131+}
132+
133+ul {
134+ margin: 0;
135+}
136+
137+body {
138+ font-family: sans-serif;
139+ background-color: #f4f4f4;
140+}
141+
142+header {
143+ display: flex;
144+ justify-content: space-between;
145+ margin: auto;
146+ margin-bottom: 1em;
147+ background: #0062cc;
148+ padding: .75em;
149+ max-width: 960px;
150+}
151+
152+nav {
153+ top: .75em;
154+ right: .75em;
155+}
156+
157+.warning {
158+ font-size: small;
159+ color: red;
160+ visibility: hidden;
161+}
162+
163+.btn {
164+ display: inline-block;
165+ padding: .1rem .75rem;
166+ background: #e9ecef;
167+ border: #343a40 1px solid;
168+ font-size: .9rem;
169+ font-weight: 400;
170+ line-height: 1.5;
171+ cursor: pointer;
172+ color: #000;
173+ border-radius: 0;
174+ text-decoration: none;
175+ transition: 0.5s all;
176+}
177+
178+.btn:hover {
179+ background-color: #fff;
180+}
181+
182+nav li {
183+ display: inline;
184+ margin: 0 0 0 0;
185+}
186+
187+.section {
188+ justify-content: left;
189+ margin-bottom: 1em;
190+ display: flex;
191+}
192+
193+.section input {
194+ max-width: 10em;
195+}
196+
197+.form-ctl {
198+ width: 1.3em;
199+ height: 1.3em;
200+ background-color: white;
201+ border-radius: 0;
202+ vertical-align: middle;
203+ border: 1px solid #ddd;
204+ appearance: none;
205+ -webkit-appearance: none;
206+ outline: none;
207+}
208+
209+.form-ctl:checked {
210+ background-color: gray;
211+}
212+
213+a {
214+ display: flex;
215+ justify-content: center;
216+ align-content: center;
217+ flex-direction: column;
218+ padding-left: 5px;
219+}
220+
221+main {
222+ width: 40%;
223+ margin: 0 auto;
224+}
225+
226+#feedUrl {
227+ text-overflow: ellipsis;
228+ max-width: 100%;
229+ white-space: normal;
230+ word-break: break-all;
231+}
232+
233+@media (width <= 600px) {
234+ main {
235+ width: 90%;
236+ }
237+}
238+
239+ </style>
240+ </head>
241+ <body>
242+ <header>
243+ <h1>Filtro para o Nerdcast</h1>
244+ <nav>
245+ <ul>
246+ <li>
247+ <a class="btn" href="https://git.gabrielgio.me/jnfilter/">Código fonte ➤</a>
248+ </li>
249+ </ul>
250+ </nav>
251+ </header>
252+ <main>
253+ <div class="section">
254+ Selecione os quadros:
255+ </div>
256+ <div class="section">
257+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="nerdcast" /><a>NerdCast</a>
258+ </div>
259+ <div class="section">
260+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="empreendedor" /><a>Empreendedor</a>
261+ </div>
262+ <div class="section">
263+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="mamicas" /><a>Canecas de Mamicas</a>
264+ </div>
265+ <div class="section">
266+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="english" /><a>Speak English</a>
267+ </div>
268+ <div class="section">
269+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="nerdcash" /><a>NerdCash</a>
270+ </div>
271+ <div class="section">
272+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="bunker" /><a>Lá do Bunker</a>
273+ </div>
274+ <div class="section">
275+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="tech" /><a>NerdTech</a>
276+ </div>
277+ <div class="section">
278+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="genera" /><a>Generacast</a>
279+ </div>
280+ <div class="section">
281+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="rpg" /><a>NerdCast RPG</a>
282+ </div>
283+ <div class="section">
284+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="catar" /><a>Vai te Catar</a>
285+ </div>
286+ <div class="section">
287+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="cloud" /><a>Nerd na Cloud</a>
288+ </div>
289+ <div class="section">
290+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="contar" /><a>Vou te Contar</a>
291+ </div>
292+ <div class="section">
293+ <input class="form-ctl" type="checkbox" onchange="updateList(this)" id="parceiro" /><a>Papo de Parceiro</a>
294+ </div>
295+ <div class="section">
296+ <a id="feedUrl" href="https://jnfilter.gabrielgio.me?q=mamicas" type="textbox">https://jnfilter.gabrielgio.me</a>
297+ </div>
298+ </main>
299+ <script>
300+var feedUrl = document.getElementById("feedUrl")
301+var fields = new Set()
302+
303+function updateFeedUrl() {
304+ if (fields.size == 0) {
305+ url = "https://jnfilter.gabrielgio.me"
306+ } else {
307+ url = "https://jnfilter.gabrielgio.me?q="+[...fields].join(',')
308+ }
309+ feedUrl.textContent = url
310+ feedUrl.href = url
311+}
312+
313+function updateList(elem) {
314+ if (elem.checked){
315+ fields.add(elem.id)
316+ } else {
317+ fields.delete(elem.id)
318+ }
319+ updateFeedUrl()
320+}
321+ </script>
322+ </body>
323+</html>