aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json5
-rw-r--r--comments.go79
-rw-r--r--deviantion.go117
-rw-r--r--examples/post.go25
-rw-r--r--go.mod3
-rw-r--r--implementation.md4
-rw-r--r--misc.go198
-rw-r--r--todo.md3
8 files changed, 434 insertions, 0 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..636b799
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "go.toolsEnvVars": {
+ "GOROOT": ""
+ }
+} \ No newline at end of file
diff --git a/comments.go b/comments.go
new file mode 100644
index 0000000..f6a86c6
--- /dev/null
+++ b/comments.go
@@ -0,0 +1,79 @@
+package devianter
+
+import (
+ "encoding/json"
+ "strconv"
+ "strings"
+)
+
+type comments struct {
+ Cursor string
+ PrevOffset int
+ HasMore, HasLess bool
+
+ Total int
+ Thread []struct {
+ CommentId, ParentId, Replies, Likes int
+ Posted time
+ IsAuthorHighlited bool
+
+ Desctiption string
+
+ // упрощение структуры с комментами
+ Comment string
+
+ TextContent struct {
+ Html struct {
+ Markup string
+ }
+ }
+
+ User struct {
+ Username string
+ IsBanned bool
+ }
+ }
+}
+
+// функция для обработки комментариев поста, пользователя, группы и многого другого
+func Comments(
+ postid string,
+ cursor string,
+ page int,
+ typ int, // 1 - комментарии поста; 4 - комментарии на стене группы или пользователя
+) (cmmts comments) {
+ for x := 0; x <= page; x++ {
+ ujson(
+ "dashared/comments/thread?typeid="+strconv.Itoa(typ)+
+ "&itemid="+postid+"&maxdepth=1000&order=newest"+
+ "&limit=50&cursor="+strings.ReplaceAll(cursor, "+", `%2B`),
+ &cmmts,
+ )
+
+ cursor = cmmts.Cursor
+
+ // парсинг json внутри json
+ for i := 0; i < len(cmmts.Thread); i++ {
+ m, l := cmmts.Thread[i].TextContent.Html.Markup, len(cmmts.Thread[i].TextContent.Html.Markup)
+ cmmts.Thread[i].Comment = m
+
+ // если начало строки {, а конец }, то срабатывает этот иф
+ if m[0] == 123 && m[l-1] == 125 {
+ var content struct {
+ Blocks []struct {
+ Text string
+ }
+ }
+
+ e := json.Unmarshal([]byte(m), &content)
+ err(e)
+
+ for _, a := range content.Blocks {
+ cmmts.Thread[i].Comment = a.Text
+ }
+ }
+ }
+ }
+
+ return
+}
diff --git a/deviantion.go b/deviantion.go
new file mode 100644
index 0000000..1f6644c
--- /dev/null
+++ b/deviantion.go
@@ -0,0 +1,117 @@
+package devianter
+
+import (
+ "encoding/json"
+ "strconv"
+ timelib "time"
+)
+
+// хрень для парсинга времени публикации
+type time struct {
+ timelib.Time
+}
+
+func (t *time) UnmarshalJSON(b []byte) (err error) {
+ if b[0] == '"' && b[len(b)-1] == '"' {
+ b = b[1 : len(b)-1]
+ }
+ t.Time, err = timelib.Parse("2006-01-02T15:04:05-0700", string(b))
+ return
+}
+
+// самая главная структура для поста
+type deviantion struct {
+ Title, Url, License string
+ PublishedTime time
+ IsMature, IsAiGenerated, IsDailyDeviation bool
+ Author struct {
+ Username string
+ }
+ Stats struct {
+ Favourites, Views, Downloads int
+ }
+ Media media
+ Extended struct {
+ Tags []struct {
+ Name string
+ }
+ DescriptionText text
+ RelatedContent []struct {
+ Deviations []deviantion
+ }
+ }
+ TextContent text
+}
+
+// её выпердыши
+type media struct {
+ BaseUri string
+ Token []string
+ Types []struct {
+ T string
+ H, W int
+ }
+}
+
+type text struct {
+ Html struct {
+ Markup, Type string
+ }
+}
+
+type Deviantion struct {
+ Deviation deviantion
+ Comments struct {
+ Total int
+ Cursor string
+ }
+
+ ParsedComments []struct {
+ Author string
+ Posted time
+ Replies, Likes int
+ }
+
+ IMG, Desctiption string
+ Recomendations []deviantion
+}
+
+// для работы функции нужно ID поста и имя пользователя.
+func Deviation(id string, user string) Deviantion {
+ var st Deviantion
+ ujson(
+ "dadeviation/init?deviationid="+id+"&username="+user+"&type=art&include_session=false&expand=deviation.related&preload=true",
+ &st,
+ )
+
+ // преобразование урла в правильный
+ for _, t := range st.Deviation.Media.Types {
+ if m := st.Deviation.Media; t.T == "fullview" {
+ if len(m.Token) > 0 {
+ st.IMG = m.BaseUri + "?token="
+ } else {
+ st.IMG = m.BaseUri + "/v1/fill/w_" + strconv.Itoa(t.W) + ",h_" + strconv.Itoa(t.H) + "/" + id + "_" + user + ".gif" + "?token="
+ }
+ st.IMG += m.Token[0]
+ }
+ }
+
+ // базовая обработка описания
+ txt := st.Deviation.TextContent.Html.Markup
+ if len(txt) > 0 && txt[0:1] == "{" {
+ var description struct {
+ Blocks []struct {
+ Text string
+ }
+ }
+
+ json.Unmarshal([]byte(txt), &description)
+ for _, a := range description.Blocks {
+ txt = a.Text
+ }
+ }
+
+ st.Desctiption = txt
+
+ return st
+}
diff --git a/examples/post.go b/examples/post.go
new file mode 100644
index 0000000..2789c46
--- /dev/null
+++ b/examples/post.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "git.macaw.me/skunky/devianter"
+)
+
+func main() {
+ id := "973578309"
+ d := devianter.Deviation(id, "Thrumyeye")
+
+ println("Post Name:", d.Deviation.Title, "\nIMG url:", d.IMG)
+
+ c := devianter.Comments(id, "", 0, 1)
+ println("\n\nPost Comments:", c.Total)
+
+ for _, a := range c.Thread {
+ if a.User.IsBanned {
+ a.User.Username += " [v bane]"
+ }
+ println(a.User.Username+":", a.Comment)
+ }
+
+ search := devianter.Search("skunk", "2", 'a')
+ println(search.Total, search.Pages)
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..c17f7e8
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module git.macaw.me/skunky/devianter
+
+go 1.18
diff --git a/implementation.md b/implementation.md
new file mode 100644
index 0000000..3f1c58a
--- /dev/null
+++ b/implementation.md
@@ -0,0 +1,4 @@
+- avatars
+- emojis
+- deviantions
+- comments
diff --git a/misc.go b/misc.go
new file mode 100644
index 0000000..c847e44
--- /dev/null
+++ b/misc.go
@@ -0,0 +1,198 @@
+package devianter
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "math"
+ "net/http"
+ "strings"
+)
+
+// функция для высера ошибки в stderr
+func err(txt error) {
+ if txt != nil {
+ println(txt.Error())
+ }
+}
+
+// сокращение для вызова щенка и парсинга жсона
+func ujson(data string, output any) {
+ input, e := puppy(data)
+ err(e)
+
+ eee := json.Unmarshal([]byte(input), output)
+ err(eee)
+}
+
+/* REQUEST SECTION */
+// структура для ответа сервера
+type reqrt struct {
+ Body string
+ Status int
+ Cookies []*http.Cookie
+ Headers http.Header
+}
+
+// функция для совершения запроса
+func request(uri string, other ...string) reqrt {
+ var r reqrt
+
+ // создаём новый запрос
+ cli := &http.Client{}
+ req, e := http.NewRequest("GET", uri, nil)
+ err(e)
+
+ req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0")
+
+ // куки и UA-шник
+ if other != nil {
+ for num, rng := range other {
+ switch num {
+ case 1:
+ req.Header.Set("User-Agent", rng)
+ case 0:
+ req.Header.Set("Cookie", rng)
+ }
+ }
+ }
+
+ resp, e := cli.Do(req)
+ err(e)
+ defer resp.Body.Close()
+
+ body, e := io.ReadAll(resp.Body)
+ err(e)
+
+ // заполняем структуру
+ r.Body = string(body)
+ r.Cookies = resp.Cookies()
+ r.Headers = resp.Header
+ r.Status = resp.StatusCode
+
+ return r
+}
+
+/* AVATARS AND EMOJIS */
+func AEmedia(name string, t rune) (string, error) {
+ // список всех возможных расширений
+ var extensions = [3]string{
+ ".jpg",
+ ".png",
+ ".gif",
+ }
+ // надо
+ name = strings.ToLower(name)
+
+ // построение ссылок. билдер потому что он быстрее обычного сложения строк.
+ var b strings.Builder
+ switch t {
+ case 'a':
+ b.WriteString("https://a.deviantart.net/avatars-big/")
+ b.WriteString(name[:1])
+ b.WriteString("/")
+ b.WriteString(name[1:2])
+ b.WriteString("/")
+ case 'e':
+ b.WriteString("https://e.deviantart.net/emoticons/")
+ b.WriteString(name[:1])
+ b.WriteString("/")
+ default:
+ log.Fatalln("Invalid type.\n- 'a' -- avatar;\n- 'e' -- emoji.")
+ }
+ b.WriteString(name)
+
+ // проверка ссылки на доступность
+ for x := 0; x < len(extensions); x++ {
+ req := request(b.String() + extensions[x])
+ if req.Status == 200 {
+ return req.Body, nil
+ }
+ }
+
+ return "", errors.New("User not exists")
+}
+
+/* SEARCH */
+type search struct {
+ Total int `json:"estTotal"`
+ Pages int // only for 'a' scope.
+ Deviations []deviantion
+}
+
+func Search(query, page string, scope rune) (ss search) {
+ var url strings.Builder
+
+ // о5 построение ссылок.
+ switch scope {
+ case 'a':
+ url.WriteString("dabrowse/search/all?q=")
+ case 't':
+ url.WriteString("dabrowse/networkbar/tag/deviations?tag=")
+ default:
+ log.Fatalln("Invalid type.\n- 'a' -- all;\n- 't' -- tag.")
+ }
+
+ url.WriteString(query)
+ url.WriteString("&page=")
+ url.WriteString(page)
+
+ ujson(url.String(), &ss)
+
+ // расчёт, сколько всего страниц по запросу. без токена 417 страниц - максимум
+ for x := 0; x < int(math.Round(float64(ss.Total/25))); x++ {
+ if x <= 417 {
+ ss.Pages = x
+ }
+ }
+
+ return
+}
+
+/* PUPPY aka DeviantArt API */
+func puppy(data string) (string, error) {
+ // получение или обновление токена
+ update := func() (string, string, error) {
+ var cookie string
+ if cookie == "" {
+ req := request("https://www.deviantart.com/_puppy")
+
+ for _, content := range req.Cookies {
+ cookie = content.Raw
+ }
+ }
+
+ req := request("https://www.deviantart.com", cookie)
+ if req.Status != 200 {
+ return "", "", errors.New(req.Body)
+ }
+
+ return cookie, req.Body[strings.Index(req.Body, "window.__CSRF_TOKEN__ = '")+25 : strings.Index(req.Body, "window.__XHR_LOCAL__")-3], nil
+ }
+
+ // использование токена
+ var (
+ cookie, token string
+ )
+ if cookie == "" || token == "" {
+ var e error
+ cookie, token, e = update()
+ if e != nil {
+ return "", e
+ }
+ }
+
+ body := request(
+ fmt.Sprintf("https://www.deviantart.com/_puppy/%s&csrf_token=%s&da_minor_version=20230710", data, token),
+ cookie,
+ )
+
+ // если код ответа не 200, возвращается ошибка
+ if body.Status != 200 {
+ return "", errors.New(body.Body)
+ }
+
+ return body.Body, nil
+}
diff --git a/todo.md b/todo.md
new file mode 100644
index 0000000..0648437
--- /dev/null
+++ b/todo.md
@@ -0,0 +1,3 @@
+- ~~users~~
+- ~~groups~~
+- ~~images in comments~~ \ No newline at end of file