Commit 42ea2980f9
Unsigned
Layout: unified · split
.vscode/launch.json deleted −16
| @@ -1,16 +0,0 @@ | ||
| 1 | { | |
| 2 | // Use IntelliSense to learn about possible attributes. | |
| 3 | // Hover to view descriptions of existing attributes. | |
| 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | |
| 5 | "version": "0.2.0", | |
| 6 | "configurations": [ | |
| 7 | { | |
| 8 | "name": "Launch Package", | |
| 9 | "type": "go", | |
| 10 | "request": "launch", | |
| 11 | "mode": "debug", | |
| 12 | "program": "${fileDirname}", | |
| 13 | "showLog": true | |
| 14 | } | |
| 15 | ] | |
| 16 | } | |
| \ No newline at end of file | ||
.vscode/settings.json deleted −5
| @@ -1,5 +0,0 @@ | ||
| 1 | { | |
| 2 | "go.toolsEnvVars": { | |
| 3 | "GOROOT": "" | |
| 4 | } | |
| 5 | } | |
| \ No newline at end of file | ||
README.md +2 −2
| @@ -1,9 +1,9 @@ | ||
| 1 | 1 | # Devianter |
| 2 | 2 | |
| 3 | [](https://nogithub.codeberg.page) [](https://pkg.go.dev/git.macaw.me/skunky/devianter) [](https://www.gnu.org/licenses/agpl-3.0) | |
| 3 | [](https://nogithub.codeberg.page) [](https://pkg.go.dev/git.macaw.me/skunky/devianter) | |
| 4 | 4 | |
| 5 | 5 | |
| 6 | A DeviantART API library for Go. | |
| 6 | A DeviantART guest API library for Go. | |
| 7 | 7 | |
| 8 | 8 | |
| 9 | 9 | I'll probably write up some documentation, but more on that later. |
comments.go +3 −3
| @@ -11,7 +11,7 @@ type Thread struct { | ||
| 11 | 11 | ID int `json:"commentId"` |
| 12 | 12 | Parent int `json:"parentId"` |
| 13 | 13 | |
| 14 | Posted time | |
| 14 | Posted timeStamp | |
| 15 | 15 | Author bool `json:"isAuthorHighlited"` |
| 16 | 16 | |
| 17 | 17 | Desctiption string |
| @@ -35,7 +35,7 @@ type Comments struct { | ||
| 35 | 35 | } |
| 36 | 36 | |
| 37 | 37 | // 1 - комментарии поста; 4 - комментарии на стене группы или пользователя |
| 38 | func CommentsFunc(postid string, cursor string, page int, typ int) (cmmts Comments) { | |
| 38 | func GetComments(postid string, cursor string, page int, typ int) (cmmts Comments) { | |
| 39 | 39 | for x := 0; x <= page; x++ { |
| 40 | 40 | ujson( |
| 41 | 41 | "dashared/comments/thread?typeid="+strconv.Itoa(typ)+ |
| @@ -60,7 +60,7 @@ func CommentsFunc(postid string, cursor string, page int, typ int) (cmmts Commen | ||
| 60 | 60 | } |
| 61 | 61 | |
| 62 | 62 | e := json.Unmarshal([]byte(m), &content) |
| 63 | err(e) | |
| 63 | try(e) | |
| 64 | 64 | |
| 65 | 65 | for _, a := range content.Blocks { |
| 66 | 66 | cmmts.Thread[i].Comment = a.Text |
deviantion.go +23 −9
| @@ -4,26 +4,26 @@ import ( | ||
| 4 | 4 | "encoding/json" |
| 5 | 5 | "strconv" |
| 6 | 6 | "strings" |
| 7 | timelib "time" | |
| 7 | "time" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | // хрень для парсинга времени публикации |
| 11 | type time struct { | |
| 12 | timelib.Time | |
| 11 | type timeStamp struct { | |
| 12 | time.Time | |
| 13 | 13 | } |
| 14 | 14 | |
| 15 | func (t *time) UnmarshalJSON(b []byte) (err error) { | |
| 15 | func (t *timeStamp) UnmarshalJSON(b []byte) (err error) { | |
| 16 | 16 | if b[0] == '"' && b[len(b)-1] == '"' { |
| 17 | 17 | b = b[1 : len(b)-1] |
| 18 | 18 | } |
| 19 | t.Time, err = timelib.Parse("2006-01-02T15:04:05-0700", string(b)) | |
| 19 | t.Time, err = time.Parse("2006-01-02T15:04:05-0700", string(b)) | |
| 20 | 20 | return |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | 23 | // самая главная структура для поста |
| 24 | 24 | type Deviation struct { |
| 25 | 25 | Title, Url, License string |
| 26 | PublishedTime time | |
| 26 | PublishedTime timeStamp | |
| 27 | 27 | ID int `json:"deviationId"` |
| 28 | 28 | |
| 29 | 29 | NSFW bool `json:"isMature"` |
| @@ -82,7 +82,7 @@ type Post struct { | ||
| 82 | 82 | |
| 83 | 83 | ParsedComments []struct { |
| 84 | 84 | Author string |
| 85 | Posted time | |
| 85 | Posted timeStamp | |
| 86 | 86 | Replies, Likes int |
| 87 | 87 | } |
| 88 | 88 | |
| @@ -90,12 +90,25 @@ type Post struct { | ||
| 90 | 90 | } |
| 91 | 91 | |
| 92 | 92 | // преобразование урла в правильный |
| 93 | func UrlFromMedia(m Media) string { | |
| 93 | func UrlFromMedia(m Media, thumb ...int) string { | |
| 94 | 94 | var url strings.Builder |
| 95 | ||
| 96 | subtractWidthHeight := func(to int, target ...*int) { | |
| 97 | for i, l := 0, len(target); i < l; i++ { | |
| 98 | for x := *target[i]; x > to; x -= to { | |
| 99 | *target[i] = x | |
| 100 | } | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 95 | 104 | for _, t := range m.Types { |
| 96 | 105 | if t.T == "fullview" { |
| 97 | 106 | url.WriteString(m.BaseUri) |
| 98 | 107 | if m.BaseUri[len(m.BaseUri)-3:] != "gif" && t.W*t.H < 33177600 { |
| 108 | if len(thumb) != 0 { | |
| 109 | subtractWidthHeight(thumb[0], &t.W, &t.H) | |
| 110 | } | |
| 111 | ||
| 99 | 112 | url.WriteString("/v1/fit/w_") |
| 100 | 113 | url.WriteString(strconv.Itoa(t.W)) |
| 101 | 114 | url.WriteString(",h_") |
| @@ -110,11 +123,12 @@ func UrlFromMedia(m Media) string { | ||
| 110 | 123 | } |
| 111 | 124 | } |
| 112 | 125 | } |
| 126 | ||
| 113 | 127 | return url.String() |
| 114 | 128 | } |
| 115 | 129 | |
| 116 | 130 | // для работы функции нужно ID поста и имя пользователя. |
| 117 | func DeviationFunc(id string, user string) Post { | |
| 131 | func GetDeviation(id string, user string) Post { | |
| 118 | 132 | var st Post |
| 119 | 133 | ujson( |
| 120 | 134 | "dadeviation/init?deviationid="+id+"&username="+user+"&type=art&include_session=false&expand=deviation.related&preload=true", |
misc.go +20 −18
| @@ -4,13 +4,16 @@ import ( | ||
| 4 | 4 | "errors" |
| 5 | 5 | "log" |
| 6 | 6 | "math" |
| 7 | u "net/url" | |
| 7 | "net/url" | |
| 8 | 8 | "strconv" |
| 9 | 9 | "strings" |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | /* AVATARS AND EMOJIS */ |
| 13 | 13 | func AEmedia(name string, t rune) (string, error) { |
| 14 | if len(name) < 2 { | |
| 15 | return "", errors.New("name must be specified") | |
| 16 | } | |
| 14 | 17 | // список всех возможных расширений |
| 15 | 18 | var extensions = [3]string{ |
| 16 | 19 | ".jpg", |
| @@ -25,9 +28,10 @@ func AEmedia(name string, t rune) (string, error) { | ||
| 25 | 28 | switch t { |
| 26 | 29 | case 'a': |
| 27 | 30 | b.WriteString("https://a.deviantart.net/avatars-big/") |
| 28 | b.WriteString(name[:1]) | |
| 31 | name_without_dashes := strings.ReplaceAll(name, "-", "_") | |
| 32 | b.WriteString(name_without_dashes[:1]) | |
| 29 | 33 | b.WriteString("/") |
| 30 | b.WriteString(name[1:2]) | |
| 34 | b.WriteString(name_without_dashes[1:2]) | |
| 31 | 35 | b.WriteString("/") |
| 32 | 36 | case 'e': |
| 33 | 37 | b.WriteString("https://e.deviantart.net/emoticons/") |
| @@ -60,7 +64,7 @@ type DailyDeviations struct { | ||
| 60 | 64 | Deviations []Deviation |
| 61 | 65 | } |
| 62 | 66 | |
| 63 | func DailyDeviationsFunc(page int) (dd DailyDeviations) { | |
| 67 | func GetDailyDeviations(page int) (dd DailyDeviations) { | |
| 64 | 68 | ujson("dabrowse/networkbar/rfy/deviations?page="+strconv.Itoa(page), &dd) |
| 65 | 69 | return |
| 66 | 70 | } |
| @@ -74,23 +78,21 @@ type Search struct { | ||
| 74 | 78 | ResultsGalleryTemp []Deviation `json:"results"` |
| 75 | 79 | } |
| 76 | 80 | |
| 77 | func SearchFunc(query string, page int, scope rune, user ...string) (ss Search, e error) { | |
| 78 | var url strings.Builder | |
| 81 | func PerformSearch(query string, page int, scope rune, user ...string) (ss Search, e error) { | |
| 82 | var buildurl strings.Builder | |
| 79 | 83 | e = nil |
| 80 | 84 | |
| 81 | 85 | // о5 построение ссылок. |
| 82 | 86 | switch scope { |
| 83 | 87 | case 'a': // поиск артов по названию |
| 84 | url.WriteString("dabrowse/search/all?q=") | |
| 88 | buildurl.WriteString("dabrowse/search/all?q=") | |
| 85 | 89 | case 't': // поиск артов по тегам |
| 86 | url.WriteString("dabrowse/networkbar/tag/deviations?tag=") | |
| 90 | buildurl.WriteString("dabrowse/networkbar/tag/deviations?tag=") | |
| 87 | 91 | case 'g': // поиск артов пользователя или группы |
| 88 | 92 | if user != nil { |
| 89 | url.WriteString("dashared/gallection/search?username=") | |
| 90 | for _, a := range user { | |
| 91 | url.WriteString(a) | |
| 92 | } | |
| 93 | url.WriteString("&type=gallery&order=most-recent&init=true&limit=50&q=") | |
| 93 | buildurl.WriteString("dashared/gallection/search?username=") | |
| 94 | buildurl.WriteString(user[0]) | |
| 95 | buildurl.WriteString("&type=gallery&order=most-recent&init=true&limit=50&q=") | |
| 94 | 96 | } else { |
| 95 | 97 | e = errors.New("missing username (last argument)") |
| 96 | 98 | return |
| @@ -99,16 +101,16 @@ func SearchFunc(query string, page int, scope rune, user ...string) (ss Search, | ||
| 99 | 101 | log.Fatalln("Invalid type.\n- 'a' -- all;\n- 't' -- tag;\n- 'g' - gallery.") |
| 100 | 102 | } |
| 101 | 103 | |
| 102 | url.WriteString(u.QueryEscape(query)) | |
| 104 | buildurl.WriteString(url.QueryEscape(query)) | |
| 103 | 105 | if scope != 'g' { // если область поиска не равна поиску по группам, то активируется этот код |
| 104 | url.WriteString("&page=") | |
| 106 | buildurl.WriteString("&page=") | |
| 105 | 107 | } else { // иначе вместо страницы будет оффсет и страница умножится на 50 |
| 106 | url.WriteString("&offset=") | |
| 108 | buildurl.WriteString("&offset=") | |
| 107 | 109 | page = 50 * page |
| 108 | 110 | } |
| 109 | url.WriteString(strconv.Itoa(page)) | |
| 111 | buildurl.WriteString(strconv.Itoa(page)) | |
| 110 | 112 | |
| 111 | ujson(url.String(), &ss) | |
| 113 | ujson(buildurl.String(), &ss) | |
| 112 | 114 | |
| 113 | 115 | if scope == 'g' { |
| 114 | 116 | ss.Results = ss.ResultsGalleryTemp |
todo.md deleted −2
| @@ -1,2 +0,0 @@ | ||
| 1 | - groups | |
| 2 | - images in comments | |
| \ No newline at end of file | ||
user-group.go +43 −35
| @@ -1,44 +1,12 @@ | ||
| 1 | 1 | package devianter |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "errors" | |
| 4 | 5 | "strconv" |
| 5 | 6 | "strings" |
| 6 | 7 | ) |
| 7 | 8 | |
| 8 | 9 | // структура группы или пользователя |
| 9 | type GroupAbout struct { | |
| 10 | FoundatedAt time `json:"foundationTs"` | |
| 11 | Description Text | |
| 12 | } | |
| 13 | type GroupAdmins struct { | |
| 14 | Results []struct { | |
| 15 | TypeId int | |
| 16 | User struct { | |
| 17 | Username string | |
| 18 | } | |
| 19 | } | |
| 20 | } | |
| 21 | ||
| 22 | type About struct { | |
| 23 | Country, Website, WebsiteLabel, Gender string | |
| 24 | RegDate int64 `json:"deviantFor"` | |
| 25 | Description Text `json:"textContent"` | |
| 26 | ||
| 27 | SocialLinks []struct { | |
| 28 | Value string | |
| 29 | } | |
| 30 | Interests []struct { | |
| 31 | Label, Value string | |
| 32 | } | |
| 33 | } | |
| 34 | ||
| 35 | type users struct { | |
| 36 | About About | |
| 37 | CoverDeviation struct { | |
| 38 | Deviation Deviation `json:"coverDeviation"` | |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | 10 | type GRuser struct { |
| 43 | 11 | ErrorDescription string |
| 44 | 12 | Owner struct { |
| @@ -106,14 +74,21 @@ type Group struct { | ||
| 106 | 74 | } |
| 107 | 75 | |
| 108 | 76 | // подходит как группа, так и пользователь |
| 109 | func (s Group) GroupFunc() (g GRuser) { | |
| 77 | func (s Group) GetGroup() (g GRuser, err error) { | |
| 78 | if s.Name == "" { | |
| 79 | return g, errors.New("missing Name field") | |
| 80 | } | |
| 110 | 81 | ujson("dauserprofile/init/about?username="+s.Name, &g) |
| 111 | 82 | |
| 112 | 83 | return |
| 113 | 84 | } |
| 114 | 85 | |
| 115 | 86 | // гарелея пользователя или группы |
| 116 | func (s Group) Gallery(page int, folderid ...int) (g Group) { | |
| 87 | func (s Group) GetGallery(page int, folderid ...int) (g Group, err error) { | |
| 88 | if s.Name == "" { | |
| 89 | return g, errors.New("missing Name field") | |
| 90 | } | |
| 91 | ||
| 117 | 92 | var url strings.Builder |
| 118 | 93 | if folderid[0] > 0 { |
| 119 | 94 | page-- |
| @@ -137,3 +112,36 @@ func (s Group) Gallery(page int, folderid ...int) (g Group) { | ||
| 137 | 112 | ujson(url.String(), &g.Content) |
| 138 | 113 | return |
| 139 | 114 | } |
| 115 | ||
| 116 | type GroupAbout struct { | |
| 117 | FoundatedAt timeStamp `json:"foundationTs"` | |
| 118 | Description Text | |
| 119 | } | |
| 120 | type GroupAdmins struct { | |
| 121 | Results []struct { | |
| 122 | TypeId int | |
| 123 | User struct { | |
| 124 | Username string | |
| 125 | } | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | type About struct { | |
| 130 | Country, Website, WebsiteLabel, Gender string | |
| 131 | RegDate int64 `json:"deviantFor"` | |
| 132 | Description Text `json:"textContent"` | |
| 133 | ||
| 134 | SocialLinks []struct { | |
| 135 | Value string | |
| 136 | } | |
| 137 | Interests []struct { | |
| 138 | Label, Value string | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | type users struct { | |
| 143 | About About | |
| 144 | CoverDeviation struct { | |
| 145 | Deviation Deviation `json:"coverDeviation"` | |
| 146 | } | |
| 147 | } | |
util.go +14 −16
| @@ -9,7 +9,7 @@ import ( | ||
| 9 | 9 | ) |
| 10 | 10 | |
| 11 | 11 | // функция для высера ошибки в stderr |
| 12 | func err(txt error) { | |
| 12 | func try(txt error) { | |
| 13 | 13 | if txt != nil { |
| 14 | 14 | println(txt.Error()) |
| 15 | 15 | } |
| @@ -17,11 +17,9 @@ func err(txt error) { | ||
| 17 | 17 | |
| 18 | 18 | // сокращение для вызова щенка и парсинга жсона |
| 19 | 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) | |
| 20 | input, err := puppy(data) | |
| 21 | try(err) | |
| 22 | try(json.Unmarshal([]byte(input), output)) | |
| 25 | 23 | } |
| 26 | 24 | |
| 27 | 25 | /* REQUEST SECTION */ |
| @@ -34,32 +32,32 @@ type reqrt struct { | ||
| 34 | 32 | } |
| 35 | 33 | |
| 36 | 34 | // функция для совершения запроса |
| 35 | var UserAgent string | |
| 36 | ||
| 37 | 37 | func request(uri string, other ...string) reqrt { |
| 38 | 38 | var r reqrt |
| 39 | 39 | |
| 40 | 40 | // создаём новый запрос |
| 41 | 41 | cli := &http.Client{} |
| 42 | 42 | req, e := http.NewRequest("GET", uri, nil) |
| 43 | err(e) | |
| 43 | try(e) | |
| 44 | 44 | |
| 45 | 45 | req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0") |
| 46 | 46 | |
| 47 | 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 | } | |
| 48 | if UserAgent != "" { | |
| 49 | req.Header.Set("User-Agent", UserAgent) | |
| 50 | } | |
| 51 | if len(other) != 0 { | |
| 52 | req.Header.Set("Cookie", other[0]) | |
| 55 | 53 | } |
| 56 | 54 | |
| 57 | 55 | resp, e := cli.Do(req) |
| 58 | err(e) | |
| 56 | try(e) | |
| 59 | 57 | defer resp.Body.Close() |
| 60 | 58 | |
| 61 | 59 | body, e := io.ReadAll(resp.Body) |
| 62 | err(e) | |
| 60 | try(e) | |
| 63 | 61 | |
| 64 | 62 | // заполняем структуру |
| 65 | 63 | r.Body = string(body) |