diff --git a/go.mod b/go.mod index f6659f2..f4d210f 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/duo/matrix-qq go 1.20 require ( - github.com/Mrs4s/MiraiGo v0.0.0-20230223093528-5a89d8a9bff2 + github.com/Mrs4s/MiraiGo v0.0.0-20230307172929-fee6c23736c1 github.com/antchfx/xmlquery v1.3.15 github.com/gabriel-vasile/mimetype v1.4.1 github.com/lib/pq v1.10.7 github.com/mattn/go-sqlite3 v1.14.16 github.com/tidwall/gjson v1.14.4 github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea - golang.org/x/image v0.5.0 + golang.org/x/image v0.6.0 maunium.net/go/maulogger/v2 v2.4.1 maunium.net/go/mautrix v0.14.0 ) @@ -36,11 +36,11 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/yuin/goldmark v1.5.4 // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect modernc.org/libc v1.22.3 // indirect diff --git a/go.sum b/go.sum index 24465b5..d681589 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/Mrs4s/MiraiGo v0.0.0-20230223093528-5a89d8a9bff2 h1:dXIxqB73+Ur2S6kUB5M6eIh/vTUAL4FWfIXvSPb2dus= -github.com/Mrs4s/MiraiGo v0.0.0-20230223093528-5a89d8a9bff2/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0= +github.com/Mrs4s/MiraiGo v0.0.0-20230307172929-fee6c23736c1 h1:A3sBjwY0pSbXlN0PVDVwTZHrTNtT/tIyc9ZftockquI= +github.com/Mrs4s/MiraiGo v0.0.0-20230307172929-fee6c23736c1/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0= github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d h1:/Xuj3fIiMY2ls1TwvPKmaqQrtJsPY+c9s+0lOScVHd8= github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA= github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA= @@ -80,18 +80,20 @@ github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= -golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= +golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= @@ -107,20 +109,24 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/command.go b/internal/command.go index 2252bc3..a160fce 100644 --- a/internal/command.go +++ b/internal/command.go @@ -407,7 +407,7 @@ var cmdDeleteSession = &commands.FullHandler{ } func fnDeleteSession(ce *WrappedCommandEvent) { - if ce.User.Token == nil && ce.User.Client == nil { + if ce.User.Token == nil && ce.User.Device == "" && ce.User.Client == nil { ce.Reply("Nothing to purge: no session information stored and no active connection.") return } diff --git a/internal/portal.go b/internal/portal.go index b4631c5..9c0ec38 100644 --- a/internal/portal.go +++ b/internal/portal.go @@ -588,7 +588,13 @@ func (p *Portal) renderQQImage(url string, intent *appservice.IntentAPI) string p.log.Warnfln("failed to upload media: %w", err) return "[图片]" } - return fmt.Sprintf("![%s](%s)", mime.String(), content.URL) + + var mediaURL = content.URL + if content.File != nil { + mediaURL = content.File.URL + } + + return fmt.Sprintf("![%s](%s)", mime.String(), mediaURL) } func (p *Portal) renderQQLightApp(elem *message.LightAppElement, intent *appservice.IntentAPI) string { @@ -1743,6 +1749,17 @@ func (p *Portal) uploadMedia(intent *appservice.IntentAPI, data []byte, content content.Info.Width, content.Info.Height = cfg.Width, cfg.Height } + // This is a hack for bad clients like Element iOS that require a thumbnail (https://github.com/vector-im/element-ios/issues/4004) + if strings.HasPrefix(content.Info.MimeType, "image/") && content.Info.ThumbnailInfo == nil { + infoCopy := *content.Info + content.Info.ThumbnailInfo = &infoCopy + if content.File != nil { + content.Info.ThumbnailFile = file + } else { + content.Info.ThumbnailURL = content.URL + } + } + return nil } diff --git a/internal/user.go b/internal/user.go index f75a261..b6effe6 100644 --- a/internal/user.go +++ b/internal/user.go @@ -1,8 +1,10 @@ package internal import ( + "bytes" "crypto/md5" "encoding/base64" + "encoding/hex" "errors" "fmt" "math/rand" @@ -13,9 +15,11 @@ import ( "github.com/duo/matrix-qq/internal/database" "github.com/duo/matrix-qq/internal/types" + "github.com/tidwall/gjson" "github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/message" + "github.com/Mrs4s/MiraiGo/warpper" "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/bridge" @@ -40,6 +44,10 @@ var ( deviceLock sync.Mutex ) +func init() { + warpper.DandelionEnergy = energy +} + type resyncQueueItem struct { portal *Portal puppet *Puppet @@ -348,17 +356,15 @@ func (u *User) createClient() { device := &client.DeviceInfo{} if len(u.Device) == 0 { device = client.GenRandomDevice() - setClientProtocol(device, u.bridge.Config.QQ.Protocol) - u.Device = string(device.ToJson()) } else { if err := device.ReadJson([]byte(u.Device)); err != nil { u.log.Warnfln("failed to load device information: %v", err) device = client.GenRandomDevice() - setClientProtocol(device, u.bridge.Config.QQ.Protocol) - u.Device = string(device.ToJson()) u.Token = nil } } + setClientProtocol(device, u.bridge.Config.QQ.Protocol) + u.Device = string(device.ToJson()) u.Client = client.NewClientEmpty() u.Client.UseDevice(device) @@ -591,11 +597,12 @@ func (u *User) DeleteConnection() { } func (u *User) DeleteSession() { + u.Device = "" u.Token = nil if !u.UID.IsEmpty() { u.UID = types.EmptyUID - u.Update() } + u.Update() } func (u *User) IsLoggedIn() bool { @@ -1033,3 +1040,21 @@ func setClientProtocol(device *client.DeviceInfo, protocol int) { device.Protocol = client.AndroidPad } } + +func energy(id string, salt []byte) []byte { + // temporary solution + response, err := Request{ + Method: http.MethodPost, + URL: "https://captcha.go-cqhttp.org/sdk/dandelion/energy", + Header: map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, + Body: bytes.NewReader([]byte(fmt.Sprintf("id=%s&salt=%s", id, hex.EncodeToString(salt)))), + }.Bytes() + if err != nil { + return nil + } + sign, err := hex.DecodeString(gjson.GetBytes(response, "result").String()) + if err != nil { + return nil + } + return sign +} diff --git a/internal/util.go b/internal/util.go index 89a1fe7..68d27d4 100644 --- a/internal/util.go +++ b/internal/util.go @@ -3,6 +3,7 @@ package internal import ( "bytes" "compress/gzip" + "errors" "io" "math/rand" "net/http" @@ -333,3 +334,56 @@ func RandomStringRange(length int, str string) string { } return sb.String() } + +// Request is a file download request +type Request struct { + Method string + URL string + Header map[string]string + Limit int64 + Body io.Reader +} + +func (r Request) do() (*http.Response, error) { + if r.Method == "" { + r.Method = http.MethodGet + } + req, err := http.NewRequest(r.Method, r.URL, r.Body) + if err != nil { + return nil, err + } + + req.Header["User-Agent"] = []string{UserAgent} + for k, v := range r.Header { + req.Header.Set(k, v) + } + + return httpClient.Do(req) +} + +func (r Request) body() (io.ReadCloser, error) { + resp, err := r.do() + if err != nil { + return nil, err + } + + limit := r.Limit // check file size limit + if limit > 0 && resp.ContentLength > limit { + _ = resp.Body.Close() + return nil, errors.New("oversize") + } + + if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") { + return NewGzipReadCloser(resp.Body) + } + return resp.Body, err +} + +func (r Request) Bytes() ([]byte, error) { + rd, err := r.body() + if err != nil { + return nil, err + } + defer rd.Close() + return io.ReadAll(rd) +} diff --git a/main.go b/main.go index 47c455e..55b5f27 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ func main() { Name: "matrix-qq", URL: "https://github.com/duo/matrix-qq", Description: "A Matrix-QQ puppeting bridge.", - Version: "0.1.4", + Version: "0.1.5", ProtocolName: "QQ", CryptoPickleKey: "github.com/duo/matrix-qq",