package main import ( "crypto/subtle" "log" "net/http" "os" "regexp" "time" ) // versionLine matches the userscript metadata block's @version directive. var versionLine = regexp.MustCompile(`(?m)^// @version[ \t]+.*$`) // stampVersion replaces the served @version with one derived from the file's // mtime, discarding whatever the file body says. // // Violentmonkey only updates when the served version sorts higher than the // installed one. Deriving it from the body means one accidental downgrade or // typo freezes updates forever; an mtime-derived version is monotonic by // construction, so any later write always outranks any earlier one. // // A file with no @version line is returned untouched: such a script never // auto-updates anyway, and inventing a metadata block is not this handler's job. func stampVersion(src []byte, mod time.Time) []byte { return versionLine.ReplaceAll(src, []byte("// @version "+mod.UTC().Format("2006.01.02.1504"))) } // userscriptHandler serves the userscript to Violentmonkey's updater. // // The token lives in the path because the update poll sends no Authorization // header, and the file embeds API_TOKEN in plain text, so an open path would // hand that token to anyone who guessed the URL. A mismatch answers 404 rather // than 401: a prober learns nothing about whether the route exists. // // The file is read per request — that is what lets a bindmounted copy be edited // on the host without a restart. It is ~50 KB and polled about once a day. func userscriptHandler(token, path string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if subtle.ConstantTimeCompare([]byte(r.PathValue("token")), []byte(token)) != 1 { http.NotFound(w, r) return } info, err := os.Stat(path) if err != nil { log.Printf("userscript: stat %s: %v", path, err) http.NotFound(w, r) return } src, err := os.ReadFile(path) if err != nil { log.Printf("userscript: read %s: %v", path, err) http.NotFound(w, r) return } w.Header().Set("Content-Type", "text/javascript; charset=utf-8") w.Header().Set("Cache-Control", "no-cache") w.Write(stampVersion(src, info.ModTime())) } }