Skip to content

Commit b35b2fe

Browse files
committed
perf: 表情原图插件本地化处理
1 parent d5cc1db commit b35b2fe

File tree

5 files changed

+156
-29
lines changed

5 files changed

+156
-29
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/yqchilde/wxbot
33
go 1.19
44

55
require (
6+
github.com/gabriel-vasile/mimetype v1.4.1
67
github.com/gin-gonic/gin v1.9.0
78
github.com/glebarez/sqlite v1.7.0
89
github.com/go-co-op/gocron v1.18.0

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
6666
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
6767
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
6868
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
69+
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
70+
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
6971
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
7072
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
7173
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
@@ -359,6 +361,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
359361
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
360362
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
361363
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
364+
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
362365
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
363366
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
364367
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -419,18 +422,21 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
419422
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
420423
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
421424
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
425+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
422426
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
423427
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
424428
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
425429
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
426430
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
427431
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
432+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
428433
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
429434
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
430435
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
431436
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
432437
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
433438
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
439+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
434440
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
435441
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
436442
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

plugins/memepicture/img.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package memepicture
2+
3+
import (
4+
"image"
5+
"image/color"
6+
"image/gif"
7+
"image/png"
8+
"os"
9+
)
10+
11+
// 将gif图片转换为png图片
12+
func gif2Png(src, dst string) error {
13+
// 读入gif图片
14+
srcFile, err := os.Open(src)
15+
if err != nil {
16+
return err
17+
}
18+
defer srcFile.Close()
19+
20+
// 解码gif图片
21+
gifImg, err := gif.DecodeAll(srcFile)
22+
if err != nil {
23+
return err
24+
}
25+
26+
// 创建一张新的png图片
27+
pngImg := image.NewRGBA(image.Rect(0, 0, gifImg.Config.Width, gifImg.Config.Height))
28+
for y := pngImg.Bounds().Min.Y; y < pngImg.Bounds().Max.Y; y++ {
29+
for x := pngImg.Bounds().Min.X; x < pngImg.Bounds().Max.X; x++ {
30+
pngImg.Set(x, y, color.RGBA{})
31+
}
32+
}
33+
34+
// 将gif图片中第一帧的像素值复制到png图片中
35+
for y := 0; y < gifImg.Config.Height; y++ {
36+
for x := 0; x < gifImg.Config.Width; x++ {
37+
pngImg.Set(x, y, gifImg.Image[0].At(x, y))
38+
}
39+
}
40+
41+
// 保存png图片
42+
dstFile, err := os.Create(dst)
43+
if err != nil {
44+
return err
45+
}
46+
defer dstFile.Close()
47+
if err := png.Encode(dstFile, pngImg); err != nil {
48+
return err
49+
}
50+
return nil
51+
}

plugins/memepicture/main.go

Lines changed: 97 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,119 @@
11
package memepicture
22

33
import (
4-
"encoding/base64"
4+
"errors"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"net/url"
9+
"os"
510
"strings"
11+
"time"
612

7-
"github.com/imroc/req/v3"
13+
"github.com/gabriel-vasile/mimetype"
814

915
"github.com/yqchilde/wxbot/engine/control"
16+
"github.com/yqchilde/wxbot/engine/pkg/log"
17+
"github.com/yqchilde/wxbot/engine/pkg/utils"
1018
"github.com/yqchilde/wxbot/engine/robot"
1119
)
1220

1321
func init() {
1422
engine := control.Register("memepicture", &control.Options{
15-
Alias: "表情原图",
16-
Help: "输入 {表情原图} => 30s内发送表情获取表情原图",
23+
Alias: "表情原图",
24+
Help: "输入 {表情原图} => 30s内发送表情获取表情原图",
25+
DataFolder: "memepicture",
1726
})
1827

1928
engine.OnFullMatch("表情原图", robot.MustMemePicture).SetBlock(true).Handle(func(ctx *robot.Ctx) {
20-
url := ctx.State["image_url"].(string)
21-
if strings.HasPrefix(url[:20], "http") {
22-
url = base64.StdEncoding.EncodeToString([]byte(url))
23-
var resp apiResponse
24-
if err := req.C().Post("https://bot.yqqy.top/api/thumbnail").
25-
SetFormData(map[string]string{
26-
"type": "url",
27-
"image": url,
28-
}).Do().Into(&resp); err == nil {
29-
ctx.ReplyShareLink("快来下载你要的表情原图", "打开后长按图片可保存到本地哦", "https://bot.yqqy.top/api/thumbnail?image="+resp.Data.Thumbnail, "https://bot.yqqy.top/api/direct?image="+resp.Data.Original)
29+
imageUrl := ctx.State["image_url"].(string)
30+
host := ctx.Bot.GetConfig().ServerAddress
31+
if host == "" {
32+
ctx.ReplyTextAndAt("请先配置在配置文件中配置serverAddress")
33+
return
34+
}
35+
36+
switch ctx.Bot.GetConfig().Framework.Name {
37+
case "千寻", "qianxun":
38+
original, thumbnail, err := featureImage(imageUrl, "", engine.GetCacheFolder())
39+
if err != nil {
40+
log.Errorf("获取表情原图失败: %v", err)
41+
ctx.ReplyTextAndAt("获取表情原图失败")
42+
return
3043
}
31-
} else {
32-
var resp apiResponse
33-
if err := req.C().Post("https://bot.yqqy.top/api/thumbnail").
34-
SetFormData(map[string]string{
35-
"type": "base64",
36-
"image": url,
37-
}).Do().Into(&resp); err == nil {
38-
ctx.ReplyShareLink("快来下载你要的表情原图", "打开后长按图片可保存到本地哦", "https://bot.yqqy.top/api/thumbnail?image="+resp.Data.Thumbnail, "https://bot.yqqy.top/api/direct?image="+resp.Data.Original)
44+
thumbnail = fmt.Sprintf("%s/wxbot/static?path=%s", host, url.QueryEscape(thumbnail))
45+
original = fmt.Sprintf("%s/wxbot/static?path=%s", host, url.QueryEscape(original))
46+
jumpUrl := fmt.Sprintf("%s/memepicture?img=%s", host, original)
47+
ctx.ReplyShareLink("快来下载你要的表情原图", "打开后长按图片可保存到本地哦", thumbnail, jumpUrl)
48+
case "VLW", "vlw":
49+
original, thumbnail, err := featureImage("", imageUrl, engine.GetCacheFolder())
50+
if err != nil {
51+
log.Errorf("获取表情原图失败: %v", err)
52+
ctx.ReplyTextAndAt("获取表情原图失败")
53+
return
3954
}
55+
thumbnail = fmt.Sprintf("%s/wxbot/static?path=%s", host, url.QueryEscape(thumbnail))
56+
original = fmt.Sprintf("%s/wxbot/static?path=%s", host, url.QueryEscape(original))
57+
jumpUrl := fmt.Sprintf("%s/memepicture?img=%s", host, original)
58+
ctx.ReplyShareLink("快来下载你要的表情原图", "打开后长按图片可保存到本地哦", thumbnail, jumpUrl)
59+
default:
60+
ctx.ReplyTextAndAt("暂不支持该框架,请联系管理员")
4061
}
4162
})
4263
}
4364

44-
type apiResponse struct {
45-
Code int `json:"code"`
46-
Data struct {
47-
Thumbnail string `json:"thumbnail"`
48-
Original string `json:"original"`
49-
} `json:"data"`
65+
func featureImage(url, b64, cacheDir string) (original, thumbnail string, err error) {
66+
tmpFile := "/tmp/" + time.Now().Local().Format("20060102150405") + ".png"
67+
if url == "" && b64 == "" {
68+
return "", "", errors.New("url和b64不能同时为空")
69+
}
70+
if url != "" && b64 != "" {
71+
return "", "", errors.New("url和b64不能同时存在")
72+
}
73+
if url != "" {
74+
// 下载图片
75+
resp, err := http.Get(url)
76+
if err != nil {
77+
return
78+
}
79+
defer resp.Body.Close()
80+
81+
// 保存图片
82+
file, err := os.Create(tmpFile)
83+
if err != nil {
84+
os.Remove(tmpFile)
85+
return
86+
}
87+
defer file.Close()
88+
if _, err := io.Copy(file, resp.Body); err != nil {
89+
os.Remove(tmpFile)
90+
return
91+
}
92+
}
93+
if b64 != "" {
94+
// 保存图片
95+
if err := utils.Base64ToImage(b64, tmpFile); err != nil {
96+
os.Remove(tmpFile)
97+
return
98+
}
99+
}
100+
101+
// 检测图片类型
102+
mime, err := mimetype.DetectFile(tmpFile)
103+
if err != nil {
104+
os.Remove(tmpFile)
105+
return
106+
}
107+
original = fmt.Sprintf("%s/%s%s", cacheDir, time.Now().Local().Format("20060102150405"), mime.Extension())
108+
os.Rename(tmpFile, original)
109+
110+
// 生成缩略图
111+
if mime.Extension() == ".gif" { // gif
112+
thumbnail = strings.ReplaceAll(original, "origin", "thumb")
113+
thumbnail = strings.ReplaceAll(original, ".gif", ".png")
114+
return original, thumbnail, gif2Png(original, thumbnail)
115+
} else {
116+
thumbnail = original
117+
return original, thumbnail, nil
118+
}
50119
}

web

Submodule web updated from 33e87eb to 888d406

0 commit comments

Comments
 (0)