mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-18 20:50:12 +08:00
feat: 添加 rsshub (#1232)
This commit is contained in:
parent
a74bcb869a
commit
aafac8d6fa
11
README.md
11
README.md
@ -1284,6 +1284,17 @@ print("run[CQ:image,file="+j["img"]+"]")
|
||||
|
||||
- [x] 打劫[对方Q号|@对方QQ]
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>RSSHub</summary>
|
||||
|
||||
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub"`
|
||||
|
||||
- [x] 添加rsshub订阅-/bookfere/weekly
|
||||
- [x] 删除rsshub订阅-/bookfere/weekly
|
||||
- [x] 查看rsshub订阅列表
|
||||
- [x] rsshub同步 (使用job执行定时任务------记录在"@every 10m"触发的指令)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>在线代码运行</summary>
|
||||
|
||||
7
go.mod
7
go.mod
@ -38,6 +38,7 @@ require (
|
||||
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5
|
||||
github.com/lithammer/fuzzysearch v1.1.8
|
||||
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5
|
||||
github.com/mmcdole/gofeed v1.3.0
|
||||
github.com/mroth/weightedrand v1.0.0
|
||||
github.com/notnil/chess v1.10.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
@ -53,8 +54,10 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
|
||||
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/antchfx/xpath v1.3.3 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/oto/v3 v3.3.2 // indirect
|
||||
@ -71,10 +74,14 @@ require (
|
||||
github.com/jfreymuth/oggvorbis v1.0.5 // indirect
|
||||
github.com/jfreymuth/vorbis v1.0.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
|
||||
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
|
||||
20
go.sum
20
go.sum
@ -19,6 +19,8 @@ github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69Pfr
|
||||
github.com/FloatTech/zbputils v1.7.2-0.20251002080916-b554b7039913 h1:uGexKAPL26sAWGemyHbfkjYyzFItMsbI8EREBLSZ/sU=
|
||||
github.com/FloatTech/zbputils v1.7.2-0.20251002080916-b554b7039913/go.mod h1:mNvv0+wCou042n/3QkK23WmbayNctT5wgkKC3A6nbmM=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
|
||||
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
|
||||
github.com/RomiChan/websocket v1.4.3-0.20251002072000-d3eb41798438 h1:I0bdwHZ+2DY45b39xPoTD2u+Z8zhvBuu9aZfjMZeiZM=
|
||||
@ -30,6 +32,8 @@ github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0
|
||||
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
|
||||
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ=
|
||||
github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM=
|
||||
github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs=
|
||||
@ -107,6 +111,7 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4er
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopxl/beep/v2 v2.1.1 h1:6FYIYMm2qPAdWkjX+7xwKrViS1x0Po5kDMdRkq8NVbU=
|
||||
@ -123,6 +128,8 @@ github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jozsefsallai/gophersauce v1.0.1 h1:BA3ovtQRrAb1qYU9JoRLbDHpxnDunlNcEkEfhCvDDCM=
|
||||
github.com/jozsefsallai/gophersauce v1.0.1/go.mod h1:YVEI7djliMTmZ1Vh01YPF8bUHi+oKhe3yXgKf1T49vg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY=
|
||||
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5/go.mod h1:c9+VS9GaommgIOzNWb5ze4lYwfT8BZ2UDyGiuQTT7yc=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
@ -141,6 +148,15 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
|
||||
github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
|
||||
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk=
|
||||
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E=
|
||||
github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
@ -172,6 +188,7 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
@ -224,6 +241,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
@ -248,6 +266,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -273,6 +292,7 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
|
||||
1
main.go
1
main.go
@ -136,6 +136,7 @@ import (
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/realcugan" // realcugan清晰术
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/robbery" // 打劫群友的ATRI币
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub" // RSSHub订阅姬
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/score" // 分数
|
||||
|
||||
134
plugin/rsshub/domain/job.go
Normal file
134
plugin/rsshub/domain/job.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Package domain rsshub领域逻辑
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mmcdole/gofeed"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// syncRss 同步所有频道
|
||||
// 返回:更新的频道&订阅信息 map[int64]*RssClientView
|
||||
// 1. 获取所有频道
|
||||
// 2. 遍历所有频道,检查频道是否更新
|
||||
// 3. 如果更新,获取更新的内容,但是返回的数据
|
||||
func (repo *RssDomain) syncRss(ctx context.Context) (updated map[int64]*RssClientView, err error) {
|
||||
updated = make(map[int64]*RssClientView)
|
||||
// 获取所有频道
|
||||
sources, err := repo.storage.GetSources(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 遍历所有源,获取每个channel对应的rss内容
|
||||
rssView := make([]*RssClientView, len(sources))
|
||||
for i, channel := range sources {
|
||||
var feed *gofeed.Feed
|
||||
// 从site获取rss内容
|
||||
feed, err = repo.rssHubClient.FetchFeed(channel.RssHubFeedPath)
|
||||
// 如果获取失败,则跳过
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub syncRss] fetch path(%+v) error: %v", channel.RssHubFeedPath, err)
|
||||
continue
|
||||
}
|
||||
rv := convertFeedToRssView(0, channel.RssHubFeedPath, feed)
|
||||
rssView[i] = rv
|
||||
}
|
||||
// 检查频道是否更新
|
||||
for _, cv := range rssView {
|
||||
if cv == nil {
|
||||
continue
|
||||
}
|
||||
var needUpdate bool
|
||||
needUpdate, err = repo.checkSourceNeedUpdate(ctx, cv.Source)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub syncRss] checkSourceNeedUpdate error: %v", err)
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
// 保存
|
||||
logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %+v, need update(real): %v", cv.Source, needUpdate)
|
||||
// 如果需要更新,更新channel 和 content
|
||||
if needUpdate {
|
||||
err = repo.storage.UpsertSource(ctx, cv.Source)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert source error: %v", err)
|
||||
}
|
||||
}
|
||||
var updateChannelView = &RssClientView{Source: cv.Source, Contents: []*RssContent{}}
|
||||
err = repo.processContentsUpdate(ctx, cv, updateChannelView)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub syncRss] processContentsUpdate error: %v", err)
|
||||
continue
|
||||
}
|
||||
if len(updateChannelView.Contents) == 0 {
|
||||
logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %s, no new content", cv.Source.RssHubFeedPath)
|
||||
continue
|
||||
}
|
||||
updateChannelView.Sort()
|
||||
updated[updateChannelView.Source.ID] = updateChannelView
|
||||
logrus.WithContext(ctx).Debugf("[rsshub syncRss] cv %s, new contents: %v", cv.Source.RssHubFeedPath, len(updateChannelView.Contents))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// checkSourceNeedUpdate 检查频道是否需要更新
|
||||
func (repo *RssDomain) checkSourceNeedUpdate(ctx context.Context, source *RssSource) (needUpdate bool, err error) {
|
||||
var sourceInDB *RssSource
|
||||
sourceInDB, err = repo.storage.GetSourceByRssHubFeedLink(ctx, source.RssHubFeedPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if sourceInDB == nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub syncRss] source not found: %v", source.RssHubFeedPath)
|
||||
return
|
||||
}
|
||||
source.ID = sourceInDB.ID
|
||||
// 检查是否需要更新到db
|
||||
if sourceInDB.IfNeedUpdate(source) {
|
||||
needUpdate = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// processContentsUpdate 处理内容(s)更新
|
||||
func (repo *RssDomain) processContentsUpdate(ctx context.Context, cv *RssClientView, updateChannelView *RssClientView) error {
|
||||
var err error
|
||||
for _, content := range cv.Contents {
|
||||
if content == nil {
|
||||
continue
|
||||
}
|
||||
content.RssSourceID = cv.Source.ID
|
||||
var existed bool
|
||||
existed, err = repo.processContentItemUpdate(ctx, content)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err)
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
if !existed {
|
||||
updateChannelView.Contents = append(updateChannelView.Contents, content)
|
||||
logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %s, add new content: %v", cv.Source.RssHubFeedPath, content.Title)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// processContentItemUpdate 处理单个内容更新
|
||||
func (repo *RssDomain) processContentItemUpdate(ctx context.Context, content *RssContent) (existed bool, err error) {
|
||||
existed, err = repo.storage.IsContentHashIDExist(ctx, content.HashID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 不需要更新&不需要发送
|
||||
if existed {
|
||||
return
|
||||
}
|
||||
// 保存
|
||||
err = repo.storage.UpsertContent(ctx, content)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
118
plugin/rsshub/domain/model.go
Normal file
118
plugin/rsshub/domain/model.go
Normal file
@ -0,0 +1,118 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ======== RSS ========[START]
|
||||
|
||||
func genHashForFeedItem(link, guid string) string {
|
||||
h := fnv.New32()
|
||||
// 分三次写入数据:link、分隔符、guid
|
||||
_, _ = h.Write([]byte(link))
|
||||
_, _ = h.Write([]byte("||"))
|
||||
_, _ = h.Write([]byte(guid))
|
||||
|
||||
encoded := hex.EncodeToString(h.Sum(nil))
|
||||
return encoded
|
||||
}
|
||||
|
||||
// RssClientView 频道视图
|
||||
type RssClientView struct {
|
||||
Source *RssSource
|
||||
Contents []*RssContent
|
||||
}
|
||||
|
||||
// ======== RSS ========[END]
|
||||
|
||||
// ======== DB ========[START]
|
||||
|
||||
const (
|
||||
tableNameRssSource = "rss_source"
|
||||
tableNameRssContent = "rss_content"
|
||||
tableNameRssSubscribe = "rss_subscribe"
|
||||
)
|
||||
|
||||
// RssSource RSS频道
|
||||
type RssSource struct {
|
||||
// Id 自增id
|
||||
ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"`
|
||||
// RssHubFeedPath 频道路由 用于区分rss_hub 不同的频道 例如: `/bangumi/tv/calendar/today`
|
||||
RssHubFeedPath string `gorm:"column:rss_hub_feed_path;not null;unique;" json:"rss_hub_feed_path"`
|
||||
// Title 频道标题
|
||||
Title string `gorm:"column:title" json:"title"`
|
||||
// ChannelDesc 频道描述
|
||||
ChannelDesc string `gorm:"column:channel_desc" json:"channel_desc"`
|
||||
// ImageURL 频道图片
|
||||
ImageURL string `gorm:"column:image_url" json:"image_url"`
|
||||
// Link 频道链接
|
||||
Link string `gorm:"column:link" json:"link"`
|
||||
// UpdatedParsed RSS页面更新时间
|
||||
UpdatedParsed time.Time `gorm:"column:updated_parsed" json:"updated_parsed"`
|
||||
// Mtime update time
|
||||
Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"`
|
||||
}
|
||||
|
||||
// TableName ...
|
||||
func (RssSource) TableName() string {
|
||||
return tableNameRssSource
|
||||
}
|
||||
|
||||
// IfNeedUpdate ...
|
||||
func (r RssSource) IfNeedUpdate(cmp *RssSource) bool {
|
||||
if r.Link != cmp.Link {
|
||||
return false
|
||||
}
|
||||
return r.UpdatedParsed.Unix() < cmp.UpdatedParsed.Unix()
|
||||
}
|
||||
|
||||
// RssContent 订阅的RSS频道的推送信息
|
||||
type RssContent struct {
|
||||
// Id 自增id
|
||||
ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"`
|
||||
HashID string `gorm:"column:hash_id;unique" json:"hash_id"`
|
||||
RssSourceID int64 `gorm:"column:rss_source_id;not null" json:"rss_source_id"`
|
||||
Title string `gorm:"column:title" json:"title"`
|
||||
Description string `gorm:"column:description" json:"description"`
|
||||
Link string `gorm:"column:link" json:"link"`
|
||||
Date time.Time `gorm:"column:date" json:"date"`
|
||||
Author string `gorm:"column:author" json:"author"`
|
||||
Thumbnail string `gorm:"column:thumbnail" json:"thumbnail"`
|
||||
Content string `gorm:"column:content" json:"content"`
|
||||
// Mtime update time
|
||||
Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"`
|
||||
}
|
||||
|
||||
// TableName ...
|
||||
func (RssContent) TableName() string {
|
||||
return tableNameRssContent
|
||||
}
|
||||
|
||||
// Sort ... order by Date desc
|
||||
func (r *RssClientView) Sort() {
|
||||
sort.Slice(r.Contents, func(i, j int) bool {
|
||||
return r.Contents[i].Date.Unix() > r.Contents[j].Date.Unix()
|
||||
})
|
||||
}
|
||||
|
||||
// RssSubscribe 订阅关系表:群组-RSS频道
|
||||
type RssSubscribe struct {
|
||||
// Id 自增id
|
||||
ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"`
|
||||
// 订阅群组
|
||||
GroupID int64 `gorm:"column:group_id;not null;uniqueIndex:uk_sid_gid"`
|
||||
// 订阅频道
|
||||
RssSourceID int64 `gorm:"column:rss_source_id;not null;uniqueIndex:uk_sid_gid"`
|
||||
// Mtime update time
|
||||
Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"`
|
||||
}
|
||||
|
||||
// TableName ...
|
||||
func (RssSubscribe) TableName() string {
|
||||
return tableNameRssSubscribe
|
||||
}
|
||||
|
||||
// ======== DB ========[END]
|
||||
101
plugin/rsshub/domain/rawFeed.go
Normal file
101
plugin/rsshub/domain/rawFeed.go
Normal file
@ -0,0 +1,101 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/FloatTech/floatbox/web"
|
||||
"github.com/mmcdole/gofeed"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// RSSHubMirrors RSSHub镜像站地址列表,第一个为默认地址
|
||||
rssHubMirrors = []string{
|
||||
"https://rsshub.rssforever.com",
|
||||
"https://rss.injahow.cn",
|
||||
}
|
||||
)
|
||||
|
||||
// RssHubClient rss hub client (http)
|
||||
type RssHubClient struct {
|
||||
*http.Client
|
||||
}
|
||||
|
||||
// FetchFeed 获取rss feed信息
|
||||
func (c *RssHubClient) FetchFeed(path string) (feed *gofeed.Feed, err error) {
|
||||
var data []byte
|
||||
// 遍历 rssHubMirrors,直到获取成功
|
||||
for _, mirror := range rssHubMirrors {
|
||||
data, err = web.RequestDataWith(c.Client, mirror+path, "GET", "", web.RandUA(), nil)
|
||||
if err == nil && len(data) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("[rsshub FetchFeed] fetch feed error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
logrus.Errorf("[rsshub FetchFeed] fetch feed error: data is empty")
|
||||
return nil, errors.New("feed data is empty")
|
||||
}
|
||||
feed, err = gofeed.NewParser().Parse(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertFeedToRssView(channelID int64, cPath string, feed *gofeed.Feed) (view *RssClientView) {
|
||||
var imgURL string
|
||||
if feed.Image != nil {
|
||||
imgURL = feed.Image.URL
|
||||
}
|
||||
view = &RssClientView{
|
||||
Source: &RssSource{
|
||||
ID: channelID,
|
||||
RssHubFeedPath: cPath,
|
||||
Title: feed.Title,
|
||||
ChannelDesc: feed.Description,
|
||||
ImageURL: imgURL,
|
||||
Link: feed.Link,
|
||||
UpdatedParsed: *(feed.UpdatedParsed),
|
||||
Mtime: time.Now(),
|
||||
},
|
||||
// 不用定长,后面可能会过滤一些元素再append
|
||||
Contents: []*RssContent{},
|
||||
}
|
||||
// convert feed items to rss content
|
||||
for _, item := range feed.Items {
|
||||
if item.Link == "" || item.Title == "" {
|
||||
continue
|
||||
}
|
||||
var thumbnail string
|
||||
if item.Image != nil {
|
||||
thumbnail = item.Image.URL
|
||||
}
|
||||
var publishedParsed = item.PublishedParsed
|
||||
if publishedParsed == nil {
|
||||
publishedParsed = &time.Time{}
|
||||
}
|
||||
aus, _ := json.Marshal(item.Authors)
|
||||
view.Contents = append(view.Contents, &RssContent{
|
||||
ID: 0,
|
||||
HashID: genHashForFeedItem(item.Link, item.GUID),
|
||||
RssSourceID: channelID,
|
||||
Title: item.Title,
|
||||
Description: item.Description,
|
||||
Link: item.Link,
|
||||
Date: *publishedParsed,
|
||||
Author: string(aus),
|
||||
Thumbnail: thumbnail,
|
||||
Content: item.Content,
|
||||
Mtime: time.Now(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
178
plugin/rsshub/domain/rssHub.go
Normal file
178
plugin/rsshub/domain/rssHub.go
Normal file
@ -0,0 +1,178 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// RssDomain RssRepo定义
|
||||
type RssDomain struct {
|
||||
storage *repoStorage
|
||||
rssHubClient *RssHubClient
|
||||
}
|
||||
|
||||
// NewRssDomain 新建RssDomain,调用方保证单例模式
|
||||
func NewRssDomain(dbPath string) (*RssDomain, error) {
|
||||
return newRssDomain(dbPath)
|
||||
}
|
||||
|
||||
func newRssDomain(dbPath string) (*RssDomain, error) {
|
||||
if _, err := os.Stat(dbPath); err != nil || os.IsNotExist(err) {
|
||||
// 生成文件
|
||||
f, err := os.Create(dbPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
}
|
||||
orm, err := gorm.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err)
|
||||
panic(err)
|
||||
}
|
||||
repo := &RssDomain{
|
||||
storage: &repoStorage{orm: orm},
|
||||
rssHubClient: &RssHubClient{Client: http.DefaultClient},
|
||||
}
|
||||
err = repo.storage.initDB()
|
||||
if err != nil {
|
||||
logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err)
|
||||
panic(err)
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// Subscribe QQ群订阅Rss频道
|
||||
func (repo *RssDomain) Subscribe(ctx context.Context, gid int64, feedPath string) (
|
||||
rv *RssClientView, isChannelExisted, isSubExisted bool, err error) {
|
||||
// 验证
|
||||
feed, err := repo.rssHubClient.FetchFeed(feedPath)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] add source error: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.WithContext(ctx).Infof("[rsshub Subscribe] try get source success: %v", len(feed.Title))
|
||||
// 新建source结构体
|
||||
rv = convertFeedToRssView(0, feedPath, feed)
|
||||
feedChannel, err := repo.storage.GetSourceByRssHubFeedLink(ctx, feedPath)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query source by feedPath error: %v", err)
|
||||
return
|
||||
}
|
||||
// 如果已经存在
|
||||
if feedChannel != nil {
|
||||
logrus.WithContext(ctx).Warningf("[rsshub Subscribe] source existed: %v", feedChannel)
|
||||
isChannelExisted = true
|
||||
} else {
|
||||
// 不存在的情况,要把更新时间置空,保证下一次同步时能够更新
|
||||
rv.Source.UpdatedParsed = time.Time{}
|
||||
}
|
||||
// 保存
|
||||
err = repo.storage.UpsertSource(ctx, rv.Source)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] save source error: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.Infof("[rsshub Subscribe] save/update source success %v", rv.Source.ID)
|
||||
// 添加群号到订阅
|
||||
subscribe, err := repo.storage.GetSubscribeByID(ctx, gid, rv.Source.ID)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query subscribe error: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.WithContext(ctx).Infof("[rsshub Subscribe] query subscribe success: %v", subscribe)
|
||||
// 如果已经存在,直接返回
|
||||
if subscribe != nil {
|
||||
isSubExisted = true
|
||||
logrus.WithContext(ctx).Infof("[rsshub Subscribe] subscribe existed: %v", subscribe)
|
||||
return
|
||||
}
|
||||
// 如果不存在,保存
|
||||
err = repo.storage.CreateSubscribe(ctx, gid, rv.Source.ID)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] save subscribe error: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.WithContext(ctx).Infof("[rsshub Subscribe] success: %v", len(rv.Contents))
|
||||
return
|
||||
}
|
||||
|
||||
// Unsubscribe 群组取消订阅
|
||||
func (repo *RssDomain) Unsubscribe(ctx context.Context, gid int64, feedPath string) (err error) {
|
||||
existedSubscribes, ifExisted, err := repo.storage.GetIfExistedSubscribe(ctx, gid, feedPath)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query sub by route error: %v", err)
|
||||
return errors.New("数据库错误")
|
||||
}
|
||||
logrus.WithContext(ctx).Infof("[rsshub Subscribe] query source by route success: %v", existedSubscribes)
|
||||
// 如果不存在订阅关系,直接返回
|
||||
if !ifExisted || existedSubscribes == nil {
|
||||
logrus.WithContext(ctx).Infof("[rsshub Subscribe] source existed: %v", ifExisted)
|
||||
return errors.New("频道不存在")
|
||||
}
|
||||
err = repo.storage.DeleteSubscribe(ctx, existedSubscribes.ID)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] delete source error: %v", err)
|
||||
return errors.New("删除失败")
|
||||
}
|
||||
// 查询是否还有群订阅这个频道
|
||||
subscribesNeedsToDel, err := repo.storage.GetSubscribesBySource(ctx, feedPath)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query source by route error: %v", err)
|
||||
return
|
||||
}
|
||||
// 没有群订阅的时候,把频道删除
|
||||
if len(subscribesNeedsToDel) == 0 {
|
||||
err = repo.storage.DeleteSource(ctx, existedSubscribes.RssSourceID)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] delete source error: %v", err)
|
||||
return errors.New("清除频道信息失败")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetSubscribedChannelsByGroupID 获取群对应的订阅的频道信息
|
||||
func (repo *RssDomain) GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) ([]*RssClientView, error) {
|
||||
channels, err := repo.storage.GetSubscribedChannelsByGroupID(ctx, gid)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub GetSubscribedChannelsByGroupID] GetSubscribedChannelsByGroupID error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
rv := make([]*RssClientView, len(channels))
|
||||
logrus.WithContext(ctx).Infof("[rsshub GetSubscribedChannelsByGroupID] query subscribe success: %v", len(channels))
|
||||
for i, cn := range channels {
|
||||
rv[i] = &RssClientView{
|
||||
Source: cn,
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// Sync 同步任务,按照群组订阅情况做好map切片
|
||||
func (repo *RssDomain) Sync(ctx context.Context) (groupView map[int64][]*RssClientView, err error) {
|
||||
groupView = make(map[int64][]*RssClientView)
|
||||
// 获取所有Rss频道
|
||||
// 获取所有频道
|
||||
updatedViews, err := repo.syncRss(ctx)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Sync] sync rss feed error: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.WithContext(ctx).Infof("[rsshub Sync] updated channels: %v", len(updatedViews))
|
||||
subscribes, err := repo.storage.GetSubscribes(ctx)
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub Sync] get subscribes error: %v", err)
|
||||
return
|
||||
}
|
||||
for _, subscribe := range subscribes {
|
||||
groupView[subscribe.GroupID] = append(groupView[subscribe.GroupID], updatedViews[subscribe.RssSourceID])
|
||||
}
|
||||
return
|
||||
}
|
||||
105
plugin/rsshub/domain/rssHub_test.go
Normal file
105
plugin/rsshub/domain/rssHub_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewRssDomain(t *testing.T) {
|
||||
dm, err := newRssDomain("rsshub.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
if dm == nil {
|
||||
t.Fatal("domain is nil")
|
||||
}
|
||||
}
|
||||
|
||||
//var testRssHubChannelUrl = "https://rsshub.rssforever.com/bangumi/tv/calendar/today"
|
||||
|
||||
var dm, _ = newRssDomain("rsshub.db")
|
||||
|
||||
func TestSub(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
feedLink string
|
||||
gid int64
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
feedLink: "/bangumi/tv/calendar/today",
|
||||
gid: 99,
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
feedLink: "/go-weekly",
|
||||
gid: 99,
|
||||
},
|
||||
{
|
||||
name: "test3",
|
||||
feedLink: "/go-weekly",
|
||||
gid: 123,
|
||||
},
|
||||
{
|
||||
name: "test3",
|
||||
feedLink: "/go-weekly",
|
||||
gid: 321,
|
||||
},
|
||||
{
|
||||
name: "test3",
|
||||
feedLink: "/go-weekly",
|
||||
gid: 4123,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
channel, ifExisted, ifSub, err := dm.Subscribe(ctx, tc.gid, tc.feedLink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
t.Logf("[TEST] add sub res: %+v,%+v,%+v\n", channel, ifExisted, ifSub)
|
||||
res, ext, err := dm.storage.GetIfExistedSubscribe(ctx, tc.gid, tc.feedLink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
t.Logf("[TEST] if exist: %+v,%+v", res, ext)
|
||||
channels, err := dm.GetSubscribedChannelsByGroupID(ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
t.Logf("[TEST] 2 channels: %+v", channels)
|
||||
// del
|
||||
//err = dm.Unsubscribe(ctx, tc.gid, tc.feedLink)
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
// return
|
||||
//}
|
||||
//res, ext, err = dm.storage.GetIfExistedSubscribe(ctx, tc.gid, tc.feedLink)
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
// return
|
||||
//}
|
||||
//t.Logf("[TEST] after del: %+v,%+v", res, ext)
|
||||
//if res != nil || ext {
|
||||
// t.Fatal("delete failed")
|
||||
//}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SyncFeed(t *testing.T) {
|
||||
feed, err := dm.Sync(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
rs, _ := json.Marshal(feed)
|
||||
t.Logf("[Test] feed: %+v", string(rs))
|
||||
}
|
||||
271
plugin/rsshub/domain/storageRepo.go
Normal file
271
plugin/rsshub/domain/storageRepo.go
Normal file
@ -0,0 +1,271 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// repoStorage db struct for rss
|
||||
type repoStorage struct {
|
||||
orm *gorm.DB
|
||||
}
|
||||
|
||||
// initDB ...
|
||||
func (s *repoStorage) initDB() (err error) {
|
||||
err = s.orm.AutoMigrate(&RssSource{}, &RssContent{}, &RssSubscribe{}).Error
|
||||
if err != nil {
|
||||
logrus.Errorf("[rsshub initDB] error: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
// s.orm.LogMode(true)
|
||||
}
|
||||
|
||||
// GetSubscribesBySource Impl
|
||||
func (s *repoStorage) GetSubscribesBySource(ctx context.Context, feedPath string) ([]*RssSubscribe, error) {
|
||||
logrus.WithContext(ctx).Infof("[rsshub GetSubscribesBySource] feedPath: %s", feedPath)
|
||||
rs := make([]*RssSubscribe, 0)
|
||||
err := s.orm.Model(&RssSubscribe{}).Joins(fmt.Sprintf("%s left join %s on %s.rss_source_id=%s.id", tableNameRssSubscribe, tableNameRssSource, tableNameRssSubscribe, tableNameRssSource)).
|
||||
Where("rss_source.rss_hub_feed_path = ?", feedPath).Select("rss_subscribe.*").Find(&rs).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub GetSubscribesBySource] error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// GetIfExistedSubscribe Impl
|
||||
func (s *repoStorage) GetIfExistedSubscribe(ctx context.Context, gid int64, feedPath string) (*RssSubscribe, bool, error) {
|
||||
rs := RssSubscribe{}
|
||||
|
||||
err := s.orm.Table(tableNameRssSubscribe).
|
||||
Select("rss_subscribe.id, rss_subscribe.group_id, rss_subscribe.rss_source_id, rss_subscribe.mtime").
|
||||
Joins(fmt.Sprintf("INNER JOIN %s ON %s.rss_source_id=%s.id",
|
||||
tableNameRssSource, tableNameRssSubscribe, tableNameRssSource)).
|
||||
Where("rss_source.rss_hub_feed_path = ? AND rss_subscribe.group_id = ?", feedPath, gid).Scan(&rs).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub GetIfExistedSubscribe] error: %v", err)
|
||||
return nil, false, err
|
||||
}
|
||||
if rs.ID == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
return &rs, true, nil
|
||||
}
|
||||
|
||||
// ==================== RepoSource ==================== [Start]
|
||||
|
||||
// UpsertSource Impl
|
||||
func (s *repoStorage) UpsertSource(ctx context.Context, source *RssSource) (err error) {
|
||||
// Update columns to default value on `id` conflict
|
||||
querySource := &RssSource{RssHubFeedPath: source.RssHubFeedPath}
|
||||
err = s.orm.First(querySource, "rss_hub_feed_path = ?", querySource.RssHubFeedPath).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = s.orm.Create(source).Omit("id").Error
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] add source error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
source.ID = querySource.ID
|
||||
logrus.WithContext(ctx).Infof("[rsshub] update source: %+v", source.UpdatedParsed)
|
||||
err = s.orm.Model(&source).Where(&RssSource{ID: source.ID}).
|
||||
Updates(&RssSource{
|
||||
Title: source.Title,
|
||||
ChannelDesc: source.ChannelDesc,
|
||||
ImageURL: source.ImageURL,
|
||||
Link: source.Link,
|
||||
UpdatedParsed: source.UpdatedParsed,
|
||||
Mtime: time.Now(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] update source error: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.Println("[rsshub] add source success: ", source.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSources Impl
|
||||
func (s *repoStorage) GetSources(ctx context.Context) (sources []RssSource, err error) {
|
||||
sources = []RssSource{}
|
||||
err = s.orm.Find(&sources, "id > 0").Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("source not found")
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] get sources error: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.WithContext(ctx).Infof("[rsshub] get sources success: %d", len(sources))
|
||||
return
|
||||
}
|
||||
|
||||
// GetSourceByRssHubFeedLink Impl
|
||||
func (s *repoStorage) GetSourceByRssHubFeedLink(ctx context.Context, rssHubFeedLink string) (source *RssSource, err error) {
|
||||
source = &RssSource{RssHubFeedPath: rssHubFeedLink}
|
||||
err = s.orm.Take(source, source).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] get source error: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteSource Impl
|
||||
func (s *repoStorage) DeleteSource(ctx context.Context, fID int64) (err error) {
|
||||
err = s.orm.Delete(&RssSource{}, "id = ?", fID).Error
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSource: %v", err)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("source not found")
|
||||
}
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ==================== RepoSource ==================== [End]
|
||||
|
||||
// ==================== RepoContent ==================== [Start]
|
||||
|
||||
// UpsertContent Impl
|
||||
func (s *repoStorage) UpsertContent(ctx context.Context, content *RssContent) (err error) {
|
||||
// check params
|
||||
if content == nil {
|
||||
err = errors.New("content is nil")
|
||||
return
|
||||
}
|
||||
// check params.RssHubFeedPath and params.HashID
|
||||
if content.RssSourceID < 0 || content.HashID == "" || content.Title == "" {
|
||||
err = errors.New("content.RssSourceID or content.HashID or content.Title is empty")
|
||||
return
|
||||
}
|
||||
err = s.orm.Create(content).Omit("id").Error
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.UpsertContent: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteSourceContents Impl
|
||||
func (s *repoStorage) DeleteSourceContents(ctx context.Context, channelID int64) (rows int64, err error) {
|
||||
err = s.orm.Delete(&RssSubscribe{}).Where(&RssSubscribe{RssSourceID: channelID}).Error
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSourceContents: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsContentHashIDExist Impl
|
||||
func (s *repoStorage) IsContentHashIDExist(ctx context.Context, hashID string) (bool, error) {
|
||||
wanted := &RssContent{HashID: hashID}
|
||||
err := s.orm.Take(wanted, wanted).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.IsContentHashIDExist: %v", err)
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ==================== RepoContent ==================== [End]
|
||||
|
||||
// ==================== RepoSubscribe ==================== [Start]
|
||||
|
||||
// CreateSubscribe Impl
|
||||
func (s *repoStorage) CreateSubscribe(ctx context.Context, gid, rssSourceID int64) (err error) {
|
||||
// check subscribe
|
||||
if rssSourceID < 0 || gid == 0 {
|
||||
err = errors.New("gid or rssSourceID is empty")
|
||||
return
|
||||
}
|
||||
err = s.orm.Create(&RssSubscribe{GroupID: gid, RssSourceID: rssSourceID}).Omit("id").Error
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.CreateSubscribe: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteSubscribe Impl
|
||||
func (s *repoStorage) DeleteSubscribe(ctx context.Context, subscribeID int64) (err error) {
|
||||
err = s.orm.Delete(&RssSubscribe{}, "id = ?", subscribeID).Error
|
||||
if err != nil {
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSubscribe error: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetSubscribeByID Impl
|
||||
func (s *repoStorage) GetSubscribeByID(ctx context.Context, gid int64, subscribeID int64) (res *RssSubscribe, err error) {
|
||||
res = &RssSubscribe{}
|
||||
err = s.orm.First(res, &RssSubscribe{GroupID: gid, RssSourceID: subscribeID}).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribeByID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetSubscribedChannelsByGroupID Impl
|
||||
func (s *repoStorage) GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) (res []*RssSource, err error) {
|
||||
res = make([]*RssSource, 0)
|
||||
err = s.orm.Model(&RssSource{}).
|
||||
Joins(fmt.Sprintf("join %s on rss_source_id=%s.id", tableNameRssSubscribe, tableNameRssSource)).Where("rss_subscribe.group_id = ?", gid).
|
||||
Select("rss_source.*").
|
||||
Find(&res).
|
||||
Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribedChannelsByGroupID: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetSubscribes Impl
|
||||
func (s *repoStorage) GetSubscribes(ctx context.Context) (res []*RssSubscribe, err error) {
|
||||
res = make([]*RssSubscribe, 0)
|
||||
err = s.orm.Find(&res).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribes: %v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ==================== RepoSubscribe ==================== [End]
|
||||
152
plugin/rsshub/main.go
Normal file
152
plugin/rsshub/main.go
Normal file
@ -0,0 +1,152 @@
|
||||
// Package rsshub rss_hub订阅插件
|
||||
package rsshub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
ctrl "github.com/FloatTech/zbpctrl"
|
||||
"github.com/FloatTech/zbputils/control"
|
||||
zbpCtxExt "github.com/FloatTech/zbputils/ctxext"
|
||||
"github.com/sirupsen/logrus"
|
||||
zero "github.com/wdvxdr1123/ZeroBot"
|
||||
"github.com/wdvxdr1123/ZeroBot/message"
|
||||
|
||||
"github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub/domain"
|
||||
)
|
||||
|
||||
// 初始化 repo
|
||||
var (
|
||||
rssRepo *domain.RssDomain
|
||||
initErr error
|
||||
regexpForSQL = regexp.MustCompile(`[\^<>\[\]%&\*\(\)\{\}\|\=]|(union\s+select|update\s+|delete\s+|drop\s+|truncate\s+|insert\s+|exec\s+|declare\s+)`)
|
||||
)
|
||||
|
||||
var (
|
||||
// 注册插件
|
||||
engine = control.Register("rsshub", &ctrl.Options[*zero.Ctx]{
|
||||
// 默认不启动
|
||||
DisableOnDefault: false,
|
||||
Brief: "rsshub订阅姬",
|
||||
// 详细帮助
|
||||
Help: "rsshub订阅姬desu~ \n" +
|
||||
"支持的详细订阅列表文档可见:\n" +
|
||||
"https://rsshub.netlify.app/zh/ \n" +
|
||||
"- 添加rsshub订阅-/bookfere/weekly \n" +
|
||||
"- 删除rsshub订阅-/bookfere/weekly \n" +
|
||||
"- 查看rsshub订阅列表 \n" +
|
||||
"- rsshub同步 \n" +
|
||||
"Tips: 定时刷新rsshub订阅信息需要配合job一起使用, 全局只需要设置一个, 无视响应状态推送, 下为例子\n" +
|
||||
"记录在\"@every 10m\"触发的指令)\n" +
|
||||
"rsshub同步",
|
||||
// 插件数据存储路径
|
||||
PrivateDataFolder: "rsshub",
|
||||
OnEnable: func(ctx *zero.Ctx) {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬现在启动了哦"))
|
||||
},
|
||||
OnDisable: func(ctx *zero.Ctx) {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬现在关闭了哦"))
|
||||
},
|
||||
}).ApplySingle(zbpCtxExt.DefaultSingle)
|
||||
)
|
||||
|
||||
// init 命令路由
|
||||
func init() {
|
||||
rssRepo, initErr = domain.NewRssDomain(engine.DataFolder() + "rsshub.db")
|
||||
if initErr != nil {
|
||||
logrus.Errorln("rsshub订阅姬:初始化失败", initErr)
|
||||
panic(initErr)
|
||||
}
|
||||
engine.OnFullMatch("rsshub同步", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||||
// 群组-频道推送视图 map[群组]推送内容数组
|
||||
groupToFeedsMap, err := rssRepo.Sync(context.Background())
|
||||
if err != nil {
|
||||
logrus.Errorln("rsshub同步失败", err)
|
||||
ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text("rsshub同步失败", err))
|
||||
return
|
||||
}
|
||||
// 没有更新的[群组-频道推送视图]则不推送
|
||||
if len(groupToFeedsMap) == 0 {
|
||||
logrus.Info("rsshub未发现更新")
|
||||
return
|
||||
}
|
||||
sendRssUpdateMsg(ctx, groupToFeedsMap)
|
||||
})
|
||||
// 添加订阅
|
||||
engine.OnPrefix("添加rsshub订阅-", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||||
routeStr := ctx.State["args"].(string)
|
||||
input := regexpForSQL.ReplaceAllString(routeStr, "")
|
||||
logrus.Debugf("添加rsshub订阅:raw(%s), replaced(%s)", routeStr, input)
|
||||
rv, _, isSubExisted, err := rssRepo.Subscribe(context.Background(), ctx.Event.GroupID, input)
|
||||
if err != nil {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬:添加失败", err.Error()))
|
||||
return
|
||||
}
|
||||
if isSubExisted {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬:已存在,更新成功"))
|
||||
} else {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬:添加成功\n", rv.Source.Title))
|
||||
}
|
||||
// 添加成功,发送订阅源快照
|
||||
msg, err := newRssDetailsMsg(ctx, rv)
|
||||
if len(msg) == 0 || err != nil {
|
||||
ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text("rsshub推送错误", err))
|
||||
return
|
||||
}
|
||||
if id := ctx.Send(msg).ID(); id == 0 {
|
||||
ctx.SendChain(message.Text("ERROR: 发送订阅源快照失败,可能被风控了"))
|
||||
}
|
||||
})
|
||||
engine.OnPrefix("删除rsshub订阅-", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||||
routeStr := ctx.State["args"].(string)
|
||||
input := regexpForSQL.ReplaceAllString(routeStr, "")
|
||||
logrus.Debugf("删除rsshub订阅:raw(%s), replaced(%s)", routeStr, input)
|
||||
err := rssRepo.Unsubscribe(context.Background(), ctx.Event.GroupID, input)
|
||||
if err != nil {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬:删除失败 ", err.Error()))
|
||||
return
|
||||
}
|
||||
ctx.SendChain(message.Text(fmt.Sprintf("rsshub订阅姬:删除%s成功", input)))
|
||||
})
|
||||
engine.OnFullMatch("查看rsshub订阅列表", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||||
rv, err := rssRepo.GetSubscribedChannelsByGroupID(context.Background(), ctx.Event.GroupID)
|
||||
if err != nil {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬:查询失败 ", err.Error()))
|
||||
return
|
||||
}
|
||||
// 添加成功,发送订阅源信息
|
||||
msg, err := newRssSourcesMsg(ctx, rv)
|
||||
if err != nil {
|
||||
ctx.SendChain(message.Text("rsshub订阅姬:查询失败 ", err.Error()))
|
||||
return
|
||||
}
|
||||
if len(msg) == 0 {
|
||||
ctx.SendChain(message.Text("ん? 没有订阅的频道哦~"))
|
||||
return
|
||||
}
|
||||
ctx.SendChain(msg...)
|
||||
})
|
||||
}
|
||||
|
||||
// sendRssUpdateMsg 发送Rss更新消息
|
||||
func sendRssUpdateMsg(ctx *zero.Ctx, groupToFeedsMap map[int64][]*domain.RssClientView) {
|
||||
for groupID, views := range groupToFeedsMap {
|
||||
logrus.Infof("rsshub插件在群 %d 触发推送检查", groupID)
|
||||
for _, view := range views {
|
||||
if view == nil || len(view.Contents) == 0 {
|
||||
continue
|
||||
}
|
||||
msg, err := newRssDetailsMsg(ctx, view)
|
||||
if len(msg) == 0 || err != nil {
|
||||
ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text(rssHubPushErrMsg, err))
|
||||
continue
|
||||
}
|
||||
logrus.Infof("rsshub插件在群 %d 开始推送 %s", groupID, view.Source.Title)
|
||||
ctx.SendGroupMessage(groupID, message.Text(fmt.Sprintf("%s\n该rsshub频道下有更新了哦~", view.Source.Title)))
|
||||
if res := ctx.SendGroupForwardMessage(groupID, msg); !res.Exists() {
|
||||
ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text(rssHubPushErrMsg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
100
plugin/rsshub/view.go
Normal file
100
plugin/rsshub/view.go
Normal file
@ -0,0 +1,100 @@
|
||||
package rsshub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/FloatTech/floatbox/binary"
|
||||
"github.com/FloatTech/zbputils/img/text"
|
||||
"github.com/sirupsen/logrus"
|
||||
zero "github.com/wdvxdr1123/ZeroBot"
|
||||
"github.com/wdvxdr1123/ZeroBot/message"
|
||||
|
||||
"github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
rssHubPushErrMsg = "RssHub推送错误"
|
||||
)
|
||||
|
||||
// formatRssViewToMessagesSlice 格式化RssClientView为消息切片
|
||||
func formatRssViewToMessagesSlice(view *domain.RssClientView) ([]message.Message, error) {
|
||||
// 取前20条
|
||||
cts := view.Contents
|
||||
if len(cts) > 20 {
|
||||
cts = cts[:20]
|
||||
}
|
||||
// 2n+1条消息
|
||||
fv := make([]message.Message, len(cts)*2+1)
|
||||
// 订阅源头图
|
||||
toastPic, err := text.RenderToBase64(fmt.Sprintf("%s\n\n\n%s\n\n\n更新时间:%v\n\n\n",
|
||||
view.Source.Title, view.Source.Link, view.Source.UpdatedParsed.Local().Format(time.DateTime)),
|
||||
text.SakuraFontFile, 1200, 40)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fv[0] = message.Message{message.Image("base64://" + binary.BytesToString(toastPic))}
|
||||
// 元素信息
|
||||
for idx, item := range cts {
|
||||
contentStr := fmt.Sprintf("%s\n\n\n", item.Title)
|
||||
// Date为空时不显示
|
||||
if !item.Date.IsZero() {
|
||||
contentStr += fmt.Sprintf("更新时间:\n%v\n", item.Date.Local().Format(time.DateTime))
|
||||
}
|
||||
var content []byte
|
||||
content, err = text.RenderToBase64(contentStr, text.SakuraFontFile, 1200, 40)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("RssHub订阅姬渲染图片失败")
|
||||
continue
|
||||
}
|
||||
itemMessagePic := message.Message{message.Image("base64://" + binary.BytesToString(content))}
|
||||
fv[2*idx+1] = itemMessagePic
|
||||
fv[2*idx+2] = message.Message{message.Text(item.Link)}
|
||||
}
|
||||
return fv, nil
|
||||
}
|
||||
|
||||
// newRssSourcesMsg Rss订阅源列表
|
||||
func newRssSourcesMsg(ctx *zero.Ctx, view []*domain.RssClientView) (message.Message, error) {
|
||||
var msgSlice []message.Message
|
||||
// 生成消息
|
||||
for _, v := range view {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
item, err := formatRssViewToMessagesSlice(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgSlice = append(msgSlice, item...)
|
||||
}
|
||||
// 伪造一个发送者为RssHub订阅姬的消息节点
|
||||
msg := make(message.Message, len(msgSlice))
|
||||
for i, item := range msgSlice {
|
||||
msg[i] = fakeSenderForwardNode(ctx.Event.SelfID, item...)
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// newRssDetailsMsg Rss订阅源详情(包含文章信息列表)
|
||||
func newRssDetailsMsg(ctx *zero.Ctx, view *domain.RssClientView) (message.Message, error) {
|
||||
// 生成消息
|
||||
msgSlice, err := formatRssViewToMessagesSlice(view)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 伪造一个发送者为RssHub订阅姬的消息节点
|
||||
msg := make(message.Message, len(msgSlice))
|
||||
for i, item := range msgSlice {
|
||||
msg[i] = fakeSenderForwardNode(ctx.Event.SelfID, item...)
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// fakeSenderForwardNode 伪造一个发送者为RssHub订阅姬的消息节点
|
||||
func fakeSenderForwardNode(userID int64, msgs ...message.Segment) message.Segment {
|
||||
return message.CustomNode(
|
||||
"RssHub订阅姬",
|
||||
userID,
|
||||
msgs)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user