util.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. package app
  2. import (
  3. "encoding/json"
  4. "io"
  5. "net/http"
  6. "net/url"
  7. "os"
  8. "skunkyart/static"
  9. "strconv"
  10. "strings"
  11. "text/template"
  12. "time"
  13. "git.macaw.me/skunky/devianter"
  14. "golang.org/x/net/html"
  15. )
  16. /* INTERNAL */
  17. var wr = io.WriteString
  18. func exit(msg string, code int) {
  19. println(msg)
  20. os.Exit(code)
  21. }
  22. func try(e error) {
  23. if e != nil {
  24. println(e.Error())
  25. }
  26. }
  27. func tryWithExitStatus(err error, code int) {
  28. if err != nil {
  29. exit(err.Error(), code)
  30. }
  31. }
  32. func restore() {
  33. if r := recover(); r != nil {
  34. recover()
  35. }
  36. }
  37. var instances []byte
  38. var About instanceAbout
  39. func RefreshInstances() {
  40. for {
  41. func() {
  42. defer restore()
  43. instances = Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body
  44. try(json.Unmarshal(instances, &About))
  45. }()
  46. time.Sleep(1 * time.Hour)
  47. }
  48. }
  49. // some crap for frontend
  50. type instanceAbout struct {
  51. Proxy bool
  52. Nsfw bool
  53. Instances []settings
  54. }
  55. type skunkyart struct {
  56. Writer http.ResponseWriter
  57. _pth string
  58. Args url.Values
  59. Page int
  60. Type rune
  61. Atom bool
  62. BasePath, Endpoint string
  63. Query, QueryRaw string
  64. API API
  65. Version string
  66. Templates struct {
  67. About instanceAbout
  68. SomeList string
  69. DDStrips string
  70. Deviation struct {
  71. Post devianter.Post
  72. Related string
  73. StringTime string
  74. Tags string
  75. Comments string
  76. }
  77. GroupUser struct {
  78. GR devianter.GRuser
  79. Admins string
  80. Group bool
  81. CreationDate string
  82. About struct {
  83. A devianter.About
  84. DescriptionFormatted string
  85. Interests, Social string
  86. Comments string
  87. BG string
  88. BGMeta devianter.Deviation
  89. }
  90. Gallery struct {
  91. Folders string
  92. Pages int
  93. List string
  94. }
  95. }
  96. Search struct {
  97. Content devianter.Search
  98. List string
  99. }
  100. }
  101. }
  102. func (s skunkyart) ExecuteTemplate(file, dir string, data any) {
  103. var buf strings.Builder
  104. tmp := template.New(file)
  105. tmp, err := tmp.ParseFS(static.Templates, dir+"/*")
  106. if err != nil {
  107. s.Writer.WriteHeader(500)
  108. wr(s.Writer, err.Error())
  109. return
  110. }
  111. try(tmp.Execute(&buf, &data))
  112. wr(s.Writer, buf.String())
  113. }
  114. func UrlBuilder(strs ...string) string {
  115. var str strings.Builder
  116. l := len(strs)
  117. str.WriteString(Host)
  118. str.WriteString(CFG.URI)
  119. for n, x := range strs {
  120. str.WriteString(x)
  121. if n := n + 1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') {
  122. str.WriteString("/")
  123. }
  124. }
  125. return str.String()
  126. }
  127. func (s skunkyart) Error(dAerr devianter.Error) {
  128. s.Writer.WriteHeader(502)
  129. var msg strings.Builder
  130. msg.WriteString(`<html><link rel="stylesheet" href="`)
  131. msg.WriteString(UrlBuilder("stylesheet"))
  132. msg.WriteString(`" /><h3>DeviantArt error — '`)
  133. msg.WriteString(dAerr.Error)
  134. msg.WriteString("'</h3></html>")
  135. wr(s.Writer, msg.String())
  136. }
  137. func (s skunkyart) ReturnHTTPError(status int) {
  138. s.Writer.WriteHeader(status)
  139. var msg strings.Builder
  140. msg.WriteString(`<html><link rel="stylesheet" href="`)
  141. msg.WriteString(UrlBuilder("stylesheet"))
  142. msg.WriteString(`" /><h1>`)
  143. msg.WriteString(strconv.Itoa(status))
  144. msg.WriteString(" - ")
  145. msg.WriteString(http.StatusText(status))
  146. msg.WriteString("</h1></html>")
  147. wr(s.Writer, msg.String())
  148. }
  149. func (s skunkyart) SetFilename(name string) {
  150. var filename strings.Builder
  151. filename.WriteString(`filename="`)
  152. filename.WriteString(name)
  153. filename.WriteString(`"`)
  154. s.Writer.Header().Add("Content-Disposition", filename.String())
  155. }
  156. type Downloaded struct {
  157. Headers http.Header
  158. Status int
  159. Body []byte
  160. }
  161. func Download(urlString string) (d Downloaded) {
  162. cli := &http.Client{}
  163. if CFG.DownloadProxy != "" {
  164. u, e := url.Parse(CFG.DownloadProxy)
  165. try(e)
  166. cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)}
  167. }
  168. req, e := http.NewRequest("GET", urlString, nil)
  169. try(e)
  170. req.Header.Set("User-Agent", CFG.UserAgent)
  171. resp, e := cli.Do(req)
  172. try(e)
  173. defer resp.Body.Close()
  174. b, e := io.ReadAll(resp.Body)
  175. try(e)
  176. d.Body = b
  177. d.Status = resp.StatusCode
  178. d.Headers = resp.Header
  179. return
  180. }
  181. /* PARSING HELPERS */
  182. func ParseMedia(media devianter.Media, thumb ...int) string {
  183. mediaUrl, filename := devianter.UrlFromMedia(media, thumb...)
  184. if len(mediaUrl) != 0 && CFG.Proxy {
  185. mediaUrl = mediaUrl[21:]
  186. dot := strings.Index(mediaUrl, ".")
  187. if filename == "" {
  188. filename = "image.gif"
  189. }
  190. return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename)
  191. } else if !CFG.Proxy {
  192. return mediaUrl
  193. }
  194. return ""
  195. }
  196. func ConvertDeviantArtUrlToSkunkyArt(url string) (output string) {
  197. if len(url) > 32 && url[27:32] != "stash" {
  198. url = url[27:]
  199. firstshash := strings.Index(url, "/")
  200. lastshash := firstshash + strings.Index(url[firstshash+1:], "/")
  201. if lastshash != -1 {
  202. output = UrlBuilder("post", url[:firstshash], url[lastshash+2:])
  203. }
  204. }
  205. return
  206. }
  207. func BuildUserPlate(name string) string {
  208. var htm strings.Builder
  209. htm.WriteString(`<div class="user-plate"><img src="`)
  210. htm.WriteString(UrlBuilder("media", "emojitar", name, "?type=a"))
  211. htm.WriteString(`"><a href="`)
  212. htm.WriteString(UrlBuilder("group_user", "?type=about&q=", name))
  213. htm.WriteString(`">`)
  214. htm.WriteString(name)
  215. htm.WriteString(`</a></div>`)
  216. return htm.String()
  217. }
  218. func GetValueOfTag(t *html.Tokenizer) string {
  219. for tt := t.Next(); ; {
  220. if tt == html.TextToken {
  221. return string(t.Text())
  222. } else {
  223. return ""
  224. }
  225. }
  226. }
  227. // навигация по страницам
  228. type DeviationList struct {
  229. Pages int
  230. More bool
  231. }
  232. // FIXME: на некоротрых артах первая страница может вызывать полное отсутствие панели навигации.
  233. func (s skunkyart) NavBase(c DeviationList) string {
  234. var list strings.Builder
  235. list.WriteString("<br>")
  236. prevrev := func(msg string, page int, onpage bool) {
  237. if !onpage {
  238. list.WriteString(`<a href="`)
  239. list.WriteString(s._pth)
  240. list.WriteString(`?p=`)
  241. list.WriteString(strconv.Itoa(page))
  242. if s.Type != 0 {
  243. list.WriteString("&type=")
  244. list.WriteRune(s.Type)
  245. }
  246. if s.Query != "" {
  247. list.WriteString("&q=")
  248. list.WriteString(s.Query)
  249. }
  250. if f := s.Args.Get("folder"); f != "" {
  251. list.WriteString("&folder=")
  252. list.WriteString(f)
  253. }
  254. list.WriteString(`">`)
  255. list.WriteString(msg)
  256. list.WriteString("</a> ")
  257. } else {
  258. list.WriteString(strconv.Itoa(page))
  259. list.WriteString(" ")
  260. }
  261. }
  262. p := s.Page
  263. if p > 1 {
  264. prevrev("<= Prev |", p-1, false)
  265. } else {
  266. p = 1
  267. }
  268. for i, x := p-6, 0; (i <= c.Pages && i <= p+6) && x < 12; i++ {
  269. if i > 0 {
  270. var onPage bool
  271. if i == p {
  272. onPage = true
  273. }
  274. prevrev(strconv.Itoa(i), i, onPage)
  275. x++
  276. }
  277. }
  278. if c.More {
  279. prevrev("| Next =>", p+1, false)
  280. }
  281. return list.String()
  282. }