package app import ( "encoding/json" "io" "net/http" "net/url" "os" "skunkyart/static" "strconv" "strings" "text/template" "time" "git.macaw.me/skunky/devianter" "golang.org/x/net/html" ) /* INTERNAL */ var wr = io.WriteString func exit(msg string, code int) { println(msg) os.Exit(code) } func try(e error) { if e != nil { println(e.Error()) } } func tryWithExitStatus(err error, code int) { if err != nil { exit(err.Error(), code) } } func restore() { if r := recover(); r != nil { recover() } } var instances []byte var About instanceAbout func RefreshInstances() { for { func() { defer restore() instances = Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body try(json.Unmarshal(instances, &About)) }() time.Sleep(1 * time.Hour) } } // some crap for frontend type instanceAbout struct { Proxy bool Nsfw bool Instances []settings } type skunkyart struct { Writer http.ResponseWriter _pth string Args url.Values Page int Type rune Atom bool BasePath, Endpoint string Query, QueryRaw string API API Version string Templates struct { About instanceAbout SomeList string DDStrips string Deviation struct { Post devianter.Post Related string StringTime string Tags string Comments string } GroupUser struct { GR devianter.GRuser Admins string Group bool CreationDate string About struct { A devianter.About DescriptionFormatted string Interests, Social string Comments string BG string BGMeta devianter.Deviation } Gallery struct { Folders string Pages int List string } } Search struct { Content devianter.Search List string } } } func (s skunkyart) ExecuteTemplate(file, dir string, data any) { var buf strings.Builder tmp := template.New(file) tmp, err := tmp.ParseFS(static.Templates, dir+"/*") if err != nil { s.Writer.WriteHeader(500) wr(s.Writer, err.Error()) return } try(tmp.Execute(&buf, &data)) wr(s.Writer, buf.String()) } func UrlBuilder(strs ...string) string { var str strings.Builder l := len(strs) str.WriteString(Host) str.WriteString(CFG.URI) for n, x := range strs { str.WriteString(x) if n := n + 1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') { str.WriteString("/") } } return str.String() } func (s skunkyart) Error(dAerr devianter.Error) { s.Writer.WriteHeader(502) var msg strings.Builder msg.WriteString(`

DeviantArt error — '`) msg.WriteString(dAerr.Error) msg.WriteString("'

") wr(s.Writer, msg.String()) } func (s skunkyart) ReturnHTTPError(status int) { s.Writer.WriteHeader(status) var msg strings.Builder msg.WriteString(`

`) msg.WriteString(strconv.Itoa(status)) msg.WriteString(" - ") msg.WriteString(http.StatusText(status)) msg.WriteString("

") wr(s.Writer, msg.String()) } func (s skunkyart) SetFilename(name string) { var filename strings.Builder filename.WriteString(`filename="`) filename.WriteString(name) filename.WriteString(`"`) s.Writer.Header().Add("Content-Disposition", filename.String()) } type Downloaded struct { Headers http.Header Status int Body []byte } func Download(urlString string) (d Downloaded) { cli := &http.Client{} if CFG.DownloadProxy != "" { u, e := url.Parse(CFG.DownloadProxy) try(e) cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)} } req, e := http.NewRequest("GET", urlString, nil) try(e) req.Header.Set("User-Agent", CFG.UserAgent) resp, e := cli.Do(req) try(e) defer resp.Body.Close() b, e := io.ReadAll(resp.Body) try(e) d.Body = b d.Status = resp.StatusCode d.Headers = resp.Header return } /* PARSING HELPERS */ func ParseMedia(media devianter.Media, thumb ...int) string { mediaUrl, filename := devianter.UrlFromMedia(media, thumb...) if len(mediaUrl) != 0 && CFG.Proxy { mediaUrl = mediaUrl[21:] dot := strings.Index(mediaUrl, ".") if filename == "" { filename = "image.gif" } return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename) } else if !CFG.Proxy { return mediaUrl } return "" } func ConvertDeviantArtUrlToSkunkyArt(url string) (output string) { if len(url) > 32 && url[27:32] != "stash" { url = url[27:] firstshash := strings.Index(url, "/") lastshash := firstshash + strings.Index(url[firstshash+1:], "/") if lastshash != -1 { output = UrlBuilder("post", url[:firstshash], url[lastshash+2:]) } } return } func BuildUserPlate(name string) string { var htm strings.Builder htm.WriteString(`
`) htm.WriteString(name) htm.WriteString(`
`) return htm.String() } func GetValueOfTag(t *html.Tokenizer) string { for tt := t.Next(); ; { if tt == html.TextToken { return string(t.Text()) } else { return "" } } } // навигация по страницам type DeviationList struct { Pages int More bool } // FIXME: на некоротрых артах первая страница может вызывать полное отсутствие панели навигации. func (s skunkyart) NavBase(c DeviationList) string { var list strings.Builder list.WriteString("
") prevrev := func(msg string, page int, onpage bool) { if !onpage { list.WriteString(``) list.WriteString(msg) list.WriteString(" ") } else { list.WriteString(strconv.Itoa(page)) list.WriteString(" ") } } p := s.Page if p > 1 { prevrev("<= Prev |", p-1, false) } else { p = 1 } for i, x := p-6, 0; (i <= c.Pages && i <= p+6) && x < 12; i++ { if i > 0 { var onPage bool if i == p { onPage = true } prevrev(strconv.Itoa(i), i, onPage) x++ } } if c.More { prevrev("| Next =>", p+1, false) } return list.String() }