Commit 0a6260b2e0
Unsigned
Layout: unified · split
misc.go −106
| @@ -1,79 +1,14 @@ | |||
| 1 | package devianter | 1 | package devianter |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | ||
| 5 | "errors" | 4 | "errors" |
| 6 | "io" | ||
| 7 | "log" | 5 | "log" |
| 8 | "math" | 6 | "math" |
| 9 | "net/http" | ||
| 10 | u "net/url" | 7 | u "net/url" |
| 11 | "strconv" | 8 | "strconv" |
| 12 | "strings" | 9 | "strings" |
| 13 | ) | 10 | ) |
| 14 | 11 | ||
| 15 | // функция для высера ошибки в stderr | ||
| 16 | func err(txt error) { | ||
| 17 | if txt != nil { | ||
| 18 | println(txt.Error()) | ||
| 19 | } | ||
| 20 | } | ||
| 21 | |||
| 22 | // сокращение для вызова щенка и парсинга жсона | ||
| 23 | func ujson(data string, output any) { | ||
| 24 | input, e := puppy(data) | ||
| 25 | err(e) | ||
| 26 | |||
| 27 | eee := json.Unmarshal([]byte(input), output) | ||
| 28 | err(eee) | ||
| 29 | } | ||
| 30 | |||
| 31 | /* REQUEST SECTION */ | ||
| 32 | // структура для ответа сервера | ||
| 33 | type reqrt struct { | ||
| 34 | Body string | ||
| 35 | Status int | ||
| 36 | Cookies []*http.Cookie | ||
| 37 | Headers http.Header | ||
| 38 | } | ||
| 39 | |||
| 40 | // функция для совершения запроса | ||
| 41 | func request(uri string, other ...string) reqrt { | ||
| 42 | var r reqrt | ||
| 43 | |||
| 44 | // создаём новый запрос | ||
| 45 | cli := &http.Client{} | ||
| 46 | req, e := http.NewRequest("GET", uri, nil) | ||
| 47 | err(e) | ||
| 48 | |||
| 49 | req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0") | ||
| 50 | |||
| 51 | // куки и UA-шник | ||
| 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 | resp, e := cli.Do(req) | ||
| 62 | err(e) | ||
| 63 | defer resp.Body.Close() | ||
| 64 | |||
| 65 | body, e := io.ReadAll(resp.Body) | ||
| 66 | err(e) | ||
| 67 | |||
| 68 | // заполняем структуру | ||
| 69 | r.Body = string(body) | ||
| 70 | r.Cookies = resp.Cookies() | ||
| 71 | r.Headers = resp.Header | ||
| 72 | r.Status = resp.StatusCode | ||
| 73 | |||
| 74 | return r | ||
| 75 | } | ||
| 76 | |||
| 77 | /* AVATARS AND EMOJIS */ | 12 | /* AVATARS AND EMOJIS */ |
| 78 | func AEmedia(name string, t rune) (string, error) { | 13 | func AEmedia(name string, t rune) (string, error) { |
| 79 | // список всех возможных расширений | 14 | // список всех возможных расширений |
| @@ -189,44 +124,3 @@ func SearchFunc(query string, page int, scope rune, user ...string) (ss Search, | |||
| 189 | 124 | ||
| 190 | return | 125 | return |
| 191 | } | 126 | } |
| 192 | |||
| 193 | /* PUPPY aka DeviantArt API */ | ||
| 194 | // получение или обновление токена | ||
| 195 | var cookie string | ||
| 196 | var token string | ||
| 197 | |||
| 198 | func UpdateCSRF() error { | ||
| 199 | if cookie == "" { | ||
| 200 | req := request("https://www.deviantart.com/_puppy") | ||
| 201 | |||
| 202 | for _, content := range req.Cookies { | ||
| 203 | cookie = content.Raw | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | req := request("https://www.deviantart.com", cookie) | ||
| 208 | if req.Status != 200 { | ||
| 209 | return errors.New(req.Body) | ||
| 210 | } | ||
| 211 | token = req.Body[strings.Index(req.Body, "window.__CSRF_TOKEN__ = '")+25 : strings.Index(req.Body, "window.__XHR_LOCAL__")-3] | ||
| 212 | |||
| 213 | return nil | ||
| 214 | } | ||
| 215 | |||
| 216 | func puppy(data string) (string, error) { | ||
| 217 | var url strings.Builder | ||
| 218 | url.WriteString("https://www.deviantart.com/_puppy/") | ||
| 219 | url.WriteString(data) | ||
| 220 | url.WriteString("&csrf_token=") | ||
| 221 | url.WriteString(token) | ||
| 222 | url.WriteString("&da_minor_version=20230710") | ||
| 223 | |||
| 224 | body := request(url.String(), cookie) | ||
| 225 | |||
| 226 | // если код ответа не 200, возвращается ошибка | ||
| 227 | if body.Status != 200 { | ||
| 228 | return "", errors.New(body.Body) | ||
| 229 | } | ||
| 230 | |||
| 231 | return body.Body, nil | ||
| 232 | } | ||
user-group.go +54 −23
| @@ -6,13 +6,14 @@ import ( | |||
| 6 | ) | 6 | ) |
| 7 | 7 | ||
| 8 | // структура группы или пользователя | 8 | // структура группы или пользователя |
| 9 | type groups struct { | 9 | type GroupAbout struct { |
| 10 | GroupAbout struct { | 10 | FoundatedAt time `json:"foundationTs"` |
| 11 | FoundatedAt time `json:"foundationTs"` | 11 | Description Text |
| 12 | Description Text | 12 | } |
| 13 | } | 13 | type GroupAdmins struct { |
| 14 | GroupAdmins struct { | 14 | Results []struct { |
| 15 | Results []struct { | 15 | TypeId int |
| 16 | User struct { | ||
| 16 | Username string | 17 | Username string |
| 17 | } | 18 | } |
| 18 | } | 19 | } |
| @@ -50,11 +51,32 @@ type GRuser struct { | |||
| 50 | Modules []struct { | 51 | Modules []struct { |
| 51 | Name string | 52 | Name string |
| 52 | ModuleData struct { | 53 | ModuleData struct { |
| 53 | groups | 54 | GroupAbout GroupAbout |
| 55 | GroupAdmins GroupAdmins | ||
| 54 | users | 56 | users |
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | Extra struct { | ||
| 62 | Tag string `json:"gruserTagline"` | ||
| 63 | Stats struct { | ||
| 64 | Deviations, Watchers, Watching, Pageviews, CommentsMade, Favourites, Friends int | ||
| 65 | FeedComments int `json:"commentsReceivedProfile"` | ||
| 66 | } | ||
| 67 | } `json:"pageExtraData"` | ||
| 68 | } | ||
| 55 | 69 | ||
| 70 | type Gallery struct { | ||
| 71 | Gruser struct { | ||
| 72 | ID int `json:"gruserId"` | ||
| 73 | Page struct { | ||
| 74 | Modules []struct { | ||
| 75 | Name string | ||
| 76 | ModuleData struct { | ||
| 56 | // группы | 77 | // группы |
| 57 | Folders struct { | 78 | Folders struct { |
| 79 | HasMore bool | ||
| 58 | Results []struct { | 80 | Results []struct { |
| 59 | FolderId int | 81 | FolderId int |
| 60 | Name string | 82 | Name string |
| @@ -63,6 +85,7 @@ type GRuser struct { | |||
| 63 | 85 | ||
| 64 | // галерея | 86 | // галерея |
| 65 | Folder struct { | 87 | Folder struct { |
| 88 | HasMore bool | ||
| 66 | Username string | 89 | Username string |
| 67 | Pages int `json:"totalPageCount"` | 90 | Pages int `json:"totalPageCount"` |
| 68 | Deviations []Deviation | 91 | Deviations []Deviation |
| @@ -71,18 +94,13 @@ type GRuser struct { | |||
| 71 | } | 94 | } |
| 72 | } | 95 | } |
| 73 | } | 96 | } |
| 74 | Extra struct { | 97 | HasMore bool |
| 75 | Tag string `json:"gruserTagline"` | 98 | Results []Deviation |
| 76 | Stats struct { | ||
| 77 | Deviations, Watchers, Watching, Pageviews, CommentsMade, Favourites, Friends int | ||
| 78 | FeedComments int `json:"commentsReceivedProfile"` | ||
| 79 | } | ||
| 80 | } `json:"pageExtraData"` | ||
| 81 | } | 99 | } |
| 82 | 100 | ||
| 83 | type Group struct { | 101 | type Group struct { |
| 84 | Name string // обязательно заполнить | 102 | Name string // обязательно заполнить |
| 85 | Content GRuser | 103 | Content Gallery |
| 86 | } | 104 | } |
| 87 | 105 | ||
| 88 | // подходит как группа, так и пользователь | 106 | // подходит как группа, так и пользователь |
| @@ -93,14 +111,27 @@ func (s Group) GroupFunc() (g GRuser) { | |||
| 93 | } | 111 | } |
| 94 | 112 | ||
| 95 | // гарелея пользователя или группы | 113 | // гарелея пользователя или группы |
| 96 | func (s Group) Gallery(page int) (g Group) { | 114 | func (s Group) Gallery(page int, folderid ...int) (g Group) { |
| 97 | var url strings.Builder | 115 | var url strings.Builder |
| 98 | url.WriteString("dauserprofile/init/gallery?username=") | 116 | if folderid[0] > 0 { |
| 99 | url.WriteString(s.Name) | 117 | page-- |
| 100 | url.WriteString("&page=") | 118 | url.WriteString("dashared/gallection/contents?username=") |
| 101 | url.WriteString(strconv.Itoa(page)) | 119 | url.WriteString(s.Name) |
| 102 | url.WriteString("&deviations_limit=50&with_subfolders=false") | 120 | url.WriteString("&folderid=") |
| 121 | url.WriteString(strconv.Itoa(folderid[0])) | ||
| 122 | url.WriteString("&offset=") | ||
| 123 | url.WriteString(strconv.Itoa(page * 50)) | ||
| 124 | url.WriteString("&type=gallery&") | ||
| 125 | } else { | ||
| 126 | url.WriteString("dauserprofile/init/gallery?username=") | ||
| 127 | url.WriteString(s.Name) | ||
| 128 | url.WriteString("&page=") | ||
| 129 | url.WriteString(strconv.Itoa(page)) | ||
| 130 | url.WriteString("&deviations_") | ||
| 131 | } | ||
| 132 | url.WriteString("limit=50") | ||
| 133 | url.WriteString("&with_subfolders=false") | ||
| 103 | 134 | ||
| 104 | ujson(url.String(), &g) | 135 | ujson(url.String(), &g.Content) |
| 105 | return | 136 | return |
| 106 | } | 137 | } |
util.go added +112
| @@ -0,0 +1,112 @@ | |||
| 1 | package devianter | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "encoding/json" | ||
| 5 | "errors" | ||
| 6 | "io" | ||
| 7 | "net/http" | ||
| 8 | "strings" | ||
| 9 | ) | ||
| 10 | |||
| 11 | // функция для высера ошибки в stderr | ||
| 12 | func err(txt error) { | ||
| 13 | if txt != nil { | ||
| 14 | println(txt.Error()) | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | // сокращение для вызова щенка и парсинга жсона | ||
| 19 | func ujson(data string, output any) { | ||
| 20 | input, e := puppy(data) | ||
| 21 | err(e) | ||
| 22 | |||
| 23 | eee := json.Unmarshal([]byte(input), output) | ||
| 24 | err(eee) | ||
| 25 | } | ||
| 26 | |||
| 27 | /* REQUEST SECTION */ | ||
| 28 | // структура для ответа сервера | ||
| 29 | type reqrt struct { | ||
| 30 | Body string | ||
| 31 | Status int | ||
| 32 | Cookies []*http.Cookie | ||
| 33 | Headers http.Header | ||
| 34 | } | ||
| 35 | |||
| 36 | // функция для совершения запроса | ||
| 37 | func request(uri string, other ...string) reqrt { | ||
| 38 | var r reqrt | ||
| 39 | |||
| 40 | // создаём новый запрос | ||
| 41 | cli := &http.Client{} | ||
| 42 | req, e := http.NewRequest("GET", uri, nil) | ||
| 43 | err(e) | ||
| 44 | |||
| 45 | req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0") | ||
| 46 | |||
| 47 | // куки и UA-шник | ||
| 48 | for num, rng := range other { | ||
| 49 | switch num { | ||
| 50 | case 1: | ||
| 51 | req.Header.Set("User-Agent", rng) | ||
| 52 | case 0: | ||
| 53 | req.Header.Set("Cookie", rng) | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | resp, e := cli.Do(req) | ||
| 58 | err(e) | ||
| 59 | defer resp.Body.Close() | ||
| 60 | |||
| 61 | body, e := io.ReadAll(resp.Body) | ||
| 62 | err(e) | ||
| 63 | |||
| 64 | // заполняем структуру | ||
| 65 | r.Body = string(body) | ||
| 66 | r.Cookies = resp.Cookies() | ||
| 67 | r.Headers = resp.Header | ||
| 68 | r.Status = resp.StatusCode | ||
| 69 | |||
| 70 | return r | ||
| 71 | } | ||
| 72 | |||
| 73 | /* PUPPY aka DeviantArt API */ | ||
| 74 | // получение или обновление токена | ||
| 75 | var cookie string | ||
| 76 | var token string | ||
| 77 | |||
| 78 | func UpdateCSRF() error { | ||
| 79 | if cookie == "" { | ||
| 80 | req := request("https://www.deviantart.com/_puppy") | ||
| 81 | |||
| 82 | for _, content := range req.Cookies { | ||
| 83 | cookie = content.Raw | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | req := request("https://www.deviantart.com", cookie) | ||
| 88 | if req.Status != 200 { | ||
| 89 | return errors.New(req.Body) | ||
| 90 | } | ||
| 91 | token = req.Body[strings.Index(req.Body, "window.__CSRF_TOKEN__ = '")+25 : strings.Index(req.Body, "window.__XHR_LOCAL__")-3] | ||
| 92 | |||
| 93 | return nil | ||
| 94 | } | ||
| 95 | |||
| 96 | func puppy(data string) (string, error) { | ||
| 97 | var url strings.Builder | ||
| 98 | url.WriteString("https://www.deviantart.com/_puppy/") | ||
| 99 | url.WriteString(data) | ||
| 100 | url.WriteString("&csrf_token=") | ||
| 101 | url.WriteString(token) | ||
| 102 | url.WriteString("&da_minor_version=20230710") | ||
| 103 | |||
| 104 | body := request(url.String(), cookie) | ||
| 105 | |||
| 106 | // если код ответа не 200, возвращается ошибка | ||
| 107 | if body.Status != 200 { | ||
| 108 | return "", errors.New(body.Body) | ||
| 109 | } | ||
| 110 | |||
| 111 | return body.Body, nil | ||
| 112 | } | ||