diff --git a/.github/workflows/pull.yml b/.github/workflows/close_pr.yml similarity index 75% rename from .github/workflows/pull.yml rename to .github/workflows/close_pr.yml index af61750c1d..b061da8099 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/close_pr.yml @@ -1,11 +1,14 @@ name: PullLint on: + pull_request: + types: [opened, synchronize, reopened] pull_request_target: types: [assigned, opened, synchronize, reopened] jobs: # This workflow closes invalid PR close-pr: name: closepr + if: ${{ github.event_name == 'pull_request_target' }} # The type of runner that the job will run on runs-on: ubuntu-latest permissions: write-all @@ -20,8 +23,7 @@ jobs: comment: "非法PR. 请`fork`后修改自己的仓库, 而不是向主仓库提交更改. 如果您确信您的PR是为了给主仓库新增功能或修复bug, 请更改默认PR标题. **注意**: 如果您再次触发本提示, 则有可能导致账号被封禁." golangci: - needs: close-pr - if: ${{ !contains(github.event.pull_request.title, '.go') }} + if: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '.go') }} uses: ./.github/workflows/lib_lint.yml with: ref: ${{ github.event.pull_request.head.sha }} @@ -29,13 +31,14 @@ jobs: runmain: needs: golangci - if: ${{ !contains(github.event.pull_request.title, '.go') }} + if: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '.go') }} uses: ./.github/workflows/lib_run.yml with: ref: ${{ github.event.pull_request.head.sha }} build: needs: [runmain] + if: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '.go') }} uses: ./.github/workflows/lib_build.yml with: prefix: "zbp_" diff --git a/.github/workflows/lib_build.yml b/.github/workflows/lib_build.yml index ac59d51bf5..0704c6fab9 100644 --- a/.github/workflows/lib_build.yml +++ b/.github/workflows/lib_build.yml @@ -36,6 +36,7 @@ jobs: - uses: actions/checkout@master with: fetch-depth: 0 + allow-unsafe-pr-checkout: true - name: Setup Go environment uses: actions/setup-go@master diff --git a/.github/workflows/lib_lint.yml b/.github/workflows/lib_lint.yml index 29bfacd181..3200b1eebf 100644 --- a/.github/workflows/lib_lint.yml +++ b/.github/workflows/lib_lint.yml @@ -36,6 +36,7 @@ jobs: ref: ${{ inputs.ref }} fetch-depth: ${{ inputs.fetch-depth }} submodules: ${{ inputs.submodules }} + allow-unsafe-pr-checkout: true - name: Prepare Necessary Runtime Files run: | diff --git a/.github/workflows/lib_run.yml b/.github/workflows/lib_run.yml index 56be97e439..5db5ae5a24 100644 --- a/.github/workflows/lib_run.yml +++ b/.github/workflows/lib_run.yml @@ -38,6 +38,7 @@ jobs: ref: ${{ inputs.ref }} fetch-depth: ${{ inputs.fetch-depth }} submodules: ${{ inputs.submodules }} + allow-unsafe-pr-checkout: true - name: Prepare and Build shell: bash diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000000..8705cf29c8 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,26 @@ +name: PR Check +on: + pull_request: + branches: [ master ] + +jobs: + golangci: + if: ${{ !contains(github.event.pull_request.title, '.go') }} + uses: ./.github/workflows/lib_lint.yml + with: + ref: ${{ github.event.pull_request.head.sha }} + commit_back: false + + runmain: + if: ${{ !contains(github.event.pull_request.title, '.go') }} + uses: ./.github/workflows/lib_run.yml + with: + ref: ${{ github.event.pull_request.head.sha }} + + build: + needs: [golangci, runmain] + if: ${{ !contains(github.event.pull_request.title, '.go') }} + uses: ./.github/workflows/lib_build.yml + with: + prefix: "zbp_" + upload_artifact: false diff --git a/README.md b/README.md index 6154dc7b28..124a1411ea 100644 --- a/README.md +++ b/README.md @@ -1223,6 +1223,32 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 解签 + +
+ Pixiv图片搜索 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/pixiv"` + + - [x] [x张]涩图 [关键词] + + - [x] 每日涩图 + + - [x] [x张]画师[画师的uid] + + - [x] p站搜图[插画pid] + + - [x] 设置p站token [token] (仅私聊,超级用户) + + - [x] 授权此处使用p站18 (仅超级用户) + + - [x] 设置pixiv代理 [http://127.0.0.1:7890] (仅私聊,超级用户) + + - [x] 查看pixiv代理 (仅私聊,超级用户) + + - [x] 清除pixiv代理 (仅私聊,超级用户) + + - 注:[]不用打出来这只是一个占位符,可添加多个关键词每个关键词用空格隔开,默认不发R-18如果要发就加一个R-18关键词 +
抽扑克 diff --git a/go.mod b/go.mod index 713a4087a4..54d1b995f6 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( github.com/fumiama/go-onebot-agent v0.0.0-20260425093914-9c92a03776e3 github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 - github.com/fumiama/imgsz v0.0.4 github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 github.com/fumiama/slowdo v0.0.0-20241001074058-27c4fe5259a4 github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6 @@ -51,9 +50,11 @@ require ( golang.org/x/image v0.38.0 golang.org/x/sys v0.41.0 golang.org/x/text v0.35.0 + gorm.io/datatypes v1.2.7 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect 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 @@ -66,15 +67,18 @@ require ( github.com/fumiama/go-simple-protobuf v0.2.0 // indirect github.com/fumiama/gofastTEA v0.1.3 // indirect github.com/fumiama/gozel v0.0.0-20260329105205-a95fde52433a // indirect + github.com/fumiama/imgsz v0.0.4 // indirect github.com/fumiama/orbyte v0.0.0-20251002065953-3bb358367eb5 // indirect github.com/fumiama/terasu v1.0.2 // indirect github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/gopxl/beep/v2 v2.1.1 // indirect 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/jinzhu/now v1.1.5 // 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 @@ -97,6 +101,8 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/net v0.50.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.5.6 // indirect + gorm.io/gorm v1.30.0 // indirect modernc.org/libc v1.67.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 976028c4ae..7634d8e77f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw= github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y= github.com/FloatTech/AnimeAPI v1.7.1-0.20260408142737-1e9f2594e3cd h1:1L91nA4W2FNCFlvxAT5UfIvmW40zeH3ho4KkHrlAt0c= @@ -100,10 +102,15 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -119,6 +126,14 @@ github.com/gopxl/beep/v2 v2.1.1 h1:6FYIYMm2qPAdWkjX+7xwKrViS1x0Po5kDMdRkq8NVbU= github.com/gopxl/beep/v2 v2.1.1/go.mod h1:ZAm9TGQ9lvpoiFLd4zf5B1IuyxZhgRACMId1XJbaW0E= github.com/guohuiyuan/music-lib v1.0.2-0.20260121020416-53f6cb24629d h1:6Cw52c4JaYvq55yAa9ZgUQeBL6b3ZWErQqkbeMZiAYw= github.com/guohuiyuan/music-lib v1.0.2-0.20260121020416-53f6cb24629d/go.mod h1:D/6kQDwhQFDNZEMjN8y760DQSVYpOGlQXrYzhKz0rCQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jfreymuth/oggvorbis v1.0.5 h1:u+Ck+R0eLSRhgq8WTmffYnrVtSztJcYrl588DM4e3kQ= github.com/jfreymuth/oggvorbis v1.0.5/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE= @@ -127,8 +142,9 @@ github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -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/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/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= @@ -149,8 +165,11 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 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/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= 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= @@ -316,6 +335,19 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk= +gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY= +gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= +gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= +gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= +gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= +gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc= +gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= diff --git a/main.go b/main.go index 4ee2794a68..d2660f0192 100644 --- a/main.go +++ b/main.go @@ -132,6 +132,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nwife" // 本地老婆 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/pig" // 来份猪猪 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/pixiv" // pixiv _ "github.com/FloatTech/ZeroBot-Plugin/plugin/poker" // 抽扑克 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/qzone" // qq空间表白墙 diff --git a/plugin/pixiv/cache/db.go b/plugin/pixiv/cache/db.go new file mode 100644 index 0000000000..5324d00bbd --- /dev/null +++ b/plugin/pixiv/cache/db.go @@ -0,0 +1,221 @@ +// Package cache ... +package cache + +import ( + "errors" + "strings" + "sync" + "time" + + "github.com/FloatTech/AnimeAPI/pixiv/model" + "github.com/jinzhu/gorm" +) + +// DB ... +type DB struct { + *gorm.DB + mu sync.Mutex +} + +// NewDB ... +func NewDB(path string) *DB { + db, err := gorm.Open("sqlite3", path) + if err != nil { + panic(err) + } + if err = db.AutoMigrate(&model.IllustCache{}, &model.SentImage{}, &model.RefreshToken{}, &model.GroupR18Permission{}, &model.PixivProxyConfig{}).Error; err != nil { + panic(err) + } + sqlDB := db.DB() + sqlDB.SetMaxOpenConns(10) + sqlDB.SetMaxIdleConns(5) + sqlDB.SetConnMaxLifetime(time.Hour) + db.LogMode(false) + return &DB{db, sync.Mutex{}} +} + +// CheckGroupR18Permission ... +func (db *DB) CheckGroupR18Permission(gid int64) bool { + db.mu.Lock() + defer db.mu.Unlock() + + var count int64 + db.Model(&model.GroupR18Permission{}).Where("group_id = ?", gid).Count(&count) + return count > 0 +} + +func (db *DB) findByKeyword(gid int64, keyword string, limit int, r18Req bool) ([]model.IllustCache, error) { + db.mu.Lock() + defer db.mu.Unlock() + + var results []model.IllustCache + query := db.Model(&model.IllustCache{}). + Where("keyword = ?", keyword). + Where("pid NOT IN (?)", db.Model(&model.SentImage{}).Where("group_id = ?", gid).Select("pid").SubQuery()). + Order("bookmarks DESC"). + Limit(limit) + + query = query.Where("r18 = ?", r18Req) + + err := query.Find(&results).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + return results, nil +} + +func (db *DB) findByTag(gid int64, tag string, needed int, r18Req bool) ([]model.IllustCache, error) { + if needed <= 0 { + return nil, nil + } + db.mu.Lock() + defer db.mu.Unlock() + + var results []model.IllustCache + escapedTag := strings.ReplaceAll(tag, "\"", "\\\"") + likePattern := "%\"" + escapedTag + "\"%" + + query := db.Model(&model.IllustCache{}). + Where("tags LIKE ?", likePattern). + Where("pid NOT IN (?)", db.Model(&model.SentImage{}).Where("group_id = ?", gid).Select("pid").SubQuery()). + Order("bookmarks DESC"). + Limit(needed) + + if !r18Req { + query = query.Where("r18 = ?", false) + } + + err := query.Find(&results).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + + return results, nil +} + +// CountIllustsSmart ... +func (db *DB) CountIllustsSmart(gid int64, keyword string, r18Req bool) (int64, error) { + db.mu.Lock() + defer db.mu.Unlock() + + var count int64 + + query := db.Model(&model.IllustCache{}). + Where("keyword = ?", keyword). + Where("pid NOT IN (?)", db.Model(&model.SentImage{}).Where("group_id = ?", gid).Select("pid").SubQuery()) + + if !r18Req { + query = query.Where("r18 = ?", false) + } + err := query.Count(&count).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, err + } + return count, nil +} + +// FindIllustsSmart ... +func (db *DB) FindIllustsSmart(gid int64, keyword string, limit int, r18Req bool) ([]model.IllustCache, error) { + seen := make(map[int64]struct{}) + results := make([]model.IllustCache, 0, limit) + + // 1. keyword 严格查询 + kwRes, err := db.findByKeyword(gid, keyword, limit, r18Req) + if err != nil { + return nil, err + } + for _, ill := range kwRes { + results = append(results, ill) + seen[ill.PID] = struct{}{} + } + + if len(results) >= limit { + return results[:limit], nil + } + + // 2. tag 查询补齐 + need := limit - len(results) + for _, tagKeyword := range strings.Fields(keyword) { + tagRes, err := db.findByTag(gid, tagKeyword, need, r18Req) + if err != nil { + return nil, err + } + + for _, ill := range tagRes { + if _, ok := seen[ill.PID]; ok { + continue + } + results = append(results, ill) + seen[ill.PID] = struct{}{} + if len(results) >= limit { + break + } + } + + if len(results) >= limit { + break + } + + need = limit - len(results) + } + + return results, nil +} + +// GetIllustIDsByKeyword ... +func (db *DB) GetIllustIDsByKeyword(keyword string) ([]int64, error) { + db.mu.Lock() + defer db.mu.Unlock() + + var illustIDs []int64 + err := db.Model(&model.IllustCache{}).Where("keyword = ?", keyword).Pluck("pid", &illustIDs).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + return illustIDs, nil +} + +// GetSentPictureIDs ... +func (db *DB) GetSentPictureIDs(gid int64) ([]int64, error) { + db.mu.Lock() + defer db.mu.Unlock() + + var pictureIDs []int64 + err := db.Model(&model.SentImage{}).Where("group_id = ?", gid).Pluck("pid", &pictureIDs).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + return pictureIDs, nil +} + +// DeleteIllustByPID 删除指定 PID 的插画缓存记录 +func (db *DB) DeleteIllustByPID(pid int64) error { + db.mu.Lock() + defer db.mu.Unlock() + + return db.Where("pid = ?", pid).Delete(&model.IllustCache{}).Error +} + +// Create wraps gorm.DB.Create with a mutex to avoid SQLITE_BUSY when requests are concurrent. +func (db *DB) Create(value interface{}) *gorm.DB { + db.mu.Lock() + defer db.mu.Unlock() + + return db.DB.Create(value) +} + +// Save wraps gorm.DB.Save with a mutex to avoid SQLITE_BUSY when requests are concurrent. +func (db *DB) Save(value interface{}) *gorm.DB { + db.mu.Lock() + defer db.mu.Unlock() + + return db.DB.Save(value) +} + +// Delete wraps gorm.DB.Delete with a mutex to avoid SQLITE_BUSY when requests are concurrent. +func (db *DB) Delete(value interface{}, where ...interface{}) *gorm.DB { + db.mu.Lock() + defer db.mu.Unlock() + + return db.DB.Delete(value, where...) +} diff --git a/plugin/pixiv/main.go b/plugin/pixiv/main.go new file mode 100644 index 0000000000..3b8c4a7128 --- /dev/null +++ b/plugin/pixiv/main.go @@ -0,0 +1,299 @@ +// Package pixiv ... +package pixiv + +import ( + "math/rand" + "os" + "strconv" + + "github.com/FloatTech/AnimeAPI/pixiv" + "github.com/FloatTech/AnimeAPI/pixiv/model" + "github.com/FloatTech/ZeroBot-Plugin/plugin/pixiv/cache" + "github.com/FloatTech/floatbox/file" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + log "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +var defaultKeyword = []string{"萝莉", "御姐", "妹妹", "姐姐", "车万"} + +var ( + service *Service +) + +func init() { + if file.IsNotExist("data/pixiv") { + err := os.MkdirAll("data/pixiv", 0775) + if err != nil { + panic(err) + } + } + if file.IsNotExist(pixivTempDir) { + err := os.MkdirAll(pixivTempDir, 0775) + if err != nil { + panic(err) + } + } + + db := cache.NewDB("data/pixiv/pixiv.db") + + var t1 model.RefreshToken + if err := db.First(&t1).Error; err != nil { + log.Warning("Fail fetching token store from database") + } + + pixivAPI := pixiv.NewPixivAPI(t1.Token) + + var proxyCfg model.PixivProxyConfig + proxyCfg.Name = "global" + if err := db.Where("name = ?", proxyCfg.Name).FirstOrCreate(&proxyCfg).Error; err == nil { + if err := pixivAPI.Client.SetProxy(proxyCfg.Proxy); err != nil { + log.Warning("设置pixiv代理错误: ", err) + } + } + + service = NewService(db, pixivAPI) +} + +const help = ` +- [x张]涩图 [关键词] +- 每日涩图 +- [x张]画师[画师的uid] +- p站搜图[插画pid] +- 设置p站token [token] +- 授权此处使用p站18 +- 设置pixiv代理 [http://127.0.0.1:7890] +- 查看pixiv代理 +- 清除pixiv代理 +[]不用打出来这只是一个占位符 +可添加多个关键词每个关键词用空格隔开 +默认不发R-18如果要发就加一个R-18关键词 +` + +func init() { + engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Brief: "Pixiv 图片搜索", + Help: help, + }) + + engine.OnFullMatch("授权此处使用p站18", zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { + id := ctx.Event.GroupID + if id == 0 { + id = -ctx.Event.UserID + } + if err := service.DB.Create(&model.GroupR18Permission{GroupID: id}).Error; err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + ctx.SendChain(message.Text("已允许")) + }) + + engine.OnRegex(`^设置p站token\s+(.*)`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { + token := ctx.State["regex_matched"].([]string)[1] + var refreshToken model.RefreshToken + refreshToken.User = ctx.Event.UserID + refreshToken.Token = token + if err := service.DB.Save(&refreshToken).Error; err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + service.API.Token.RefreshToken = token + + ctx.SendChain(message.Text("Pixiv Token: ", token)) + }) + + engine.OnRegex(`^设置pixiv代理\s+([\s\S]+)$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { + proxy := ctx.State["regex_matched"].([]string)[1] + + var proxyCfg model.PixivProxyConfig + proxyCfg.Name = "global" + if err := service.DB.Where("name = ?", proxyCfg.Name).FirstOrCreate(&proxyCfg).Error; err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + proxyCfg.Proxy = proxy + if err := service.DB.Save(&proxyCfg).Error; err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + if err := service.API.Client.SetProxy(proxy); err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + ctx.SendChain(message.Text("Pixiv client proxy 已设置为: ", proxy)) + }) + + engine.OnFullMatch("清除pixiv代理", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { + var proxyCfg model.PixivProxyConfig + proxyCfg.Name = "global" + if err := service.DB.Where("name = ?", proxyCfg.Name).FirstOrCreate(&proxyCfg).Error; err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + proxyCfg.Proxy = "" + if err := service.DB.Save(&proxyCfg).Error; err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + if err := service.API.Client.SetProxy(""); err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + ctx.SendChain(message.Text("Pixiv client proxy 已清除")) + }) + + engine.OnFullMatch("查看pixiv代理", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { + var proxyCfg model.PixivProxyConfig + proxyCfg.Name = "global" + if err := service.DB.Where("name = ?", proxyCfg.Name).FirstOrCreate(&proxyCfg).Error; err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + if proxyCfg.Proxy == "" { + ctx.SendChain(message.Text("Pixiv client proxy: 未设置")) + return + } + ctx.SendChain(message.Text("Pixiv client proxy: ", proxyCfg.Proxy)) + }) + + engine.OnRegex(`^p站搜图(\d+)`).SetBlock(true).Handle(func(ctx *zero.Ctx) { + rawPID := ctx.State["regex_matched"].([]string)[1] + pid, err := strconv.ParseInt(rawPID, 10, 64) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + illust, err := service.API.FetchPixivByPID(pid) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + _ = service.DB.Create(illust) + service.SendIllusts(ctx, []model.IllustCache{*illust}) + }) + + engine.OnRegex(`^(\d+)?张?画师(\d+)`).SetBlock(true).Handle(func(ctx *zero.Ctx) { + if !service.Acquire(ctx.Event.UserID) { + ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("上一个任务还没结束,请稍后再试")) + return + } + defer service.Release(ctx.Event.UserID) + + limit := ctx.State["regex_matched"].([]string)[1] + if limit == "" { + limit = "1" + } + rawUID := ctx.State["regex_matched"].([]string)[2] + limitInt, err := strconv.Atoi(limit) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + uid, err := strconv.ParseInt(rawUID, 10, 64) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + pictureIDs, err := service.DB.GetSentPictureIDs(ctx.Event.GroupID) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + illustInfos, err := service.API.FetchPixivByUser(uid, limitInt, pictureIDs) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + if len(illustInfos) == 0 { + ctx.SendChain(message.Text("没有找到图片")) + return + } + + service.SendIllusts(ctx, illustInfos) + }) + + engine.OnRegex(`^每日[色|涩|瑟]图$`).SetBlock(true).Handle(func(ctx *zero.Ctx) { + illusts, err := service.API.FetchPixivRecommend(1) + if err != nil { + ctx.SendChain(message.Text("发送涩图失败惹")) + return + } + service.SendIllusts(ctx, []model.IllustCache{illusts[0]}) + }) + + engine.OnRegex(`^(\d+)?张?[色|瑟|涩]图\s*(.+)?`).SetBlock(true).Handle(func(ctx *zero.Ctx) { + limit := ctx.State["regex_matched"].([]string)[1] + keyword := ctx.State["regex_matched"].([]string)[2] + + if !service.Acquire(ctx.Event.UserID) { + ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("上一个任务还没结束,请稍后再试")) + return + } + defer service.Release(ctx.Event.UserID) + + if limit == "" { + limit = "1" + } + + if keyword == "" { + keyword = defaultKeyword[rand.Intn(len(defaultKeyword))] + } + + limitInt, err := strconv.Atoi(limit) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + if limitInt > 10 { + ctx.SendChain(message.Text("图片太多了")) + return + } + + gid := ctx.Event.GroupID + r18Req := pixiv.IsR18(keyword) + cleanKeyword := pixiv.RemoveR18Keywords(keyword) + + if gid == 0 { + gid = -ctx.Event.UserID + } + + if r18Req && !service.DB.CheckGroupR18Permission(gid) { + ctx.SendChain(message.Text([]string{ + "笨蛋笨蛋大笨蛋", + "此处未授权r18", + }[rand.Intn(2)])) + return + } + + cachedIllusts, err := service.DB.FindIllustsSmart(gid, cleanKeyword, limitInt, r18Req) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + cached, _ := service.DB.GetSentPictureIDs(gid) + + for _, ill := range cachedIllusts { + cached = append(cached, ill.PID) + } + + illusts, err := service.API.GetIllustsByKeyword(keyword, limitInt, cachedIllusts, cached) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + for _, illust := range illusts { + log.Print("获取到图片 PID: ", illust.PID) + _ = service.DB.Create(&illust).Error + } + + service.SendIllusts(ctx, illusts) + service.BackgroundCacheFiller(cleanKeyword, 10, r18Req, 5, ctx.Event.GroupID) + }) +} diff --git a/plugin/pixiv/service.go b/plugin/pixiv/service.go new file mode 100644 index 0000000000..389b9c0f17 --- /dev/null +++ b/plugin/pixiv/service.go @@ -0,0 +1,383 @@ +// Package pixiv ... +package pixiv + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/FloatTech/AnimeAPI/pixiv" + "github.com/FloatTech/AnimeAPI/pixiv/model" + "github.com/FloatTech/ZeroBot-Plugin/plugin/pixiv/cache" + "github.com/FloatTech/floatbox/file" + log "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +var cacheFilling sync.Map + +// Service 用于封装整个 Pixiv 模块的依赖与接口 +type Service struct { + DB *cache.DB + API *pixiv.PixivAPI + // 内部任务锁:限制每个人同一时间只能执行一个请求 + taskMu sync.Mutex + tasks map[int64]*taskState + + // 并发控制 + DownloadWorkers int + SendWorkers int +} + +type taskState struct { + Running bool +} + +const pixivTempDir = "data/pixiv/temp" + +// NewService ... +func NewService(db *cache.DB, pixAPI *pixiv.PixivAPI) *Service { + return &Service{ + DB: db, + API: pixAPI, + tasks: make(map[int64]*taskState), + DownloadWorkers: 4, + SendWorkers: 2, + } +} + +// Acquire ... +func (s *Service) Acquire(userID int64) bool { + s.taskMu.Lock() + defer s.taskMu.Unlock() + + t, ok := s.tasks[userID] + if ok && t.Running { + return false + } + + if !ok { + t = &taskState{} + s.tasks[userID] = t + } + t.Running = true + return true +} + +// Release ... +func (s *Service) Release(userID int64) { + s.taskMu.Lock() + defer s.taskMu.Unlock() + + if t, ok := s.tasks[userID]; ok { + t.Running = false + } + delete(s.tasks, userID) +} + +func removeTempImages(paths []string) { + for _, p := range paths { + if err := os.Remove(p); err != nil && !os.IsNotExist(err) { + log.Warnf("删除 pixiv 临时文件失败: %s: %v", p, err) + } + } +} + +func scheduleCleanupPixivImages(paths []string, delay time.Duration) { + if len(paths) == 0 { + return + } + local := append([]string(nil), paths...) + go func() { + time.Sleep(delay) + removeTempImages(local) + }() +} + +func pixivImageExt(rawURL string) string { + parsed, err := url.Parse(rawURL) + if err != nil { + return ".jpg" + } + ext := path.Ext(parsed.Path) + if ext == "" { + return ".jpg" + } + return ext +} + +func createPixivTempImagePath(pid int64, index int, rawURL string) (string, error) { + baseDir := filepath.Join(file.BOTPATH, pixivTempDir) + if err := os.MkdirAll(baseDir, 0o775); err != nil { + return "", err + } + pattern := fmt.Sprintf("%d-", pid) + if index > 0 { + pattern = fmt.Sprintf("%d-%d-", pid, index) + } + tmp, err := os.CreateTemp(baseDir, pattern+"*"+pixivImageExt(rawURL)) + if err != nil { + return "", err + } + name := tmp.Name() + if err = tmp.Close(); err != nil { + return "", err + } + return name, nil +} + +func writeTempImages(pid int64, images [][]byte, urls []string) ([]string, error) { + paths := make([]string, 0, len(images)) + cleanup := func() { + removeTempImages(paths) + } + + for i, img := range images { + if len(img) == 0 { + continue + } + rawURL := "" + if i < len(urls) { + rawURL = urls[i] + } + imagePath, err := createPixivTempImagePath(pid, i+1, rawURL) + if err != nil { + cleanup() + return nil, err + } + if err = os.WriteFile(imagePath, img, 0o644); err != nil { + cleanup() + return nil, err + } + paths = append(paths, imagePath) + } + + return paths, nil +} + +func toFileImage(path string) message.Segment { + normalized := strings.ReplaceAll(path, "\\", "/") + if strings.HasPrefix(normalized, "/") { + return message.Image("file://" + normalized) + } + return message.Image("file:///" + normalized) +} + +// SendIllusts ... +func (s *Service) SendIllusts(ctx *zero.Ctx, illusts []model.IllustCache) { + downloadSem := make(chan struct{}, s.DownloadWorkers) + type DLResult struct { + Ill model.IllustCache + Images [][]byte + Err error + } + + gid := ctx.Event.GroupID + if gid == 0 { + gid = -ctx.Event.UserID + } + + results := make(chan DLResult, len(illusts)) + + // 并发下载 + for _, ill := range illusts { + ill1 := ill + downloadSem <- struct{}{} + + go func() { + defer func() { <-downloadSem }() + + d := DLResult{ + Ill: ill1, + } + + if ill1.PageCount > 1 { + + type pageResult struct { + index int + data []byte + err error + } + + pageCh := make(chan pageResult, ill1.PageCount) + var wg sync.WaitGroup + + pageSem := make(chan struct{}, s.DownloadWorkers) + + for i := 0; int64(i) < ill1.PageCount; i++ { + wg.Add(1) + pageSem <- struct{}{} + + go func(page int) { + defer wg.Done() + defer func() { <-pageSem }() + + u := pixiv.ModifyPageGeneric(ill1.OriginalURL, page) + img, err := s.API.Client.FetchPixivImage(ill1, u) + if err != nil { + pageCh <- pageResult{ + index: page, + err: err, + } + return + } + pageCh <- pageResult{ + index: page, + data: img, + err: err, + } + }(i) + } + + // 关闭 channel + go func() { + wg.Wait() + close(pageCh) + }() + + // 预分配,保证顺序 + d.Images = make([][]byte, ill1.PageCount) + + for r := range pageCh { + if r.err != nil && d.Err == nil { + d.Err = r.err + } + if len(r.data) == 0 { + continue + } + d.Images[r.index] = r.data + } + + } else { + img, err := s.API.Client.FetchPixivImage(ill1, ill1.OriginalURL) + if err == nil { + d.Images = append(d.Images, img) + } else { + d.Err = err + } + } + + log.Print("下载图片完成:", ill1.PID) + results <- d + }() + + } + + // 发送(顺序) + for range illusts { + res := <-results + + if res.Err != nil { + if httpErr, ok := errors.AsType[*pixiv.HTTPStatusError](res.Err); ok && httpErr.StatusCode == http.StatusNotFound { + if err := s.DB.DeleteIllustByPID(res.Ill.PID); err != nil { + ctx.SendChain(message.Text("清理已失效图片失败: ", err)) + } else { + ctx.SendChain(message.Text("图片已被删除,已移除缓存,PID: ", res.Ill.PID)) + } + } else { + ctx.SendChain(message.Text("下载失败: ", res.Err)) + } + + continue + } + + var msg message.Message + msg = append(msg, message.Text( + "PID:", res.Ill.PID, + "\n标题:", res.Ill.Title, + "\n画师:", res.Ill.AuthorName, + "\ntag:", res.Ill.Tags, + "\n收藏数:", res.Ill.Bookmarks, + "\n浏览数:", res.Ill.TotalView, + "\n发布时间:", res.Ill.CreateDate, + )) + + imageURLs := make([]string, len(res.Images)) + for i := range imageURLs { + if i == 0 { + imageURLs[i] = res.Ill.OriginalURL + continue + } + imageURLs[i] = pixiv.ModifyPageGeneric(res.Ill.OriginalURL, i) + } + + tempPaths, err := writeTempImages(res.Ill.PID, res.Images, imageURLs) + if err != nil { + ctx.SendChain(message.Text("暂存图片失败: ", err)) + continue + } + for _, tpath := range tempPaths { + msg = append(msg, toFileImage(tpath)) + } + + ctx.Send(msg) + scheduleCleanupPixivImages(tempPaths, 15*time.Second) + + s.DB.Create(&model.SentImage{ + GroupID: gid, + PID: res.Ill.PID, + }) + } +} + +// BackgroundCacheFiller ... +func (s *Service) BackgroundCacheFiller(keyword string, minCache int, r18Req bool, fetchCount int, gid int64) { + if _, loaded := cacheFilling.LoadOrStore(keyword, struct{}{}); loaded { + log.Print("已有后台任务在补缓存: ", keyword) + return + } + + go func() { + defer cacheFilling.Delete(keyword) + + count, err := s.DB.CountIllustsSmart(gid, keyword, r18Req) + if err != nil { + log.Print("查询数据库发生错误: ", err) + return + } + + if count >= int64(minCache) { + log.Print("缓存足够,无需补充: ", keyword) + return + } + + log.Printf("后台补充关键词 %s, 数量 %d\n", keyword, fetchCount) + + sendedcache, err := s.DB.GetSentPictureIDs(gid) + if err != nil { + log.Print("后台补充缓存失败: ", err) + return + } + s1, err := s.DB.GetIllustIDsByKeyword(keyword) + if err != nil { + log.Print("后台补充缓存失败: ", err) + return + } + sendedcache = append(sendedcache, s1...) + newIllusts, err := s.API.FetchPixivIllusts(keyword, r18Req, fetchCount, sendedcache) + if err != nil { + log.Print("后台补充缓存失败: ", err) + return + } + + if len(newIllusts) == 0 { + log.Print("后台补充缓存:没有新图") + return + } + + for _, illust := range newIllusts { + s.DB.Create(&illust) + log.Print("后台补充缓存:", illust.PID) + } + + log.Printf("后台成功补充 %d 张图片到关键词 %s 缓存\n", len(newIllusts), keyword) + }() +}