publishport-opencli 1.0.39 → 1.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli-manifest.json +453 -0
- package/clis/fanqie/_shared.js +22 -0
- package/clis/fanqie/auth.js +1 -0
- package/clis/fanqie/books.js +1 -0
- package/clis/fanqie/chapters.js +1 -0
- package/clis/fanqie/delete.js +1 -0
- package/clis/fanqie/publish.js +1 -0
- package/clis/qimao/_shared.js +7 -0
- package/clis/qimao/auth.js +4 -0
- package/clis/qimao/books.js +1 -0
- package/clis/qimao/chapters.js +1 -0
- package/clis/qimao/delete.js +1 -0
- package/clis/qimao/publish.js +1 -0
- package/clis/xiaohongshu/auth.js +2 -2
- package/dist/src/browser/state-budget.d.ts +26 -0
- package/dist/src/browser/state-budget.js +7 -0
- package/dist/src/cli.js +13 -13
- package/package.json +1 -1
- package/skills/opencli-browser/SKILL.md +2 -2
package/cli-manifest.json
CHANGED
|
@@ -16976,6 +16976,231 @@
|
|
|
16976
16976
|
"navigateBefore": false,
|
|
16977
16977
|
"siteSession": "persistent"
|
|
16978
16978
|
},
|
|
16979
|
+
{
|
|
16980
|
+
"site": "fanqie",
|
|
16981
|
+
"name": "books",
|
|
16982
|
+
"description": "列出番茄小说作家账号下的全部作品(作品 ID、书名、字数、阅读数、连载状态),作品 ID 供 publish / chapters / delete 的 --book 使用。",
|
|
16983
|
+
"access": "read",
|
|
16984
|
+
"domain": "fanqienovel.com",
|
|
16985
|
+
"strategy": "cookie",
|
|
16986
|
+
"browser": true,
|
|
16987
|
+
"args": [],
|
|
16988
|
+
"columns": [
|
|
16989
|
+
"book_id",
|
|
16990
|
+
"book_name",
|
|
16991
|
+
"word_count",
|
|
16992
|
+
"read_count",
|
|
16993
|
+
"chapter_count",
|
|
16994
|
+
"status"
|
|
16995
|
+
],
|
|
16996
|
+
"type": "js",
|
|
16997
|
+
"modulePath": "fanqie/books.js",
|
|
16998
|
+
"sourceFile": "fanqie/books.js",
|
|
16999
|
+
"navigateBefore": "https://fanqienovel.com"
|
|
17000
|
+
},
|
|
17001
|
+
{
|
|
17002
|
+
"site": "fanqie",
|
|
17003
|
+
"name": "chapters",
|
|
17004
|
+
"description": "列出番茄作品各分卷下的章节(章节 ID、标题、分卷、字数、状态、定时时间);--drafts 改看草稿箱。",
|
|
17005
|
+
"access": "read",
|
|
17006
|
+
"domain": "fanqienovel.com",
|
|
17007
|
+
"strategy": "cookie",
|
|
17008
|
+
"browser": true,
|
|
17009
|
+
"args": [
|
|
17010
|
+
{
|
|
17011
|
+
"name": "book",
|
|
17012
|
+
"type": "str",
|
|
17013
|
+
"required": true,
|
|
17014
|
+
"help": "作品 ID(`ppcli fanqie books` 查)"
|
|
17015
|
+
},
|
|
17016
|
+
{
|
|
17017
|
+
"name": "drafts",
|
|
17018
|
+
"type": "boolean",
|
|
17019
|
+
"required": false,
|
|
17020
|
+
"help": "只看草稿箱里未发布的章节"
|
|
17021
|
+
}
|
|
17022
|
+
],
|
|
17023
|
+
"columns": [
|
|
17024
|
+
"item_id",
|
|
17025
|
+
"title",
|
|
17026
|
+
"volume",
|
|
17027
|
+
"word_count",
|
|
17028
|
+
"status",
|
|
17029
|
+
"timer_time"
|
|
17030
|
+
],
|
|
17031
|
+
"type": "js",
|
|
17032
|
+
"modulePath": "fanqie/chapters.js",
|
|
17033
|
+
"sourceFile": "fanqie/chapters.js",
|
|
17034
|
+
"navigateBefore": "https://fanqienovel.com"
|
|
17035
|
+
},
|
|
17036
|
+
{
|
|
17037
|
+
"site": "fanqie",
|
|
17038
|
+
"name": "delete",
|
|
17039
|
+
"description": "删除番茄作品里的一章(已发布、定时待发、草稿都可),删除后不可恢复。",
|
|
17040
|
+
"access": "write",
|
|
17041
|
+
"domain": "fanqienovel.com",
|
|
17042
|
+
"strategy": "cookie",
|
|
17043
|
+
"browser": true,
|
|
17044
|
+
"args": [
|
|
17045
|
+
{
|
|
17046
|
+
"name": "item",
|
|
17047
|
+
"type": "str",
|
|
17048
|
+
"required": true,
|
|
17049
|
+
"positional": true,
|
|
17050
|
+
"help": "章节 ID(`ppcli fanqie chapters` 查)"
|
|
17051
|
+
},
|
|
17052
|
+
{
|
|
17053
|
+
"name": "book",
|
|
17054
|
+
"type": "str",
|
|
17055
|
+
"required": true,
|
|
17056
|
+
"help": "作品 ID"
|
|
17057
|
+
},
|
|
17058
|
+
{
|
|
17059
|
+
"name": "execute",
|
|
17060
|
+
"type": "boolean",
|
|
17061
|
+
"required": false,
|
|
17062
|
+
"help": "真正执行删除;不加此参数命令拒绝写入"
|
|
17063
|
+
}
|
|
17064
|
+
],
|
|
17065
|
+
"columns": [
|
|
17066
|
+
"status",
|
|
17067
|
+
"item_id",
|
|
17068
|
+
"book_id"
|
|
17069
|
+
],
|
|
17070
|
+
"type": "js",
|
|
17071
|
+
"modulePath": "fanqie/delete.js",
|
|
17072
|
+
"sourceFile": "fanqie/delete.js",
|
|
17073
|
+
"navigateBefore": "https://fanqienovel.com"
|
|
17074
|
+
},
|
|
17075
|
+
{
|
|
17076
|
+
"site": "fanqie",
|
|
17077
|
+
"name": "login",
|
|
17078
|
+
"description": "打开番茄小说作家后台登录页并等待登录完成(供桌面客户端引导登录)。",
|
|
17079
|
+
"access": "write",
|
|
17080
|
+
"domain": "fanqienovel.com",
|
|
17081
|
+
"strategy": "cookie",
|
|
17082
|
+
"browser": true,
|
|
17083
|
+
"args": [
|
|
17084
|
+
{
|
|
17085
|
+
"name": "timeout",
|
|
17086
|
+
"type": "int",
|
|
17087
|
+
"default": 300,
|
|
17088
|
+
"required": false,
|
|
17089
|
+
"help": "Maximum seconds to wait for the user to finish login"
|
|
17090
|
+
}
|
|
17091
|
+
],
|
|
17092
|
+
"columns": [
|
|
17093
|
+
"status",
|
|
17094
|
+
"logged_in",
|
|
17095
|
+
"site",
|
|
17096
|
+
"name"
|
|
17097
|
+
],
|
|
17098
|
+
"type": "js",
|
|
17099
|
+
"modulePath": "fanqie/auth.js",
|
|
17100
|
+
"sourceFile": "fanqie/auth.js",
|
|
17101
|
+
"navigateBefore": false,
|
|
17102
|
+
"siteSession": "persistent",
|
|
17103
|
+
"defaultWindowMode": "foreground"
|
|
17104
|
+
},
|
|
17105
|
+
{
|
|
17106
|
+
"site": "fanqie",
|
|
17107
|
+
"name": "publish",
|
|
17108
|
+
"description": "在番茄小说作品里发布一章:默认立即发布,--schedule 定时发布。正文可直接给或用 --file 读 UTF-8 文本 / Markdown,每个非空行成一段。完本模式的作品不允许定时发布。",
|
|
17109
|
+
"access": "write",
|
|
17110
|
+
"domain": "fanqienovel.com",
|
|
17111
|
+
"strategy": "cookie",
|
|
17112
|
+
"browser": true,
|
|
17113
|
+
"args": [
|
|
17114
|
+
{
|
|
17115
|
+
"name": "title",
|
|
17116
|
+
"type": "str",
|
|
17117
|
+
"required": true,
|
|
17118
|
+
"positional": true,
|
|
17119
|
+
"help": "章节标题,带章节序号,如「第12章 夜雨」"
|
|
17120
|
+
},
|
|
17121
|
+
{
|
|
17122
|
+
"name": "text",
|
|
17123
|
+
"type": "str",
|
|
17124
|
+
"required": false,
|
|
17125
|
+
"positional": true,
|
|
17126
|
+
"help": "章节正文(与 --file 二选一)"
|
|
17127
|
+
},
|
|
17128
|
+
{
|
|
17129
|
+
"name": "file",
|
|
17130
|
+
"type": "str",
|
|
17131
|
+
"required": false,
|
|
17132
|
+
"help": "从文件读取章节正文(UTF-8)"
|
|
17133
|
+
},
|
|
17134
|
+
{
|
|
17135
|
+
"name": "book",
|
|
17136
|
+
"type": "str",
|
|
17137
|
+
"required": true,
|
|
17138
|
+
"help": "作品 ID(`ppcli fanqie books` 查)"
|
|
17139
|
+
},
|
|
17140
|
+
{
|
|
17141
|
+
"name": "schedule",
|
|
17142
|
+
"type": "str",
|
|
17143
|
+
"required": false,
|
|
17144
|
+
"help": "定时发布:北京时间 YYYY-MM-DD HH:mm、带时区 ISO8601 或 Unix 秒(平台对部分作品不开放定时,会直接报错)"
|
|
17145
|
+
},
|
|
17146
|
+
{
|
|
17147
|
+
"name": "volume",
|
|
17148
|
+
"type": "str",
|
|
17149
|
+
"required": false,
|
|
17150
|
+
"help": "分卷 ID(默认作品当前分卷;填了不存在的 ID 会报错并列出全部分卷)"
|
|
17151
|
+
},
|
|
17152
|
+
{
|
|
17153
|
+
"name": "author-say",
|
|
17154
|
+
"type": "str",
|
|
17155
|
+
"required": false,
|
|
17156
|
+
"help": "作者有话说(附在章末)"
|
|
17157
|
+
},
|
|
17158
|
+
{
|
|
17159
|
+
"name": "ai-generated",
|
|
17160
|
+
"type": "boolean",
|
|
17161
|
+
"required": false,
|
|
17162
|
+
"help": "声明本章使用了 AI 辅助创作"
|
|
17163
|
+
},
|
|
17164
|
+
{
|
|
17165
|
+
"name": "execute",
|
|
17166
|
+
"type": "boolean",
|
|
17167
|
+
"required": false,
|
|
17168
|
+
"help": "真正执行写操作;不加此参数命令拒绝写入"
|
|
17169
|
+
}
|
|
17170
|
+
],
|
|
17171
|
+
"columns": [
|
|
17172
|
+
"status",
|
|
17173
|
+
"item_id",
|
|
17174
|
+
"title",
|
|
17175
|
+
"book_id",
|
|
17176
|
+
"publish_time",
|
|
17177
|
+
"message"
|
|
17178
|
+
],
|
|
17179
|
+
"type": "js",
|
|
17180
|
+
"modulePath": "fanqie/publish.js",
|
|
17181
|
+
"sourceFile": "fanqie/publish.js",
|
|
17182
|
+
"navigateBefore": "https://fanqienovel.com"
|
|
17183
|
+
},
|
|
17184
|
+
{
|
|
17185
|
+
"site": "fanqie",
|
|
17186
|
+
"name": "whoami",
|
|
17187
|
+
"description": "查看当前登录的番茄小说作家账号(笔名)。",
|
|
17188
|
+
"access": "read",
|
|
17189
|
+
"domain": "fanqienovel.com",
|
|
17190
|
+
"strategy": "cookie",
|
|
17191
|
+
"browser": true,
|
|
17192
|
+
"args": [],
|
|
17193
|
+
"columns": [
|
|
17194
|
+
"logged_in",
|
|
17195
|
+
"site",
|
|
17196
|
+
"name"
|
|
17197
|
+
],
|
|
17198
|
+
"type": "js",
|
|
17199
|
+
"modulePath": "fanqie/auth.js",
|
|
17200
|
+
"sourceFile": "fanqie/auth.js",
|
|
17201
|
+
"navigateBefore": false,
|
|
17202
|
+
"siteSession": "persistent"
|
|
17203
|
+
},
|
|
16979
17204
|
{
|
|
16980
17205
|
"site": "flathub",
|
|
16981
17206
|
"name": "app",
|
|
@@ -34475,6 +34700,234 @@
|
|
|
34475
34700
|
"navigateBefore": false,
|
|
34476
34701
|
"siteSession": "persistent"
|
|
34477
34702
|
},
|
|
34703
|
+
{
|
|
34704
|
+
"site": "qimao",
|
|
34705
|
+
"name": "books",
|
|
34706
|
+
"description": "列出七猫作家账号下的全部小说(作品 ID、书名、总字数、最新章节、签约状态),作品 ID 供 publish / chapters / delete 的 --book 使用。",
|
|
34707
|
+
"access": "read",
|
|
34708
|
+
"domain": "zuozhe.qimao.com",
|
|
34709
|
+
"strategy": "cookie",
|
|
34710
|
+
"browser": true,
|
|
34711
|
+
"args": [],
|
|
34712
|
+
"columns": [
|
|
34713
|
+
"book_id",
|
|
34714
|
+
"title",
|
|
34715
|
+
"total_words",
|
|
34716
|
+
"latest_chapter",
|
|
34717
|
+
"status",
|
|
34718
|
+
"signed"
|
|
34719
|
+
],
|
|
34720
|
+
"type": "js",
|
|
34721
|
+
"modulePath": "qimao/books.js",
|
|
34722
|
+
"sourceFile": "qimao/books.js",
|
|
34723
|
+
"navigateBefore": "https://zuozhe.qimao.com"
|
|
34724
|
+
},
|
|
34725
|
+
{
|
|
34726
|
+
"site": "qimao",
|
|
34727
|
+
"name": "chapters",
|
|
34728
|
+
"description": "列出七猫作品的章节(章节 ID、标题、字数、审核状态、发布时间);--drafts 改看草稿箱。",
|
|
34729
|
+
"access": "read",
|
|
34730
|
+
"domain": "zuozhe.qimao.com",
|
|
34731
|
+
"strategy": "cookie",
|
|
34732
|
+
"browser": true,
|
|
34733
|
+
"args": [
|
|
34734
|
+
{
|
|
34735
|
+
"name": "book",
|
|
34736
|
+
"type": "str",
|
|
34737
|
+
"required": true,
|
|
34738
|
+
"help": "作品 ID(`ppcli qimao books` 查)"
|
|
34739
|
+
},
|
|
34740
|
+
{
|
|
34741
|
+
"name": "drafts",
|
|
34742
|
+
"type": "boolean",
|
|
34743
|
+
"required": false,
|
|
34744
|
+
"help": "只看草稿箱"
|
|
34745
|
+
}
|
|
34746
|
+
],
|
|
34747
|
+
"columns": [
|
|
34748
|
+
"chapter_id",
|
|
34749
|
+
"title",
|
|
34750
|
+
"word_count",
|
|
34751
|
+
"status",
|
|
34752
|
+
"publish_time",
|
|
34753
|
+
"volume_id"
|
|
34754
|
+
],
|
|
34755
|
+
"type": "js",
|
|
34756
|
+
"modulePath": "qimao/chapters.js",
|
|
34757
|
+
"sourceFile": "qimao/chapters.js",
|
|
34758
|
+
"navigateBefore": "https://zuozhe.qimao.com"
|
|
34759
|
+
},
|
|
34760
|
+
{
|
|
34761
|
+
"site": "qimao",
|
|
34762
|
+
"name": "delete",
|
|
34763
|
+
"description": "删除七猫作品里的一章草稿,删除后不可恢复。七猫不允许删除已发布 / 定时发布的章节。",
|
|
34764
|
+
"access": "write",
|
|
34765
|
+
"domain": "zuozhe.qimao.com",
|
|
34766
|
+
"strategy": "cookie",
|
|
34767
|
+
"browser": true,
|
|
34768
|
+
"args": [
|
|
34769
|
+
{
|
|
34770
|
+
"name": "chapter",
|
|
34771
|
+
"type": "str",
|
|
34772
|
+
"required": true,
|
|
34773
|
+
"positional": true,
|
|
34774
|
+
"help": "草稿章节 ID(`ppcli qimao chapters --drafts` 查)"
|
|
34775
|
+
},
|
|
34776
|
+
{
|
|
34777
|
+
"name": "book",
|
|
34778
|
+
"type": "str",
|
|
34779
|
+
"required": true,
|
|
34780
|
+
"help": "作品 ID"
|
|
34781
|
+
},
|
|
34782
|
+
{
|
|
34783
|
+
"name": "execute",
|
|
34784
|
+
"type": "boolean",
|
|
34785
|
+
"required": false,
|
|
34786
|
+
"help": "真正执行删除;不加此参数命令拒绝写入"
|
|
34787
|
+
}
|
|
34788
|
+
],
|
|
34789
|
+
"columns": [
|
|
34790
|
+
"status",
|
|
34791
|
+
"chapter_id",
|
|
34792
|
+
"book_id"
|
|
34793
|
+
],
|
|
34794
|
+
"type": "js",
|
|
34795
|
+
"modulePath": "qimao/delete.js",
|
|
34796
|
+
"sourceFile": "qimao/delete.js",
|
|
34797
|
+
"navigateBefore": "https://zuozhe.qimao.com"
|
|
34798
|
+
},
|
|
34799
|
+
{
|
|
34800
|
+
"site": "qimao",
|
|
34801
|
+
"name": "login",
|
|
34802
|
+
"description": "打开七猫作家中心登录页并等待登录完成(供桌面客户端引导登录)。",
|
|
34803
|
+
"access": "write",
|
|
34804
|
+
"domain": "zuozhe.qimao.com",
|
|
34805
|
+
"strategy": "cookie",
|
|
34806
|
+
"browser": true,
|
|
34807
|
+
"args": [
|
|
34808
|
+
{
|
|
34809
|
+
"name": "timeout",
|
|
34810
|
+
"type": "int",
|
|
34811
|
+
"default": 300,
|
|
34812
|
+
"required": false,
|
|
34813
|
+
"help": "Maximum seconds to wait for the user to finish login"
|
|
34814
|
+
}
|
|
34815
|
+
],
|
|
34816
|
+
"columns": [
|
|
34817
|
+
"status",
|
|
34818
|
+
"logged_in",
|
|
34819
|
+
"site",
|
|
34820
|
+
"account_id",
|
|
34821
|
+
"pen_name"
|
|
34822
|
+
],
|
|
34823
|
+
"type": "js",
|
|
34824
|
+
"modulePath": "qimao/auth.js",
|
|
34825
|
+
"sourceFile": "qimao/auth.js",
|
|
34826
|
+
"navigateBefore": false,
|
|
34827
|
+
"siteSession": "persistent",
|
|
34828
|
+
"defaultWindowMode": "foreground"
|
|
34829
|
+
},
|
|
34830
|
+
{
|
|
34831
|
+
"site": "qimao",
|
|
34832
|
+
"name": "publish",
|
|
34833
|
+
"description": "在七猫小说作品里上传一章:默认立即发布,--schedule 定时发布,--draft 只存草稿。正文可直接给或用 --file 读 UTF-8 文本 / Markdown,每个非空行成一段。七猫要求正文 1000~50000 字(草稿不限)、不能与已有章节重复;部分作品(如未签约新书)只允许定时发布。",
|
|
34834
|
+
"access": "write",
|
|
34835
|
+
"domain": "zuozhe.qimao.com",
|
|
34836
|
+
"strategy": "cookie",
|
|
34837
|
+
"browser": true,
|
|
34838
|
+
"args": [
|
|
34839
|
+
{
|
|
34840
|
+
"name": "title",
|
|
34841
|
+
"type": "str",
|
|
34842
|
+
"required": true,
|
|
34843
|
+
"positional": true,
|
|
34844
|
+
"help": "章节名,如「夜雨」或「第12章 夜雨」(带序号时按该序号上传)"
|
|
34845
|
+
},
|
|
34846
|
+
{
|
|
34847
|
+
"name": "text",
|
|
34848
|
+
"type": "str",
|
|
34849
|
+
"required": false,
|
|
34850
|
+
"positional": true,
|
|
34851
|
+
"help": "章节正文(与 --file 二选一)"
|
|
34852
|
+
},
|
|
34853
|
+
{
|
|
34854
|
+
"name": "file",
|
|
34855
|
+
"type": "str",
|
|
34856
|
+
"required": false,
|
|
34857
|
+
"help": "从文件读取章节正文(UTF-8)"
|
|
34858
|
+
},
|
|
34859
|
+
{
|
|
34860
|
+
"name": "book",
|
|
34861
|
+
"type": "str",
|
|
34862
|
+
"required": true,
|
|
34863
|
+
"help": "作品 ID(`ppcli qimao books` 查)"
|
|
34864
|
+
},
|
|
34865
|
+
{
|
|
34866
|
+
"name": "draft",
|
|
34867
|
+
"type": "boolean",
|
|
34868
|
+
"required": false,
|
|
34869
|
+
"help": "只存草稿,不发布"
|
|
34870
|
+
},
|
|
34871
|
+
{
|
|
34872
|
+
"name": "schedule",
|
|
34873
|
+
"type": "str",
|
|
34874
|
+
"required": false,
|
|
34875
|
+
"help": "定时发布:北京时间 YYYY-MM-DD HH:mm、带时区 ISO8601 或 Unix 秒"
|
|
34876
|
+
},
|
|
34877
|
+
{
|
|
34878
|
+
"name": "volume",
|
|
34879
|
+
"type": "str",
|
|
34880
|
+
"required": false,
|
|
34881
|
+
"help": "分卷 ID(默认最后一个分卷;填了不存在的 ID 会报错并列出全部分卷)"
|
|
34882
|
+
},
|
|
34883
|
+
{
|
|
34884
|
+
"name": "author-say",
|
|
34885
|
+
"type": "str",
|
|
34886
|
+
"required": false,
|
|
34887
|
+
"help": "作者的话(附在章末)"
|
|
34888
|
+
},
|
|
34889
|
+
{
|
|
34890
|
+
"name": "execute",
|
|
34891
|
+
"type": "boolean",
|
|
34892
|
+
"required": false,
|
|
34893
|
+
"help": "真正执行写操作;不加此参数命令拒绝写入"
|
|
34894
|
+
}
|
|
34895
|
+
],
|
|
34896
|
+
"columns": [
|
|
34897
|
+
"status",
|
|
34898
|
+
"chapter_id",
|
|
34899
|
+
"title",
|
|
34900
|
+
"book_id",
|
|
34901
|
+
"word_count",
|
|
34902
|
+
"publish_time",
|
|
34903
|
+
"message"
|
|
34904
|
+
],
|
|
34905
|
+
"type": "js",
|
|
34906
|
+
"modulePath": "qimao/publish.js",
|
|
34907
|
+
"sourceFile": "qimao/publish.js",
|
|
34908
|
+
"navigateBefore": "https://zuozhe.qimao.com"
|
|
34909
|
+
},
|
|
34910
|
+
{
|
|
34911
|
+
"site": "qimao",
|
|
34912
|
+
"name": "whoami",
|
|
34913
|
+
"description": "查看当前登录的七猫作家账号(账号 ID、笔名)。",
|
|
34914
|
+
"access": "read",
|
|
34915
|
+
"domain": "zuozhe.qimao.com",
|
|
34916
|
+
"strategy": "cookie",
|
|
34917
|
+
"browser": true,
|
|
34918
|
+
"args": [],
|
|
34919
|
+
"columns": [
|
|
34920
|
+
"logged_in",
|
|
34921
|
+
"site",
|
|
34922
|
+
"account_id",
|
|
34923
|
+
"pen_name"
|
|
34924
|
+
],
|
|
34925
|
+
"type": "js",
|
|
34926
|
+
"modulePath": "qimao/auth.js",
|
|
34927
|
+
"sourceFile": "qimao/auth.js",
|
|
34928
|
+
"navigateBefore": false,
|
|
34929
|
+
"siteSession": "persistent"
|
|
34930
|
+
},
|
|
34478
34931
|
{
|
|
34479
34932
|
"site": "qoder",
|
|
34480
34933
|
"name": "account",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import{readFile as f,stat as u}from"node:fs/promises";import{ArgumentError as o,AuthRequiredError as l,CommandExecutionError as a}from"@jackwener/opencli/errors";export const DOMAIN="fanqienovel.com",HOME="https://fanqienovel.com/main/writer/",LOGIN_URL="https://fanqienovel.com/main/writer/login";export async function openWriter(t){await t.goto(HOME);await t.wait(2)}export async function api(t,r,e,n={}){const i=await t.evaluate(`(async ({ method, path, params }) => {
|
|
2
|
+
const common = { aid: '2503', app_name: 'muye_novel' };
|
|
3
|
+
const flat = {};
|
|
4
|
+
for (const [k, v] of Object.entries({ ...common, ...params })) flat[k] = String(v);
|
|
5
|
+
const init = { credentials: 'include', headers: { accept: 'application/json, text/plain, */*' } };
|
|
6
|
+
let url = path;
|
|
7
|
+
if (method === 'GET') {
|
|
8
|
+
url += '?' + new URLSearchParams(flat).toString();
|
|
9
|
+
} else {
|
|
10
|
+
const head = await fetch(path, { method: 'HEAD', credentials: 'include', headers: { 'x-secsdk-csrf-request': '1', 'x-secsdk-csrf-version': '1.2.22' } });
|
|
11
|
+
const ware = (head.headers.get('x-ware-csrf-token') || '').split(',');
|
|
12
|
+
if (!ware[1]) return { __err: 'csrf', detail: head.headers.get('x-ware-csrf-token') || ('HTTP ' + head.status) };
|
|
13
|
+
init.method = 'POST';
|
|
14
|
+
init.headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
15
|
+
init.headers['x-secsdk-csrf-token'] = ware[1];
|
|
16
|
+
init.body = new URLSearchParams(flat).toString();
|
|
17
|
+
}
|
|
18
|
+
const res = await fetch(url, init);
|
|
19
|
+
const text = await res.text();
|
|
20
|
+
try { return { status: res.status, body: JSON.parse(text) }; }
|
|
21
|
+
catch { return { status: res.status, raw: text.slice(0, 200) }; }
|
|
22
|
+
})(${JSON.stringify({method:r,path:e,params:n})})`);if(!i)throw new a(`番茄作家后台 ${e}:页面内请求没有返回结果`);if(i.__err==="csrf")throw new a(`番茄作家后台 ${e}:没拿到写操作令牌(${i.detail})`);if(!i.body)throw new a(`番茄作家后台 ${e}:返回不是 JSON(HTTP ${i.status}):${i.raw}`);const{code:s,message:c,data:d}=i.body;if(s===-3)throw new l(DOMAIN,"番茄作家后台未登录("+(c||"登录信息有误")+"),请先在 PublishPort 客户端登录番茄小说");if(s!==0)throw new a(`番茄作家后台 ${e} 返回错误 ${s}:${c||"未知错误"}`);return d}export function requireExecute(t,r){if(!t.execute)throw new o(`${r}需要 --execute 参数。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用`)}export function requireBook(t){const r=String(t.book??"").trim();if(!/^\d+$/.test(r))throw new o("--book 必须是作品 ID(数字),用 `ppcli fanqie books` 查");return r}export async function resolveText(t){const r=typeof t.text==="string"?t.text:void 0,e=typeof t.file==="string"?t.file:void 0;if(r&&e)throw new o("正文和 --file 只能二选一");let n=r??"";if(e){let i;try{i=await u(e)}catch{throw new o("文件不存在:"+e)}if(!i.isFile())throw new o("必须指定可读的文本文件:"+e);const s=await f(e);try{n=new TextDecoder("utf-8",{fatal:!0}).decode(s)}catch{throw new o("文件编码不是 UTF-8:"+e)}}if(!n.trim())throw new o("章节正文不能为空");return n}function w(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}export function toChapterHtml(t){const r=t.trim();if(/^<p[\s>]/i.test(r))return r;return r.split(/\r?\n/).map((e)=>e.trim()).filter((e)=>e&&e!=="---"&&!/^#{1,6}\s/.test(e)).map((e)=>"<p>"+w(e.replace(/\*\*/g,""))+"</p>").join("")}export function scheduleSeconds(t,r=Date.now()){if(t===void 0||t===null||t==="")return;const e=String(t).trim();let n;if(/^\d+$/.test(e))n=Number(e)*1000;else if(/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}$/.test(e))n=Date.parse(e.replace(" ","T")+":00+08:00");else if(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/i.test(e))n=Date.parse(e);else throw new o("--schedule 使用北京时间 YYYY-MM-DD HH:mm、带时区 ISO8601 或 Unix 秒");if(!Number.isFinite(n)||n<=r+60000)throw new o("--schedule 必须晚于当前时间");return Math.floor(n/1000)}export function beijingTime(t){const r=Number(t);if(!Number.isFinite(r)||r<=0)return"";return new Date(r*1000+28800000).toISOString().slice(0,16).replace("T"," ")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AuthRequiredError as r}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as s}from"../_shared/site-auth.js";import{DOMAIN as o,LOGIN_URL as m,api as c,openWriter as u}from"./_shared.js";async function n(i){return(await i.getCookies({url:"https://fanqienovel.com"})).some((e)=>e.name==="sessionid"&&e.value)}async function a(i){if(!await n(i))throw new r(o,"番茄作家后台未登录(缺少 sessionid)");await u(i);const t=await c(i,"GET","/api/author/account/info/v0/");return{name:String(t?.author_name??"")}}s({site:"fanqie",domain:o,loginUrl:m,columns:["name"],whoamiDescription:"查看当前登录的番茄小说作家账号(笔名)。",loginDescription:"打开番茄小说作家后台登录页并等待登录完成(供桌面客户端引导登录)。",quickCheck:n,verify:a,poll:async(i)=>{if(!await n(i))throw new r(o,"等待番茄作家后台登录");return a(i)}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as a,Strategy as e}from"@jackwener/opencli/registry";import{DOMAIN as r,api as n,openWriter as i}from"./_shared.js";a({site:"fanqie",name:"books",access:"read",description:"列出番茄小说作家账号下的全部作品(作品 ID、书名、字数、阅读数、连载状态),作品 ID 供 publish / chapters / delete 的 --book 使用。",domain:r,strategy:e.COOKIE,browser:!0,columns:["book_id","book_name","word_count","read_count","chapter_count","status"],func:async(t)=>{await i(t);return((await n(t,"GET","/api/author/book/book_list/v0",{page_index:0,page_count:50}))?.book_list??[]).map((o)=>({book_id:String(o.book_id),book_name:o.book_name??"",word_count:Number(o.word_count??0),read_count:Number(o.read_count??0),chapter_count:Number(o.chapter_number??0),status:Number(o.creation_status)===1?"连载中":"已完结"}))}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as n,Strategy as u}from"@jackwener/opencli/registry";import{DOMAIN as d,api as r,beijingTime as l,openWriter as c,requireBook as p}from"./_shared.js";const f={4:"审核中"};n({site:"fanqie",name:"chapters",access:"read",description:"列出番茄作品各分卷下的章节(章节 ID、标题、分卷、字数、状态、定时时间);--drafts 改看草稿箱。",domain:d,strategy:u.COOKIE,browser:!0,args:[{name:"book",required:!0,help:"作品 ID(`ppcli fanqie books` 查)"},{name:"drafts",type:"boolean",help:"只看草稿箱里未发布的章节"}],columns:["item_id","title","volume","word_count","status","timer_time"],func:async(i,s)=>{const o=p(s);await c(i);if(s.drafts)return((await r(i,"GET","/api/author/chapter/draft_list/v1",{book_id:o,page_index:0,page_count:100}))?.draft_list??[]).map((t)=>({item_id:String(t.item_id),title:t.title??"",volume:t.volume_name??"",word_count:Number(t.word_number??0),status:"草稿",timer_time:""}));const _=await r(i,"GET","/api/author/volume/volume_list/v1",{book_id:o}),m=[];for(const a of _?.volume_list??[]){const t=await r(i,"GET","/api/author/chapter/chapter_list/v1",{book_id:o,volume_id:a.volume_id,page_index:0,page_count:500,status:0,must_have_correction_feedback:0,need_correction_feedback_num:1,sort:""});for(const e of t?.item_list??[])m.push({item_id:String(e.item_id),title:e.title??"",volume:a.volume_name??"",word_count:Number(e.word_number??0),status:f[e.display_status]??`display_status=${e.display_status}`,timer_time:l(e.timer_time)})}return m}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as o,Strategy as a}from"@jackwener/opencli/registry";import{ArgumentError as m}from"@jackwener/opencli/errors";import{DOMAIN as n,api as d,openWriter as u,requireBook as s,requireExecute as c}from"./_shared.js";o({site:"fanqie",name:"delete",access:"write",description:"删除番茄作品里的一章(已发布、定时待发、草稿都可),删除后不可恢复。",domain:n,strategy:a.COOKIE,browser:!0,args:[{name:"item",positional:!0,required:!0,help:"章节 ID(`ppcli fanqie chapters` 查)"},{name:"book",required:!0,help:"作品 ID"},{name:"execute",type:"boolean",help:"真正执行删除;不加此参数命令拒绝写入"}],columns:["status","item_id","book_id"],func:async(i,e)=>{c(e,"删除番茄章节");const r=s(e),t=String(e.item??"").trim();if(!/^\d+$/.test(t))throw new m("章节 ID 必须是数字");await u(i);await d(i,"POST","/api/author/delete_article/v1",{item_id:t,book_id:r});return[{status:"deleted",item_id:t,book_id:r}]}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as f,Strategy as w}from"@jackwener/opencli/registry";import{ArgumentError as c,CommandExecutionError as p}from"@jackwener/opencli/errors";import{DOMAIN as S,api as n,beijingTime as h,openWriter as y,requireBook as I,requireExecute as k,resolveText as T,scheduleSeconds as x,toChapterHtml as q}from"./_shared.js";f({site:"fanqie",name:"publish",access:"write",description:"在番茄小说作品里发布一章:默认立即发布,--schedule 定时发布。正文可直接给或用 --file 读 UTF-8 文本 / Markdown,每个非空行成一段。完本模式的作品不允许定时发布。",domain:S,strategy:w.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"章节标题,带章节序号,如「第12章 夜雨」"},{name:"text",positional:!0,help:"章节正文(与 --file 二选一)"},{name:"file",help:"从文件读取章节正文(UTF-8)"},{name:"book",required:!0,help:"作品 ID(`ppcli fanqie books` 查)"},{name:"schedule",help:"定时发布:北京时间 YYYY-MM-DD HH:mm、带时区 ISO8601 或 Unix 秒(平台对部分作品不开放定时,会直接报错)"},{name:"volume",help:"分卷 ID(默认作品当前分卷;填了不存在的 ID 会报错并列出全部分卷)"},{name:"author-say",help:"作者有话说(附在章末)"},{name:"ai-generated",type:"boolean",help:"声明本章使用了 AI 辅助创作"},{name:"execute",type:"boolean",help:"真正执行写操作;不加此参数命令拒绝写入"}],columns:["status","item_id","title","book_id","publish_time","message"],func:async(a,i)=>{k(i,"发布番茄章节");const e=I(i),l=String(i.title??"").trim();if(!l)throw new c("章节标题不能为空");const v=q(await T(i)),o=x(i.schedule),_=String(i["author-say"]??"").trim();await y(a);const s=(await n(a,"GET","/api/author/volume/volume_list/v1",{book_id:e}))?.volume_list??[],m=i.volume?String(i.volume).trim():"";if(m&&!s.some((t)=>String(t.volume_id)===m))throw new c(`作品 ${e} 里没有分卷 ${m},可选:${s.map((t)=>`${t.volume_id}(${t.volume_name})`).join("、")||"无"}`);const d=await n(a,"POST","/api/author/article/new_article/v0/",{book_id:e,need_reuse:0}),r=String(d?.item_id??"");if(!r)throw new p(`作品 ${e} 新建章节失败:平台没有返回章节 ID`);const u=m||String(d.volume_id??""),b=s.find((t)=>String(t.volume_id)===u)?.volume_name??"";try{await n(a,"POST","/api/author/publish_article/v0/",{item_id:r,book_id:e,title:l,content:v,volume_id:u,volume_name:b,timer_status:o?1:0,timer_time:o??0,publish_status:1,need_pay:0,use_ai:i["ai-generated"]?1:2,device_platform:"pc",speak_type:_?1:0,speak_text:_,timer_chapter_preview:"[]",has_chapter_ad:"false",chapter_ad_types:""})}catch(t){await n(a,"POST","/api/author/delete_article/v1",{item_id:r,book_id:e});throw t}if(!((await n(a,"GET","/api/author/chapter/chapter_list/v1",{book_id:e,volume_id:u,page_index:0,page_count:500,status:0,must_have_correction_feedback:0,need_correction_feedback_num:1,sort:""}))?.item_list??[]).some((t)=>String(t.item_id)===r))throw new p(`番茄接口返回成功,但作品 ${e} 的章节列表里没有找到章节 ${r},请用 \`ppcli fanqie chapters --book ${e}\` 核对`);return[{status:o?"scheduled":"published",item_id:r,title:l,book_id:e,publish_time:o?h(o):"",message:o?`已设为定时发布(北京时间 ${h(o)}),平台审核通过后读者可见`:"已提交发布,平台审核通过后读者可见"}]}});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{readFile as l,stat as f}from"node:fs/promises";import{ArgumentError as i,AuthRequiredError as p,CommandExecutionError as c}from"@jackwener/opencli/errors";export const DOMAIN="zuozhe.qimao.com",HOME="https://zuozhe.qimao.com/front/index",LOGIN_URL="https://zuozhe.qimao.com/front/register-login/login";const d=new Set(["40002013","88888888","20002003"]);export async function openAuthor(t){await t.goto(HOME);for(let r=0;r<20;r++){if(await t.evaluate("(() => { const el = document.querySelector('#app'); return !!(el && el.__vue__ && el.__vue__.$store); })()"))return;await t.wait(0.5)}throw new c("七猫作家中心页面没有加载完成(找不到页面的请求入口)")}export async function call(t,r,e,o={}){const s=r==="post"?"postRequestNew":"getRequestNew",n=await t.evaluate(`(async ({ action, type, params }) => {
|
|
2
|
+
const el = document.querySelector('#app');
|
|
3
|
+
const store = el && el.__vue__ && el.__vue__.$store;
|
|
4
|
+
if (!store) return { __err: 'no-store' };
|
|
5
|
+
const res = await store.dispatch(action, { type, params });
|
|
6
|
+
return res ? JSON.parse(JSON.stringify(res)) : { __err: 'empty' };
|
|
7
|
+
})(${JSON.stringify({action:s,type:e,params:o})})`);if(!n||n.__err)throw new c(`七猫作家中心 ${e}:页面内请求失败(${n?.__err??"无返回"})`);if(n.errors){const a=String(n.errors.code??""),u=n.errors.title||n.errors.details||"未知错误";if(d.has(a))throw new p(DOMAIN,`七猫作家中心未登录(${u}),请先在 PublishPort 客户端登录七猫`);throw new c(`七猫作家中心 ${e} 返回错误 ${a}:${u}`)}return n.data}export function requireExecute(t,r){if(!t.execute)throw new i(`${r}需要 --execute 参数。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用`)}export function requireBook(t){const r=String(t.book??"").trim();if(!/^\d+$/.test(r))throw new i("--book 必须是作品 ID(数字),用 `ppcli qimao books` 查");return r}export async function resolveText(t){const r=typeof t.text==="string"?t.text:void 0,e=typeof t.file==="string"?t.file:void 0;if(r&&e)throw new i("正文和 --file 只能二选一");let o=r??"";if(e){let s;try{s=await f(e)}catch{throw new i("文件不存在:"+e)}if(!s.isFile())throw new i("必须指定可读的文本文件:"+e);const n=await l(e);try{o=new TextDecoder("utf-8",{fatal:!0}).decode(n)}catch{throw new i("文件编码不是 UTF-8:"+e)}}if(!o.trim())throw new i("章节正文不能为空");return o}function w(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}export function toChapterHtml(t){const r=t.trim();if(/^<p[\s>]/i.test(r))return r;return r.split(/\r?\n/).map((e)=>e.trim()).filter((e)=>e&&e!=="---"&&!/^#{1,6}\s/.test(e)).map((e)=>"<p>"+w(e.replace(/\*\*/g,""))+"</p>").join("")}export function scheduleSeconds(t,r=Date.now()){if(t===void 0||t===null||t==="")return;const e=String(t).trim();let o;if(/^\d+$/.test(e))o=Number(e)*1000;else if(/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}$/.test(e))o=Date.parse(e.replace(" ","T")+":00+08:00");else if(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/i.test(e))o=Date.parse(e);else throw new i("--schedule 使用北京时间 YYYY-MM-DD HH:mm、带时区 ISO8601 或 Unix 秒");if(!Number.isFinite(o)||o<=r+60000)throw new i("--schedule 必须晚于当前时间");return Math.floor(o/1000)}export function beijingTime(t){const r=Number(t);if(!Number.isFinite(r)||r<=0)return"";return new Date(r*1000+28800000).toISOString().slice(0,16).replace("T"," ")}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{AuthRequiredError as n}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as u}from"../_shared/site-auth.js";import{DOMAIN as o,LOGIN_URL as c,call as s,openAuthor as m}from"./_shared.js";async function i(t){return(await t.getCookies({url:"https://zuozhe.qimao.com"})).some((r)=>r.name==="author-token"&&r.value)}async function a(t){if(!await i(t))throw new n(o,"七猫作家中心未登录(缺少 author-token)");await m(t);await s(t,"get","BOOK_MANAGE_BOOK_LIST_V2",{next_id:""});const e=await t.evaluate(`(() => {
|
|
2
|
+
const u = document.querySelector('#app').__vue__.$store.state.globalData.user || {};
|
|
3
|
+
return { account_id: String(u.account_id || ''), pen_name: String(u.pen_name || '') };
|
|
4
|
+
})()`);if(!e.account_id)throw new n(o,"七猫作家中心未返回账号信息");return e}u({site:"qimao",domain:o,loginUrl:c,columns:["account_id","pen_name"],whoamiDescription:"查看当前登录的七猫作家账号(账号 ID、笔名)。",loginDescription:"打开七猫作家中心登录页并等待登录完成(供桌面客户端引导登录)。",quickCheck:i,verify:a,poll:async(t)=>{if(!await i(t))throw new n(o,"等待七猫作家中心登录");return a(t)}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as a,Strategy as n}from"@jackwener/opencli/registry";import{DOMAIN as l,call as _,openAuthor as c}from"./_shared.js";a({site:"qimao",name:"books",access:"read",description:"列出七猫作家账号下的全部小说(作品 ID、书名、总字数、最新章节、签约状态),作品 ID 供 publish / chapters / delete 的 --book 使用。",domain:l,strategy:n.COOKIE,browser:!0,columns:["book_id","title","total_words","latest_chapter","status","signed"],func:async(o)=>{await c(o);const s=[];let e="";for(let r=0;r<20;r++){const i=await _(o,"get","BOOK_MANAGE_BOOK_LIST_V2",{next_id:e});for(const t of i?.list??[])s.push({book_id:String(t.book_id),title:t.title??"",total_words:Number(t.total_words??0),latest_chapter:String(t.latest_chapter_desc??"").replace(/^最新章节:/,""),status:String(t.is_over)==="1"?"已完结":"连载中",signed:String(t.sign_status)==="0"?"未签约":"已签约"});e=String(i?.next_id??"");if(!e)break}return s}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as _,Strategy as n}from"@jackwener/opencli/registry";import{DOMAIN as p,call as s,openAuthor as d,requireBook as u}from"./_shared.js";export async function listChapters(e,a,o){if(o)return(await s(e,"get","BOOK_MANAGE_BOOK_CHAPTERS_LIST",{book_id:a,order_type:"2",is_draft:1,chapter_type:1}))?.list??[];const t=[];for(let r=1;r<=50;r++){const i=(await s(e,"get","BOOK_MANAGE_BOOK_CHAPTERS_LIST",{book_id:a,page:r,page_size:100,is_draft:0,order_type:"2",tab_type:"0",search_type:"1",chapter_name:"",volume_id:"0"}))?.list??[];t.push(...i);if(i.length<100)break}return t}_({site:"qimao",name:"chapters",access:"read",description:"列出七猫作品的章节(章节 ID、标题、字数、审核状态、发布时间);--drafts 改看草稿箱。",domain:p,strategy:n.COOKIE,browser:!0,args:[{name:"book",required:!0,help:"作品 ID(`ppcli qimao books` 查)"},{name:"drafts",type:"boolean",help:"只看草稿箱"}],columns:["chapter_id","title","word_count","status","publish_time","volume_id"],func:async(e,a)=>{const o=u(a);await d(e);return(await listChapters(e,o,Boolean(a.drafts))).map((t)=>({chapter_id:String(t.chapter_id),title:t.chapter_name_txt||t.chapter_name||"",word_count:Number(t.words_num??0),status:t.status_name||t.status_tooltip||"",publish_time:t.publish_time||"",volume_id:String(t.volume_id??"")}))}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as d,Strategy as p}from"@jackwener/opencli/registry";import{ArgumentError as n}from"@jackwener/opencli/errors";import{DOMAIN as m,call as u,openAuthor as h,requireBook as l,requireExecute as _}from"./_shared.js";import{listChapters as s}from"./chapters.js";d({site:"qimao",name:"delete",access:"write",description:"删除七猫作品里的一章草稿,删除后不可恢复。七猫不允许删除已发布 / 定时发布的章节。",domain:m,strategy:p.COOKIE,browser:!0,args:[{name:"chapter",positional:!0,required:!0,help:"草稿章节 ID(`ppcli qimao chapters --drafts` 查)"},{name:"book",required:!0,help:"作品 ID"},{name:"execute",type:"boolean",help:"真正执行删除;不加此参数命令拒绝写入"}],columns:["status","chapter_id","book_id"],func:async(r,i)=>{_(i,"删除七猫章节");const e=l(i),t=String(i.chapter??"").trim();if(!/^\d+$/.test(t))throw new n("章节 ID 必须是数字");await h(r);const a=(await s(r,e,!0)).find((o)=>String(o.chapter_id)===t);if(!a){const o=(await s(r,e,!1)).some((c)=>String(c.chapter_id)===t);throw new n(o?`章节 ${t} 已发布或在定时发布中,七猫不允许删除,只能在作家后台修改内容或定时时间`:`作品 ${e} 的草稿里没有章节 ${t}`)}await u(r,"post","BOOK_MANAGE_BOOK_DEL_CHAPTER",{book_id:e,volume_id:String(a.volume_id??"0"),chapter_id:t});return[{status:"deleted",chapter_id:t,book_id:e}]}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as x,Strategy as T}from"@jackwener/opencli/registry";import{ArgumentError as p,CommandExecutionError as S}from"@jackwener/opencli/errors";import{DOMAIN as q,beijingTime as y,call as O,openAuthor as M,requireBook as B,requireExecute as K,resolveText as C,scheduleSeconds as N,toChapterHtml as U}from"./_shared.js";import{listChapters as H}from"./chapters.js";x({site:"qimao",name:"publish",access:"write",description:"在七猫小说作品里上传一章:默认立即发布,--schedule 定时发布,--draft 只存草稿。正文可直接给或用 --file 读 UTF-8 文本 / Markdown,每个非空行成一段。七猫要求正文 1000~50000 字(草稿不限)、不能与已有章节重复;部分作品(如未签约新书)只允许定时发布。",domain:q,strategy:T.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"章节名,如「夜雨」或「第12章 夜雨」(带序号时按该序号上传)"},{name:"text",positional:!0,help:"章节正文(与 --file 二选一)"},{name:"file",help:"从文件读取章节正文(UTF-8)"},{name:"book",required:!0,help:"作品 ID(`ppcli qimao books` 查)"},{name:"draft",type:"boolean",help:"只存草稿,不发布"},{name:"schedule",help:"定时发布:北京时间 YYYY-MM-DD HH:mm、带时区 ISO8601 或 Unix 秒"},{name:"volume",help:"分卷 ID(默认最后一个分卷;填了不存在的 ID 会报错并列出全部分卷)"},{name:"author-say",help:"作者的话(附在章末)"},{name:"execute",type:"boolean",help:"真正执行写操作;不加此参数命令拒绝写入"}],columns:["status","chapter_id","title","book_id","word_count","publish_time","message"],func:async(o,t)=>{K(t,"上传七猫章节");const e=B(t),u=String(t.title??"").trim();if(!u)throw new p("章节名不能为空");if(t.draft&&t.schedule)throw new p("--draft 与 --schedule 不能同时使用");const A=U(await C(t)),n=N(t.schedule),i=t.draft?"3":n?"2":"1";await M(o);const d=await O(o,"get","BOOK_MANAGE_BOOK_CHAPTER_UPLOAD_INDEX",{book_id:e}),v=String(d?.book_info?.chapter_index_rule??"0"),c=v==="1"||v==="2",s=u.match(/^第\s*(\d+)\s*章\s*(.*)$/),E=s?s[1]:String(d?.chapter_info?.chapter_index??""),_=c&&s?s[2].trim():u;if(!_)throw new p("章节名不能只有序号");const l=(await O(o,"get","BOOK_MANAGE_VOLUME_LIST",{book_id:e}))?.list??[],h=t.volume?String(t.volume).trim():"",f=h?l.find((r)=>String(r.volume_id)===h):l[l.length-1];if(!f)throw new p(`作品 ${e} 里没有分卷 ${h||"(空)"},可选:${l.map((r)=>`${r.volume_id}(${r.volume_title})`).join("、")||"无"}`);const D=await o.evaluate("String(document.querySelector('#app').__vue__.$store.state.globalData.pc_version || '')"),b={book_id:e,chapter_id:"",chapter_type:"1",chapter_index:E,chapter_name:c?"":_,chapter_name_v2:c?_:"",is_vip:"",chapter_content:A,author_say:String(t["author-say"]??"").trim(),publish_type:i,volume_id:String(f.volume_id),volume_content_type:String(f.volume_content_type??"0"),disclaimer_type:String(d?.chapter_info?.disclaimer_type??"1"),pc_version:D,aigc_list:[],aigc_apply_times:"0"};if(n)b.submit_time=String(n);if(i!=="3")b.is_update_chapter_name=0;const w=await O(o,"post",c?"BOOK_MANAGE_BOOK_CHAPTER_UPLOAD_V2":"BOOK_MANAGE_BOOK_CHAPTER_UPLOAD",b),a=String(w?.chapter_id??"");if(!a||a==="0")throw new S("七猫没有返回章节 ID,上传结果未知,请用 `ppcli qimao chapters` 核对后再决定是否重传");const m=(await H(o,e,i==="3")).find((r)=>String(r.chapter_id)===a);if(!m)throw new S(`七猫返回了章节 ${a},但在${i==="3"?"草稿箱":"章节列表"}里没有找到,请用 \`ppcli qimao chapters --book ${e}\` 核对`);const I={1:"published",2:"scheduled",3:"draft"}[i],g=i==="3"?"已存入草稿箱":i==="2"?`已设为定时发布(北京时间 ${y(n)});作品签约前章节显示为待审核`:"已提交发布;作品签约前章节显示为待审核";return[{status:I,chapter_id:a,title:m.chapter_name_txt||_,book_id:e,word_count:Number(w?.words_num??m.words_num??0),publish_time:n?y(n):m.publish_time||"",message:g}]}});
|
package/clis/xiaohongshu/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AuthRequiredError as n,CommandExecutionError as l}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as m}from"../_shared/site-auth.js";async function i(o){const
|
|
1
|
+
import{AuthRequiredError as n,CommandExecutionError as l}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as m}from"../_shared/site-auth.js";async function i(o){const a=await o.getCookies({url:"https://creator.xiaohongshu.com"}),r=new Set(a.filter((e)=>e.value).map((e)=>e.name));return r.has("web_session")&&(r.has("x-user-id-creator.xiaohongshu.com")||r.has("id_token"))}async function c(o){await o.goto("https://creator.xiaohongshu.com/new/home");const a=await o.evaluate(`
|
|
2
2
|
async () => {
|
|
3
3
|
try {
|
|
4
4
|
const resp = await fetch('/api/galaxy/creator/home/personal_info', { credentials: 'include' });
|
|
@@ -10,4 +10,4 @@ import{AuthRequiredError as n,CommandExecutionError as l}from"@jackwener/opencli
|
|
|
10
10
|
return [false, 0, null, String(error && error.message || error)];
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
`),[
|
|
13
|
+
`),[r,e,s,h]=Array.isArray(a)?a:[];if(!r){const u=s?.msg??h??`HTTP ${e??""}`;throw new n("creator.xiaohongshu.com",`Xiaohongshu creator profile requires login: ${u}`)}const t=s?.data;if(!t)throw new l("Xiaohongshu creator profile returned malformed personal_info payload");return{username:t.name??"",followers:t.fans_count??0}}m({site:"xiaohongshu",domain:"creator.xiaohongshu.com",loginUrl:"https://creator.xiaohongshu.com/",columns:["username","followers"],quickCheck:i,verify:c,poll:async(o)=>{if(!await i(o))throw new n("creator.xiaohongshu.com","Waiting for Xiaohongshu session cookies");return c(o)}});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output budget for `ppcli browser <session> state`.
|
|
3
|
+
*
|
|
4
|
+
* The DOM snapshot has no size bound of its own: it walks viewport + 2000px and
|
|
5
|
+
* prints every surviving node. On a busy page that is 40k–200k characters
|
|
6
|
+
* (GitHub trending ≈ 207k, Reddit ≈ 150k, Juejin ≈ 54k), and an agent pays for
|
|
7
|
+
* all of it on every `state` call even when it only needs the controls in view.
|
|
8
|
+
*
|
|
9
|
+
* Two steps, applied to the printed text only (refs are already annotated in the
|
|
10
|
+
* page, so every `[N]` stays clickable whether or not it is printed):
|
|
11
|
+
*
|
|
12
|
+
* 1. Lossless: drop attribute-less, text-less, ref-less wrapper lines
|
|
13
|
+
* (`<div />`, `<span />`, …) that have at most one child, and pull their
|
|
14
|
+
* subtree up one level. Grouping is untouched — a bare wrapper with two or
|
|
15
|
+
* more children is kept because it is the only thing telling siblings apart.
|
|
16
|
+
* 2. Budget: if the result is still over `maxChars`, keep the header, the tree
|
|
17
|
+
* prefix that fits, and the footer sections (compounds limited to the refs
|
|
18
|
+
* still shown), plus one line saying what was cut and how to reach it.
|
|
19
|
+
*
|
|
20
|
+
* `maxChars <= 0` disables step 2.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_STATE_MAX_CHARS = 12000;
|
|
23
|
+
/** Step 1 — remove single-child anonymous wrappers, re-indenting what they held. */
|
|
24
|
+
export declare function collapseBareWrappers(tree: string[]): string[];
|
|
25
|
+
/** Apply both steps. Unknown formats (no `---` sections) are only cut at a line boundary. */
|
|
26
|
+
export declare function budgetStateText(text: string, maxChars: number, session?: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const DEFAULT_STATE_MAX_CHARS=12000;const B=/^ *<[a-z][a-z0-9-]* \/>$/,m=/\[(\d+)\]</,k=2,M=420;function O(o){let t=0;while(t<o.length&&o[t]===" ")t++;return t}export function collapseBareWrappers(o){const t=o.length,r=o.map(O),l=Array(t).fill(!1);for(let e=0;e<t;e++){if(!B.test(o[e]))continue;let f=0;for(let h=e+1;h<t&&r[h]>r[e];h++)if(r[h]===r[e]+k&&++f>1)break;if(f<=1)l[e]=!0}const i=[],s=[];for(let e=0;e<t;e++){while(s.length&&s[s.length-1]>=r[e])s.pop();if(l[e]){s.push(r[e]);continue}const f=Math.min(r[e],s.length*k);i.push(o[e].slice(f))}return i}function z(o){const t=o.split(`
|
|
2
|
+
`),r=t.indexOf("---");if(r<0)return null;let l=t.indexOf("---",r+1);if(l<0)l=t.length;const i=[];let s=null;for(let e=l;e<t.length;e++)if(t[e]==="---"){s=["---"];i.push(s)}else if(s)s.push(t[e]);return{header:t.slice(0,r+1),tree:t.slice(r+1,l),tail:i}}const p=(o)=>o.reduce((t,r)=>t+r.length+1,0);function A(o,t,r,l){const i=t.length?` (${t.length} with [N] refs)`:"";return`… truncated: ${o} more lines${i} not printed (--max-chars ${r}); those refs still work. To reach them: \`ppcli browser ${l} find --text <keyword>\` (or --role/--name, --css <selector>), \`scroll down\` then \`state\` again, \`extract\` for long text, or \`state --max-chars 0\` for the full tree.`}export function budgetStateText(o,t,r="<session>"){const l=z(o);if(!l){if(t<=0||o.length<=t)return o;const n=o.lastIndexOf(`
|
|
3
|
+
`,t),a=o.slice(0,n>0?n:t),u=o.slice(a.length).split(`
|
|
4
|
+
`).filter(Boolean),b=u.map((c)=>m.exec(c)?.[1]).filter((c)=>!!c).map(Number);return`${a}
|
|
5
|
+
${A(u.length,b,t,r)}`}const i=collapseBareWrappers(l.tree),s=[...l.header,...i,...l.tail.flat()].join(`
|
|
6
|
+
`);if(t<=0||s.length<=t)return s;const e=(n)=>n[1]?.startsWith("compounds (")===!0,f=l.tail.filter((n)=>!e(n)),h=p(l.tail.filter(e).flat()),N=t-p(l.header)-p(f.flat())-M-Math.min(h,Math.floor(t/4)),d=[];let g=0;for(const n of i){if(g+n.length+1>N)break;d.push(n);g+=n.length+1}const w=i.slice(d.length),T=w.map((n)=>m.exec(n)?.[1]).filter((n)=>!!n).map(Number),E=A(w.length,T,t,r),S=new Set(d.map((n)=>m.exec(n)?.[1]).filter(Boolean));let R=t-p(l.header)-g-E.length-1-p(f.flat());const _=l.tail.flatMap((n)=>{if(!e(n))return[n];const a=n.slice(2).filter((c)=>S.has(/^ {2}\[(\d+)\]/.exec(c)?.[1])),u=[];for(const c of a){if(c.length+1>R-80)break;u.push(c);R-=c.length+1}if(!a.length)return[];return[["---",`compounds (${u.length} shown of ${n.length-2}; others: \`find --css <selector>\` returns them inline):`,...u]]});return[...l.header,...d,E,..._.flat()].join(`
|
|
7
|
+
`)}
|