krz/devianter
A DeviantArt guest API library for Go.
clone: git clone https://gitbay.org/krz/devianter.git
v0.1.0: misc.go · raw
1package devianter
2
3import (
4 "encoding/json"
5 "errors"
6 "io"
7 "log"
8 "math"
9 "net/http"
10 "strconv"
11 "strings"
12)
13
14// функция для высера ошибки в stderr
15func err(txt error) {
16 if txt != nil {
17 println(txt.Error())
18 }
19}
20
21// сокращение для вызова щенка и парсинга жсона
22func ujson(data string, output any) {
23 input, e := puppy(data)
24 err(e)
25
26 eee := json.Unmarshal([]byte(input), output)
27 err(eee)
28}
29
30/* REQUEST SECTION */
31// структура для ответа сервера
32type reqrt struct {
33 Body string
34 Status int
35 Cookies []*http.Cookie
36 Headers http.Header
37}
38
39// функция для совершения запроса
40func request(uri string, other ...string) reqrt {
41 var r reqrt
42
43 // создаём новый запрос
44 cli := &http.Client{}
45 req, e := http.NewRequest("GET", uri, nil)
46 err(e)
47
48 req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0")
49
50 // куки и UA-шник
51 if other != nil {
52 for num, rng := range other {
53 switch num {
54 case 1:
55 req.Header.Set("User-Agent", rng)
56 case 0:
57 req.Header.Set("Cookie", rng)
58 }
59 }
60 }
61
62 resp, e := cli.Do(req)
63 err(e)
64 defer resp.Body.Close()
65
66 body, e := io.ReadAll(resp.Body)
67 err(e)
68
69 // заполняем структуру
70 r.Body = string(body)
71 r.Cookies = resp.Cookies()
72 r.Headers = resp.Header
73 r.Status = resp.StatusCode
74
75 return r
76}
77
78/* AVATARS AND EMOJIS */
79func AEmedia(name string, t rune) (string, error) {
80 // список всех возможных расширений
81 var extensions = [3]string{
82 ".jpg",
83 ".png",
84 ".gif",
85 }
86 // надо
87 name = strings.ToLower(name)
88
89 // построение ссылок. билдер потому что он быстрее обычного сложения строк.
90 var b strings.Builder
91 switch t {
92 case 'a':
93 b.WriteString("https://a.deviantart.net/avatars-big/")
94 b.WriteString(name[:1])
95 b.WriteString("/")
96 b.WriteString(name[1:2])
97 b.WriteString("/")
98 case 'e':
99 b.WriteString("https://e.deviantart.net/emoticons/")
100 b.WriteString(name[:1])
101 b.WriteString("/")
102 default:
103 log.Fatalln("Invalid type.\n- 'a' -- avatar;\n- 'e' -- emoji.")
104 }
105 b.WriteString(name)
106
107 // проверка ссылки на доступность
108 for x := 0; x < len(extensions); x++ {
109 req := request(b.String() + extensions[x])
110 if req.Status == 200 {
111 return req.Body, nil
112 }
113 }
114
115 return "", errors.New("User not exists")
116}
117
118/* SEARCH */
119type search struct {
120 Total int `json:"estTotal"`
121 Pages int // only for 'a' and 'g' scope.
122 Results []deviantion `json:"deviations,results"`
123}
124
125func Search(query string, page int, scope rune, user ...string) (ss search, e error) {
126 var url strings.Builder
127 e = nil
128
129 // о5 построение ссылок.
130 switch scope {
131 case 'a': // поиск артов по названию
132 url.WriteString("dabrowse/search/all?q=")
133 case 't': // поиск артов по тегам
134 url.WriteString("dabrowse/networkbar/tag/deviations?tag=")
135 case 'g': // поиск артов пользователя или группы
136 if user != nil {
137 url.WriteString("dashared/gallection/search?username=")
138 for _, a := range user {
139 url.WriteString(a)
140 }
141 url.WriteString("&type=gallery&order=most-recent&init=true&limit=50&q=")
142 } else {
143 e = errors.New("Missing username (last argument)")
144 return
145 }
146 default:
147 log.Fatalln("Invalid type.\n- 'a' -- all;\n- 't' -- tag;\n- 'g' - gallery.")
148 }
149
150 url.WriteString(query)
151 if scope != 'g' { // если область поиска не равна поиску по группам, то активируется этот код
152 url.WriteString("&page=")
153 } else { // иначе вместо страницы будет оффсет и страница умножится на 50
154 url.WriteString("&offset=")
155 page = 50 * page
156 }
157 url.WriteString(strconv.Itoa(page))
158
159 ujson(url.String(), &ss)
160
161 // расчёт, сколько всего страниц по запросу. без токена 417 страниц - максимум
162 for x := 0; x < int(math.Round(float64(ss.Total/25))); x++ {
163 if x <= 417 {
164 ss.Pages = x
165 }
166 }
167
168 return
169}
170
171/* PUPPY aka DeviantArt API */
172func puppy(data string) (string, error) {
173 // получение или обновление токена
174 update := func() (string, string, error) {
175 var cookie string
176 if cookie == "" {
177 req := request("https://www.deviantart.com/_puppy")
178
179 for _, content := range req.Cookies {
180 cookie = content.Raw
181 }
182 }
183
184 req := request("https://www.deviantart.com", cookie)
185 if req.Status != 200 {
186 return "", "", errors.New(req.Body)
187 }
188
189 return cookie, req.Body[strings.Index(req.Body, "window.__CSRF_TOKEN__ = '")+25 : strings.Index(req.Body, "window.__XHR_LOCAL__")-3], nil
190 }
191
192 // использование токена
193 var (
194 cookie, token string
195 )
196 if cookie == "" || token == "" {
197 var e error
198 cookie, token, e = update()
199 if e != nil {
200 return "", e
201 }
202 }
203
204 var url strings.Builder
205 url.WriteString("https://www.deviantart.com/_puppy/")
206 url.WriteString(data)
207 url.WriteString("&csrf_token=")
208 url.WriteString(token)
209 url.WriteString("&da_minor_version=20230710")
210
211 body := request(url.String(), cookie)
212
213 // если код ответа не 200, возвращается ошибка
214 if body.Status != 200 {
215 return "", errors.New(body.Body)
216 }
217
218 return body.Body, nil
219}