publishport-opencli 1.0.16 → 1.0.17

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 CHANGED
@@ -6147,35 +6147,152 @@
6147
6147
  },
6148
6148
  {
6149
6149
  "site": "boss",
6150
- "name": "batchgreet",
6151
- "description": "BOSS直聘批量向推荐候选人发送招呼",
6150
+ "name": "accept",
6151
+ "description": "BOSS直聘同意/拒绝 Boss 发来的交换请求(求职端,手机/微信/简历)",
6152
6152
  "access": "write",
6153
6153
  "domain": "www.zhipin.com",
6154
6154
  "strategy": "cookie",
6155
6155
  "browser": true,
6156
6156
  "args": [
6157
6157
  {
6158
- "name": "job-id",
6158
+ "name": "uid",
6159
+ "type": "str",
6160
+ "required": true,
6161
+ "positional": true,
6162
+ "help": "Boss 的 uid(数字 friendId 或 encryptFriendId,从 boss chatlist --side geek 拿)"
6163
+ },
6164
+ {
6165
+ "name": "reject",
6166
+ "type": "boolean",
6167
+ "default": false,
6168
+ "required": false,
6169
+ "help": "拒绝请求(默认同意)"
6170
+ },
6171
+ {
6172
+ "name": "resume-id",
6159
6173
  "type": "str",
6160
6174
  "default": "",
6161
6175
  "required": false,
6162
- "help": "Filter by encrypted job ID (greet all jobs if empty)"
6176
+ "help": "HR 索要简历时指定发哪份 encryptResumeId(从 boss resumelist 拿)"
6177
+ }
6178
+ ],
6179
+ "columns": [
6180
+ "status",
6181
+ "detail"
6182
+ ],
6183
+ "type": "js",
6184
+ "modulePath": "boss/accept.js",
6185
+ "sourceFile": "boss/accept.js",
6186
+ "navigateBefore": false
6187
+ },
6188
+ {
6189
+ "site": "boss",
6190
+ "name": "applied",
6191
+ "description": "BOSS直聘查看已投递的职位(求职端)",
6192
+ "access": "read",
6193
+ "domain": "www.zhipin.com",
6194
+ "strategy": "cookie",
6195
+ "browser": true,
6196
+ "args": [
6197
+ {
6198
+ "name": "page",
6199
+ "type": "int",
6200
+ "default": 1,
6201
+ "required": false,
6202
+ "help": "Page number"
6163
6203
  },
6164
6204
  {
6165
6205
  "name": "limit",
6166
6206
  "type": "int",
6167
- "default": 5,
6207
+ "default": 30,
6208
+ "required": false,
6209
+ "help": "Number of results to return"
6210
+ }
6211
+ ],
6212
+ "columns": [
6213
+ "job_name",
6214
+ "company",
6215
+ "salary",
6216
+ "status",
6217
+ "time",
6218
+ "security_id"
6219
+ ],
6220
+ "type": "js",
6221
+ "modulePath": "boss/applied.js",
6222
+ "sourceFile": "boss/applied.js",
6223
+ "navigateBefore": false
6224
+ },
6225
+ {
6226
+ "site": "boss",
6227
+ "name": "batchgreet",
6228
+ "description": "BOSS直聘批量打招呼(求职端批量投递职位 / 招聘端批量招呼候选人)",
6229
+ "access": "write",
6230
+ "domain": "www.zhipin.com",
6231
+ "strategy": "cookie",
6232
+ "browser": true,
6233
+ "args": [
6234
+ {
6235
+ "name": "side",
6236
+ "type": "str",
6237
+ "default": "auto",
6238
+ "required": false,
6239
+ "help": "Identity side: auto (default), geek (job-seeker), boss (recruiter)",
6240
+ "choices": [
6241
+ "auto",
6242
+ "geek",
6243
+ "boss"
6244
+ ]
6245
+ },
6246
+ {
6247
+ "name": "security-ids",
6248
+ "type": "str",
6249
+ "default": "",
6250
+ "required": false,
6251
+ "help": "[geek] 逗号分隔的职位 securityId(先跑 boss search 拿 security_id 列)"
6252
+ },
6253
+ {
6254
+ "name": "lid",
6255
+ "type": "str",
6256
+ "default": "",
6168
6257
  "required": false,
6169
- "help": "Max candidates to greet"
6258
+ "help": "[geek] Lid parameter from search results (optional)"
6259
+ },
6260
+ {
6261
+ "name": "job-id",
6262
+ "type": "str",
6263
+ "default": "",
6264
+ "required": false,
6265
+ "help": "[boss] Filter by encrypted job ID (greet all jobs if empty)"
6170
6266
  },
6171
6267
  {
6172
6268
  "name": "text",
6173
6269
  "type": "str",
6174
6270
  "default": "",
6175
6271
  "required": false,
6176
- "help": "Custom greeting message (uses default if empty)",
6272
+ "help": "[boss] Custom greeting message (uses default if empty)",
6177
6273
  "fromFile": true
6178
6274
  },
6275
+ {
6276
+ "name": "limit",
6277
+ "type": "int",
6278
+ "default": 5,
6279
+ "required": false,
6280
+ "help": "Max targets to greet"
6281
+ },
6282
+ {
6283
+ "name": "delay",
6284
+ "type": "number",
6285
+ "default": 1.5,
6286
+ "required": false,
6287
+ "help": "Seconds to wait between greetings (风控节流,别调太低)"
6288
+ },
6289
+ {
6290
+ "name": "dry-run",
6291
+ "type": "boolean",
6292
+ "default": false,
6293
+ "required": false,
6294
+ "help": "只预览将要打招呼的目标,不实际发送"
6295
+ },
6179
6296
  {
6180
6297
  "name": "file",
6181
6298
  "type": "str",
@@ -6194,6 +6311,38 @@
6194
6311
  "sourceFile": "boss/batchgreet.js",
6195
6312
  "navigateBefore": false
6196
6313
  },
6314
+ {
6315
+ "site": "boss",
6316
+ "name": "bossinfo",
6317
+ "description": "BOSS直聘查看与某个 Boss 的会话上下文(求职端)",
6318
+ "access": "read",
6319
+ "domain": "www.zhipin.com",
6320
+ "strategy": "cookie",
6321
+ "browser": true,
6322
+ "args": [
6323
+ {
6324
+ "name": "uid",
6325
+ "type": "str",
6326
+ "required": true,
6327
+ "positional": true,
6328
+ "help": "Boss 的 uid(数字 friendId 或 encryptFriendId,从 boss chatlist --side geek 拿)"
6329
+ }
6330
+ ],
6331
+ "columns": [
6332
+ "name",
6333
+ "company",
6334
+ "job",
6335
+ "security_id",
6336
+ "encrypt_job_id",
6337
+ "phone",
6338
+ "wechat",
6339
+ "exchange"
6340
+ ],
6341
+ "type": "js",
6342
+ "modulePath": "boss/bossinfo.js",
6343
+ "sourceFile": "boss/bossinfo.js",
6344
+ "navigateBefore": false
6345
+ },
6197
6346
  {
6198
6347
  "site": "boss",
6199
6348
  "name": "chatlist",
@@ -6299,6 +6448,24 @@
6299
6448
  "sourceFile": "boss/chatmsg.js",
6300
6449
  "navigateBefore": false
6301
6450
  },
6451
+ {
6452
+ "site": "boss",
6453
+ "name": "cities",
6454
+ "description": "BOSS直聘列出支持的城市与城市代码",
6455
+ "access": "read",
6456
+ "domain": "www.zhipin.com",
6457
+ "strategy": "cookie",
6458
+ "browser": false,
6459
+ "args": [],
6460
+ "columns": [
6461
+ "city",
6462
+ "code"
6463
+ ],
6464
+ "type": "js",
6465
+ "modulePath": "boss/cities.js",
6466
+ "sourceFile": "boss/cities.js",
6467
+ "navigateBefore": false
6468
+ },
6302
6469
  {
6303
6470
  "site": "boss",
6304
6471
  "name": "detail",
@@ -6344,7 +6511,7 @@
6344
6511
  {
6345
6512
  "site": "boss",
6346
6513
  "name": "exchange",
6347
- "description": "BOSS直聘交换联系方式(请求手机/微信)",
6514
+ "description": "BOSS直聘请求交换联系方式(手机/微信,求职端与招聘端都支持)",
6348
6515
  "access": "write",
6349
6516
  "domain": "www.zhipin.com",
6350
6517
  "strategy": "cookie",
@@ -6355,7 +6522,7 @@
6355
6522
  "type": "str",
6356
6523
  "required": true,
6357
6524
  "positional": true,
6358
- "help": "Encrypted UID of the candidate"
6525
+ "help": "对方 uid:求职端=Boss 的 friendId/encryptFriendId;招聘端=候选人的 encryptUid"
6359
6526
  },
6360
6527
  {
6361
6528
  "name": "type",
@@ -6367,6 +6534,18 @@
6367
6534
  "phone",
6368
6535
  "wechat"
6369
6536
  ]
6537
+ },
6538
+ {
6539
+ "name": "side",
6540
+ "type": "str",
6541
+ "default": "auto",
6542
+ "required": false,
6543
+ "help": "Identity side: auto (default), geek (job-seeker), boss (recruiter)",
6544
+ "choices": [
6545
+ "auto",
6546
+ "geek",
6547
+ "boss"
6548
+ ]
6370
6549
  }
6371
6550
  ],
6372
6551
  "columns": [
@@ -6381,7 +6560,7 @@
6381
6560
  {
6382
6561
  "site": "boss",
6383
6562
  "name": "greet",
6384
- "description": "BOSS直聘向新候选人发送招呼(开始聊天)",
6563
+ "description": "BOSS直聘打招呼(求职端向 Boss 投递简历 / 招聘端向候选人发招呼)",
6385
6564
  "access": "write",
6386
6565
  "domain": "www.zhipin.com",
6387
6566
  "strategy": "cookie",
@@ -6390,28 +6569,49 @@
6390
6569
  {
6391
6570
  "name": "uid",
6392
6571
  "type": "str",
6393
- "required": true,
6572
+ "default": "",
6573
+ "required": false,
6394
6574
  "positional": true,
6395
- "help": "Encrypted UID of the candidate (from recommend)"
6575
+ "help": "[boss] Encrypted UID of the candidate (from recommend)"
6396
6576
  },
6397
6577
  {
6398
6578
  "name": "security-id",
6399
6579
  "type": "str",
6400
6580
  "required": true,
6401
- "help": "Security ID of the candidate"
6581
+ "help": "Security ID:求职端=职位的 securityId(boss search 结果);招聘端=候选人的 securityId"
6582
+ },
6583
+ {
6584
+ "name": "side",
6585
+ "type": "str",
6586
+ "default": "auto",
6587
+ "required": false,
6588
+ "help": "Identity side: auto (default), geek (job-seeker), boss (recruiter)",
6589
+ "choices": [
6590
+ "auto",
6591
+ "geek",
6592
+ "boss"
6593
+ ]
6594
+ },
6595
+ {
6596
+ "name": "lid",
6597
+ "type": "str",
6598
+ "default": "",
6599
+ "required": false,
6600
+ "help": "[geek] Lid parameter from search results (optional)"
6402
6601
  },
6403
6602
  {
6404
6603
  "name": "job-id",
6405
6604
  "type": "str",
6406
- "required": true,
6407
- "help": "Encrypted job ID"
6605
+ "default": "",
6606
+ "required": false,
6607
+ "help": "[boss] Encrypted job ID"
6408
6608
  },
6409
6609
  {
6410
6610
  "name": "text",
6411
6611
  "type": "str",
6412
6612
  "default": "",
6413
6613
  "required": false,
6414
- "help": "Custom greeting message (uses default template if empty)",
6614
+ "help": "[boss] Custom greeting message (uses default template if empty)",
6415
6615
  "fromFile": true
6416
6616
  },
6417
6617
  {
@@ -6431,6 +6631,79 @@
6431
6631
  "sourceFile": "boss/greet.js",
6432
6632
  "navigateBefore": false
6433
6633
  },
6634
+ {
6635
+ "site": "boss",
6636
+ "name": "history",
6637
+ "description": "BOSS直聘查看职位浏览历史(求职端)",
6638
+ "access": "read",
6639
+ "domain": "www.zhipin.com",
6640
+ "strategy": "cookie",
6641
+ "browser": true,
6642
+ "args": [
6643
+ {
6644
+ "name": "page",
6645
+ "type": "int",
6646
+ "default": 1,
6647
+ "required": false,
6648
+ "help": "Page number"
6649
+ },
6650
+ {
6651
+ "name": "limit",
6652
+ "type": "int",
6653
+ "default": 30,
6654
+ "required": false,
6655
+ "help": "Number of results to return"
6656
+ }
6657
+ ],
6658
+ "columns": [
6659
+ "job_name",
6660
+ "company",
6661
+ "salary",
6662
+ "area",
6663
+ "boss",
6664
+ "security_id",
6665
+ "url"
6666
+ ],
6667
+ "type": "js",
6668
+ "modulePath": "boss/history.js",
6669
+ "sourceFile": "boss/history.js",
6670
+ "navigateBefore": false
6671
+ },
6672
+ {
6673
+ "site": "boss",
6674
+ "name": "interviews",
6675
+ "description": "BOSS直聘查看面试(求职端=收到的邀请 / 招聘端=我发出的面试)",
6676
+ "access": "read",
6677
+ "domain": "www.zhipin.com",
6678
+ "strategy": "cookie",
6679
+ "browser": true,
6680
+ "args": [
6681
+ {
6682
+ "name": "side",
6683
+ "type": "str",
6684
+ "default": "auto",
6685
+ "required": false,
6686
+ "help": "Identity side: auto (default), geek (job-seeker), boss (recruiter)",
6687
+ "choices": [
6688
+ "auto",
6689
+ "geek",
6690
+ "boss"
6691
+ ]
6692
+ }
6693
+ ],
6694
+ "columns": [
6695
+ "job_name",
6696
+ "company",
6697
+ "time",
6698
+ "address",
6699
+ "contact",
6700
+ "status"
6701
+ ],
6702
+ "type": "js",
6703
+ "modulePath": "boss/interviews.js",
6704
+ "sourceFile": "boss/interviews.js",
6705
+ "navigateBefore": false
6706
+ },
6434
6707
  {
6435
6708
  "site": "boss",
6436
6709
  "name": "invite",
@@ -6531,7 +6804,7 @@
6531
6804
  {
6532
6805
  "site": "boss",
6533
6806
  "name": "mark",
6534
- "description": "BOSS直聘给候选人添加标签",
6807
+ "description": "BOSS直聘给会话打标签(求职端标记 Boss / 招聘端标记候选人)",
6535
6808
  "access": "write",
6536
6809
  "domain": "www.zhipin.com",
6537
6810
  "strategy": "cookie",
@@ -6542,7 +6815,7 @@
6542
6815
  "type": "str",
6543
6816
  "required": true,
6544
6817
  "positional": true,
6545
- "help": "Encrypted UID of the candidate"
6818
+ "help": "对方 uid(从 boss chatlist 拿)"
6546
6819
  },
6547
6820
  {
6548
6821
  "name": "label",
@@ -6556,6 +6829,18 @@
6556
6829
  "default": false,
6557
6830
  "required": false,
6558
6831
  "help": "Remove the label instead of adding"
6832
+ },
6833
+ {
6834
+ "name": "side",
6835
+ "type": "str",
6836
+ "default": "auto",
6837
+ "required": false,
6838
+ "help": "Identity side: auto (default), geek (job-seeker), boss (recruiter)",
6839
+ "choices": [
6840
+ "auto",
6841
+ "geek",
6842
+ "boss"
6843
+ ]
6559
6844
  }
6560
6845
  ],
6561
6846
  "columns": [
@@ -6567,30 +6852,94 @@
6567
6852
  "sourceFile": "boss/mark.js",
6568
6853
  "navigateBefore": false
6569
6854
  },
6855
+ {
6856
+ "site": "boss",
6857
+ "name": "me",
6858
+ "description": "BOSS直聘查看我的资料与求职期望(求职端)",
6859
+ "access": "read",
6860
+ "domain": "www.zhipin.com",
6861
+ "strategy": "cookie",
6862
+ "browser": true,
6863
+ "args": [
6864
+ {
6865
+ "name": "expect-id",
6866
+ "type": "str",
6867
+ "default": "",
6868
+ "required": false,
6869
+ "help": "encryptExpectId(不传则从简历页自动抠)"
6870
+ }
6871
+ ],
6872
+ "columns": [
6873
+ "name",
6874
+ "gender",
6875
+ "age",
6876
+ "degree",
6877
+ "work_years",
6878
+ "start_work",
6879
+ "hometown",
6880
+ "expect_position",
6881
+ "expect_city",
6882
+ "expect_salary",
6883
+ "expect_industry",
6884
+ "done_positions",
6885
+ "interest_positions",
6886
+ "interest_cities"
6887
+ ],
6888
+ "type": "js",
6889
+ "modulePath": "boss/me.js",
6890
+ "sourceFile": "boss/me.js",
6891
+ "navigateBefore": false
6892
+ },
6570
6893
  {
6571
6894
  "site": "boss",
6572
6895
  "name": "recommend",
6573
- "description": "BOSS直聘查看推荐候选人(新招呼列表)",
6896
+ "description": "BOSS直聘查看推荐(求职端=推荐职位 / 招聘端=推荐候选人)",
6574
6897
  "access": "read",
6575
6898
  "domain": "www.zhipin.com",
6576
6899
  "strategy": "cookie",
6577
6900
  "browser": true,
6578
6901
  "args": [
6902
+ {
6903
+ "name": "side",
6904
+ "type": "str",
6905
+ "default": "auto",
6906
+ "required": false,
6907
+ "help": "Identity side: auto (default), geek (job-seeker), boss (recruiter)",
6908
+ "choices": [
6909
+ "auto",
6910
+ "geek",
6911
+ "boss"
6912
+ ]
6913
+ },
6579
6914
  {
6580
6915
  "name": "limit",
6581
6916
  "type": "int",
6582
6917
  "default": 20,
6583
6918
  "required": false,
6584
6919
  "help": "Number of results to return"
6920
+ },
6921
+ {
6922
+ "name": "page",
6923
+ "type": "int",
6924
+ "default": 1,
6925
+ "required": false,
6926
+ "help": "[geek] Page number"
6585
6927
  }
6586
6928
  ],
6587
6929
  "columns": [
6588
6930
  "name",
6931
+ "salary",
6932
+ "company",
6933
+ "area",
6934
+ "experience",
6935
+ "degree",
6936
+ "boss",
6937
+ "security_id",
6938
+ "url",
6589
6939
  "job_name",
6590
6940
  "last_time",
6591
6941
  "labels",
6592
6942
  "encrypt_uid",
6593
- "security_id",
6594
6943
  "encrypt_job_id"
6595
6944
  ],
6596
6945
  "type": "js",
@@ -6632,6 +6981,95 @@
6632
6981
  "sourceFile": "boss/resume.js",
6633
6982
  "navigateBefore": false
6634
6983
  },
6984
+ {
6985
+ "site": "boss",
6986
+ "name": "resumedelete",
6987
+ "description": "BOSS直聘删除账号里的一份附件简历(求职端)",
6988
+ "access": "write",
6989
+ "domain": "www.zhipin.com",
6990
+ "strategy": "cookie",
6991
+ "browser": true,
6992
+ "args": [
6993
+ {
6994
+ "name": "resume-id",
6995
+ "type": "str",
6996
+ "required": true,
6997
+ "positional": true,
6998
+ "help": "encryptResumeId(从 boss resumelist 拿)"
6999
+ }
7000
+ ],
7001
+ "columns": [
7002
+ "status",
7003
+ "detail"
7004
+ ],
7005
+ "type": "js",
7006
+ "modulePath": "boss/resumedelete.js",
7007
+ "sourceFile": "boss/resumedelete.js",
7008
+ "navigateBefore": false
7009
+ },
7010
+ {
7011
+ "site": "boss",
7012
+ "name": "resumelist",
7013
+ "description": "BOSS直聘列出账号绑定的附件简历(求职端)",
7014
+ "access": "read",
7015
+ "domain": "www.zhipin.com",
7016
+ "strategy": "cookie",
7017
+ "browser": true,
7018
+ "args": [],
7019
+ "columns": [
7020
+ "resume_id",
7021
+ "name",
7022
+ "suffix",
7023
+ "size",
7024
+ "upload_time"
7025
+ ],
7026
+ "type": "js",
7027
+ "modulePath": "boss/resumelist.js",
7028
+ "sourceFile": "boss/resumelist.js",
7029
+ "navigateBefore": false
7030
+ },
7031
+ {
7032
+ "site": "boss",
7033
+ "name": "resumeupload",
7034
+ "description": "BOSS直聘上传附件简历到账号库(求职端)",
7035
+ "access": "write",
7036
+ "domain": "www.zhipin.com",
7037
+ "strategy": "cookie",
7038
+ "browser": true,
7039
+ "args": [
7040
+ {
7041
+ "name": "file",
7042
+ "type": "str",
7043
+ "required": true,
7044
+ "positional": true,
7045
+ "help": "本地简历文件路径(pdf/doc/docx/ppt/pptx/png/jpg,≤10MB)"
7046
+ },
7047
+ {
7048
+ "name": "file-type",
7049
+ "type": "int",
7050
+ "default": 1,
7051
+ "required": false,
7052
+ "help": "1=普通简历(默认),2=作品集"
7053
+ },
7054
+ {
7055
+ "name": "annex-type",
7056
+ "type": "int",
7057
+ "default": 1,
7058
+ "required": false,
7059
+ "help": "save 时的 annexType,1=普通简历(默认)"
7060
+ }
7061
+ ],
7062
+ "columns": [
7063
+ "status",
7064
+ "resume_id",
7065
+ "name",
7066
+ "detail"
7067
+ ],
7068
+ "type": "js",
7069
+ "modulePath": "boss/resumeupload.js",
7070
+ "sourceFile": "boss/resumeupload.js",
7071
+ "navigateBefore": false
7072
+ },
6635
7073
  {
6636
7074
  "site": "boss",
6637
7075
  "name": "search",
@@ -6726,7 +7164,7 @@
6726
7164
  {
6727
7165
  "site": "boss",
6728
7166
  "name": "send",
6729
- "description": "BOSS直聘发送聊天消息",
7167
+ "description": "BOSS直聘发送聊天消息(求职端回复 Boss / 招聘端回复候选人)",
6730
7168
  "access": "write",
6731
7169
  "domain": "www.zhipin.com",
6732
7170
  "strategy": "cookie",
@@ -6737,7 +7175,7 @@
6737
7175
  "type": "str",
6738
7176
  "required": true,
6739
7177
  "positional": true,
6740
- "help": "Encrypted UID of the candidate (from chatlist)"
7178
+ "help": "对方 uid(从 boss chatlist 拿)"
6741
7179
  },
6742
7180
  {
6743
7181
  "name": "text",
@@ -6748,6 +7186,18 @@
6748
7186
  "fromFile": true,
6749
7187
  "requiredUnlessFile": true
6750
7188
  },
7189
+ {
7190
+ "name": "side",
7191
+ "type": "str",
7192
+ "default": "auto",
7193
+ "required": false,
7194
+ "help": "Identity side: auto (default), geek (job-seeker), boss (recruiter)",
7195
+ "choices": [
7196
+ "auto",
7197
+ "geek",
7198
+ "boss"
7199
+ ]
7200
+ },
6751
7201
  {
6752
7202
  "name": "file",
6753
7203
  "type": "str",
@@ -6765,6 +7215,39 @@
6765
7215
  "sourceFile": "boss/send.js",
6766
7216
  "navigateBefore": false
6767
7217
  },
7218
+ {
7219
+ "site": "boss",
7220
+ "name": "sendresume",
7221
+ "description": "BOSS直聘向 Boss 发送附件简历(求职端)",
7222
+ "access": "write",
7223
+ "domain": "www.zhipin.com",
7224
+ "strategy": "cookie",
7225
+ "browser": true,
7226
+ "args": [
7227
+ {
7228
+ "name": "uid",
7229
+ "type": "str",
7230
+ "required": true,
7231
+ "positional": true,
7232
+ "help": "Boss 的 uid(数字 friendId 或 encryptFriendId,从 boss chatlist --side geek 拿)"
7233
+ },
7234
+ {
7235
+ "name": "resume-id",
7236
+ "type": "str",
7237
+ "default": "",
7238
+ "required": false,
7239
+ "help": "encryptResumeId(从 boss resumelist 拿)。账号只有一份时可不传"
7240
+ }
7241
+ ],
7242
+ "columns": [
7243
+ "status",
7244
+ "detail"
7245
+ ],
7246
+ "type": "js",
7247
+ "modulePath": "boss/sendresume.js",
7248
+ "sourceFile": "boss/sendresume.js",
7249
+ "navigateBefore": false
7250
+ },
6768
7251
  {
6769
7252
  "site": "boss",
6770
7253
  "name": "stats",
@@ -6795,6 +7278,36 @@
6795
7278
  "sourceFile": "boss/stats.js",
6796
7279
  "navigateBefore": false
6797
7280
  },
7281
+ {
7282
+ "site": "boss",
7283
+ "name": "unread",
7284
+ "description": "BOSS直聘查看未回复的消息(求职端,Boss 发来但我还没回)",
7285
+ "access": "read",
7286
+ "domain": "www.zhipin.com",
7287
+ "strategy": "cookie",
7288
+ "browser": true,
7289
+ "args": [
7290
+ {
7291
+ "name": "limit",
7292
+ "type": "int",
7293
+ "default": 50,
7294
+ "required": false,
7295
+ "help": "How many recent conversations to scan"
7296
+ }
7297
+ ],
7298
+ "columns": [
7299
+ "name",
7300
+ "company",
7301
+ "job",
7302
+ "last_msg",
7303
+ "last_time",
7304
+ "uid"
7305
+ ],
7306
+ "type": "js",
7307
+ "modulePath": "boss/unread.js",
7308
+ "sourceFile": "boss/unread.js",
7309
+ "navigateBefore": false
7310
+ },
6798
7311
  {
6799
7312
  "site": "boss",
6800
7313
  "name": "whoami",
@@ -0,0 +1 @@
1
+ import{cli as W,Strategy as X}from"@jackwener/opencli/registry";import{CommandExecutionError as O,EmptyResultError as Y}from"@jackwener/opencli/errors";import{requirePage as _,navigateToGeekChat as B,geekPost as H,fetchGeekBossData as S,fetchGeekLastMsgs as j,resolveGeekBossId as q,resolveGeekResumeId as f,verbose as K}from"./utils.js";const C=["是否同意","交换","附件简历","联系方式"];W({site:"boss",name:"accept",access:"write",description:"BOSS直聘同意/拒绝 Boss 发来的交换请求(求职端,手机/微信/简历)",domain:"www.zhipin.com",strategy:X.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Boss 的 uid(数字 friendId 或 encryptFriendId,从 boss chatlist --side geek 拿)"},{name:"reject",type:"boolean",default:!1,help:"拒绝请求(默认同意)"},{name:"resume-id",default:"",help:"HR 索要简历时指定发哪份 encryptResumeId(从 boss resumelist 拿)"}],columns:["status","detail"],func:async(F,Z)=>{_(F);await B(F);const z=await q(F,Z.uid),L=await j(F,[z]);if(L.length===0)throw new Y("boss accept",`找不到与 uid=${z} 的消息记录`);const M=L[0].lastMsgInfo||{},Q=M.msgId,J=M.showText||"";if(!Q)throw new O("无法获取消息 ID(lastMsgInfo.msgId 缺失)");if(!C.some((T)=>J.includes(T)))throw new O(`最新消息不是交换请求,不处理:「${J.slice(0,40)}」`);const U=await S(F,z),V=U.name||U.bossName||z,$=U.securityId?{securityId:U.securityId}:{};if(Z.reject){K(`Rejecting exchange from boss ${z}...`);await H(F,"/wapi/zpchat/geek/rejectItemContact",{bossId:z,mid:Q,...$});return[{status:"✅ 已拒绝交换请求",detail:`已拒绝 ${V} 的请求:${J.slice(0,40)}`}]}if(J.includes("微信")){K(`Accepting wechat exchange from boss ${z}...`);await H(F,"/wapi/zpchat/geek/acceptItemWeiXinRequest",{bossId:z,mid:Q,...$});return[{status:"✅ 已同意交换微信",detail:`已同意 ${V} 的微信交换请求`}]}const N=J.includes("简历")||J.includes("附件"),A={bossId:z,mid:Q,...$};if(N)A.encryptResumeId=await f(F,Z["resume-id"]);K(`Accepting exchange from boss ${z}...`);await H(F,"/wapi/zpchat/geek/acceptItemContact",A);return[{status:"✅ 已同意交换请求",detail:N?`已向 ${V} 发送附件简历 ${A.encryptResumeId}`:`已同意 ${V} 的请求:${J.slice(0,40)}`}]}});
@@ -0,0 +1 @@
1
+ import{cli as n,Strategy as m}from"@jackwener/opencli/registry";import{EmptyResultError as p}from"@jackwener/opencli/errors";import{requirePage as l,navigateToGeekJob as u,geekGet as d,verbose as c}from"./utils.js";n({site:"boss",name:"applied",access:"read",description:"BOSS直聘查看已投递的职位(求职端)",domain:"www.zhipin.com",strategy:m.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"page",type:"int",default:1,help:"Page number"},{name:"limit",type:"int",default:30,help:"Number of results to return"}],columns:["job_name","company","salary","status","time","security_id"],func:async(a,r)=>{l(a);c("Fetching applied jobs...");await u(a);const s=(await d(a,"/wapi/zprelation/resume/geekDeliverList",{page:r.page||1})).cardList??[];if(s.length===0)throw new p("boss applied","暂无投递记录。");const i=r.limit||30;return s.slice(0,i).map((e)=>{const t=e.jobInfo??e,o=e.brandInfo??e;return{job_name:t.jobName||e.jobName||"",company:o.brandName||e.brandName||"",salary:t.salaryDesc||e.salaryDesc||"",status:e.deliverStatusDesc||e.statusDesc||"",time:e.updateTimeDesc||e.createTimeDesc||"",security_id:t.securityId||e.securityId||""}})}});
package/clis/boss/auth.js CHANGED
@@ -1,13 +1 @@
1
- import{AuthRequiredError as i,CommandExecutionError as a}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as c}from"../_shared/site-auth.js";async function s(t){const e=await t.getCookies({url:"https://www.zhipin.com"}),r=new Set(e.filter((o)=>o.value).map((o)=>o.name));return r.has("wt2")||r.has("t")}async function n(t){if(!await s(t))throw new i("zhipin.com","Boss wt2 / t cookies missing");await t.goto("https://www.zhipin.com/web/geek/job-recommend");await t.wait(3);const e=await t.evaluate(`
2
- (() => {
3
- const path = location.pathname || '';
4
- if (/\\/web\\/user\\/login|\\/login\\.html/.test(location.href)) {
5
- return { kind: 'auth', detail: 'Boss redirected to login page' };
6
- }
7
- const userType = /\\/web\\/geek\\//.test(path) ? 'geek' : /\\/web\\/(boss|recruit|chat\\/boss)/.test(path) ? 'recruiter' : '';
8
- if (!userType) {
9
- return { kind: 'auth', detail: 'Boss path does not look like authenticated geek/recruiter page: ' + path };
10
- }
11
- return { ok: true, user_type: userType };
12
- })()
13
- `);if(e?.kind==="auth")throw new i("zhipin.com",e.detail);if(!e?.ok)throw new a(`Unexpected Boss probe: ${JSON.stringify(e)}`);return{user_type:e.user_type}}c({site:"boss",domain:"zhipin.com",loginUrl:"https://login.zhipin.com/",columns:["user_type"],quickCheck:s,verify:n,poll:async(t)=>{if(!await s(t))throw new i("zhipin.com","Waiting for Boss wt2 / t cookies");return n(t)}});
1
+ import{AuthRequiredError as J,CommandExecutionError as Z}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as W}from"../_shared/site-auth.js";async function M(b){const D=await b.getCookies({url:"https://www.zhipin.com"}),F=new Set(D.filter((K)=>K.value).map((K)=>K.name));return F.has("wt2")||F.has("t")}const P=/login\.zhipin\.com|\/web\/user\/|\/login\.html/;function X(b){const D=(()=>{try{return new URL(b).pathname||""}catch{return String(b||"")}})();if(/^\/web\/geek\//.test(D))return"geek";if(/^\/web\/(boss|recruit)\//.test(D))return"recruiter";if(/^\/web\/chat\//.test(D))return"recruiter";return""}async function Q(b){const D=await b.evaluate("location.href");return typeof D==="string"?D:""}async function V(b){if(!await M(b))throw new J("zhipin.com","Boss wt2 / t cookies missing");await b.goto("https://www.zhipin.com/web/geek/job-recommend");await b.wait(3);const D=await Q(b);if(P.test(D))throw new J("zhipin.com","Boss redirected to login page");const F=X(D);if(!F)return{user_type:"unknown"};return{user_type:F}}W({site:"boss",domain:"zhipin.com",loginUrl:"https://login.zhipin.com/",columns:["user_type"],quickCheck:M,verify:V,poll:async(b)=>{if(!await M(b))throw new J("zhipin.com","Waiting for Boss wt2 / t cookies");const D=await Q(b);if(!D||P.test(D))throw new J("zhipin.com","Waiting for Boss to leave the login page");return V(b)}});
@@ -1 +1 @@
1
- import{cli as l,Strategy as u}from"@jackwener/opencli/registry";import{requirePage as f,navigateToChat as h,fetchRecommendList as b,clickCandidateInList as g,typeAndSendMessage as p,verbose as w}from"./utils.js";l({site:"boss",name:"batchgreet",access:"write",description:"BOSS直聘批量向推荐候选人发送招呼",domain:"www.zhipin.com",strategy:u.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"job-id",default:"",help:"Filter by encrypted job ID (greet all jobs if empty)"},{name:"limit",type:"int",default:5,help:"Max candidates to greet"},{name:"text",fromFile:!0,default:"",help:"Custom greeting message (uses default if empty)"}],columns:["name","status","detail"],func:async(t,n)=>{f(t);const c=n["job-id"]||"",d=n.limit||5,r=n.text||"你好,请问您对这个职位感兴趣吗?";w(`Batch greeting up to ${d} candidates...`);await h(t,3);let e=await b(t);if(c)e=e.filter((i)=>i.encryptJobId===c);e=e.slice(0,d);if(e.length===0)return[{name:"-",status:"⚠️ 无候选人",detail:"当前没有待招呼的推荐候选人"}];const a=[];for(const i of e){const m=i.uid,s=i.name||"候选人";try{if(!await g(t,m)){a.push({name:s,status:"❌ 跳过",detail:"在聊天列表中未找到"});continue}await t.wait({time:2});if(!await p(t,r)){a.push({name:s,status:"❌ 失败",detail:"找不到消息输入框"});continue}await t.wait({time:1.5});a.push({name:s,status:"✅ 已发送",detail:r})}catch(o){a.push({name:s,status:"❌ 失败",detail:o.message?.substring(0,80)||"未知错误"})}}return a}});
1
+ import{cli as F,Strategy as P}from"@jackwener/opencli/registry";import{ArgumentError as U}from"@jackwener/opencli/errors";import{requirePage as C,navigateToChat as L,navigateToGeekJob as M,fetchRecommendList as T,clickCandidateInList as j,typeAndSendMessage as x,geekAddFriend as z,verbose as $}from"./utils.js";async function q(D,B){const V=String(B["security-ids"]||"").split(",").map((H)=>H.trim()).filter(Boolean);if(V.length===0)throw new U("求职端批量投递需要 --security-ids(逗号分隔的职位 securityId,先跑 boss search 拿 security_id 列)");const X=B.limit||5,Q=V.slice(0,X),Z=Math.round((B.delay??1.5)*1000);if(B["dry-run"])return Q.map((H,K)=>({name:`#${K+1}`,status:"\uD83D\uDCCB 预览",detail:`将投递 securityId=${H}(未实际发送)`}));$(`Batch applying to ${Q.length} jobs...`);await M(D);const O=[];for(let H=0;H<Q.length;H++){const K=Q[H];try{const W=await z(D,K,B.lid||"");O.push({name:W?.bossName||K.slice(0,12),status:"✅ 已投递",detail:W?.jobName?`${W.jobName}`:K})}catch(W){O.push({name:K.slice(0,12),status:"❌ 失败",detail:W.message?.substring(0,80)||"未知错误"})}if(H<Q.length-1)await D.wait({time:Z/1000})}return O}async function E(D,B){const V=B["job-id"]||"",X=B.limit||5,Q=B.text||"你好,请问您对这个职位感兴趣吗?",Z=B.delay??1.5;$(`Batch greeting up to ${X} candidates...`);await L(D,3);let O=await T(D);if(V)O=O.filter((K)=>K.encryptJobId===V);O=O.slice(0,X);if(O.length===0)return[{name:"-",status:"⚠️ 无候选人",detail:"当前没有待招呼的推荐候选人"}];if(B["dry-run"])return O.map((K)=>({name:K.name||"候选人",status:"\uD83D\uDCCB 预览",detail:`将发送:${Q}(未实际发送)`}));const H=[];for(const K of O){const W=K.uid,Y=K.name||"候选人";try{if(!await j(D,W)){H.push({name:Y,status:"❌ 跳过",detail:"在聊天列表中未找到"});continue}await D.wait({time:2});if(!await x(D,Q)){H.push({name:Y,status:"❌ 失败",detail:"找不到消息输入框"});continue}await D.wait({time:Z});H.push({name:Y,status:"✅ 已发送",detail:Q})}catch(_){H.push({name:Y,status:"❌ 失败",detail:_.message?.substring(0,80)||"未知错误"})}}return H}F({site:"boss",name:"batchgreet",access:"write",description:"BOSS直聘批量打招呼(求职端批量投递职位 / 招聘端批量招呼候选人)",domain:"www.zhipin.com",strategy:P.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"side",default:"auto",choices:["auto","geek","boss"],help:"Identity side: auto (default), geek (job-seeker), boss (recruiter)"},{name:"security-ids",default:"",help:"[geek] 逗号分隔的职位 securityId(先跑 boss search 拿 security_id 列)"},{name:"lid",default:"",help:"[geek] Lid parameter from search results (optional)"},{name:"job-id",default:"",help:"[boss] Filter by encrypted job ID (greet all jobs if empty)"},{name:"text",fromFile:!0,default:"",help:"[boss] Custom greeting message (uses default if empty)"},{name:"limit",type:"int",default:5,help:"Max targets to greet"},{name:"delay",type:"number",default:1.5,help:"Seconds to wait between greetings (风控节流,别调太低)"},{name:"dry-run",type:"boolean",default:!1,help:"只预览将要打招呼的目标,不实际发送"}],columns:["name","status","detail"],func:async(D,B)=>{C(D);const V=B.side||"auto";if(V==="geek")return q(D,B);if(V==="boss")return E(D,B);return B["security-ids"]?q(D,B):E(D,B)}});
@@ -0,0 +1 @@
1
+ import{cli as a,Strategy as i}from"@jackwener/opencli/registry";import{requirePage as c,navigateToGeekChat as r,fetchGeekBossData as d,resolveGeekBossId as m,verbose as b}from"./utils.js";a({site:"boss",name:"bossinfo",access:"read",description:"BOSS直聘查看与某个 Boss 的会话上下文(求职端)",domain:"www.zhipin.com",strategy:i.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Boss 的 uid(数字 friendId 或 encryptFriendId,从 boss chatlist --side geek 拿)"}],columns:["name","company","job","security_id","encrypt_job_id","phone","wechat","exchange"],func:async(o,n)=>{c(o);await r(o);const t=await m(o,n.uid);b(`Fetching boss data for ${t}...`);const e=await d(o,t),s=e.exchange||{};return[{name:e.name||e.bossName||"",company:e.brandName||e.company||"",job:e.jobName||"",security_id:e.securityId||"",encrypt_job_id:e.encryptJobId||"",phone:s.phone||e.phone||"",wechat:s.weixin||s.wechat||e.weixin||"",exchange:Object.keys(s).length?JSON.stringify(s):""}]}});
@@ -0,0 +1 @@
1
+ import{cli as c,Strategy as i}from"@jackwener/opencli/registry";import{CITY_CODES as t}from"./utils.js";c({site:"boss",name:"cities",access:"read",description:"BOSS直聘列出支持的城市与城市代码",domain:"www.zhipin.com",strategy:i.COOKIE,navigateBefore:!1,browser:!1,args:[],columns:["city","code"],func:async()=>Object.entries(t).map(([e,s])=>({city:e,code:s}))});
@@ -1 +1 @@
1
- import{cli as c,Strategy as o}from"@jackwener/opencli/registry";import{requirePage as s,navigateToChat as h,bossFetch as d,findFriendByUid as p,verbose as u}from"./utils.js";c({site:"boss",name:"exchange",access:"write",description:"BOSS直聘交换联系方式(请求手机/微信)",domain:"www.zhipin.com",strategy:o.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",required:!0,positional:!0,help:"Encrypted UID of the candidate"},{name:"type",default:"phone",choices:["phone","wechat"],help:"Exchange type: phone or wechat"}],columns:["status","detail"],func:async(e,a)=>{s(e);const n=a.type||"phone";u(`Requesting ${n} exchange for ${a.uid}...`);await h(e);const t=await p(e,a.uid,{checkGreetList:!0});if(!t)throw Error("未找到该候选人");const i=t.name||"候选人",r=new URLSearchParams({type:String(n==="wechat"?2:1),securityId:t.securityId||"",uniqueId:String(t.uid),name:i});await d(e,"https://www.zhipin.com/wapi/zpchat/exchange/request",{method:"POST",body:r.toString()});return[{status:"✅ 交换请求已发送",detail:`已向 ${i} 发送${n==="wechat"?"微信":"手机号"}交换请求`}]}});
1
+ import{cli as Y,Strategy as Z}from"@jackwener/opencli/registry";import{CommandExecutionError as _}from"@jackwener/opencli/errors";import{requirePage as $,navigateToChat as q,navigateToGeekChat as U,bossFetch as E,findFriendByUid as F,geekPost as L,fetchGeekBossData as N,resolveGeekBossId as P,isRecruiterSide as S,verbose as Q}from"./utils.js";function V(z){return z==="wechat"?"微信":"手机号"}async function W(z,H){const J=H.type||"phone";await U(z);const K=await P(z,H.uid),M=await N(z,K),O=M.securityId||"";if(!O)throw new _(`无法获取 securityId (uid=${K}),该会话可能已失效`);Q(`Requesting ${J} exchange from boss ${K}...`);await L(z,"/wapi/zpchat/exchange/request",{type:J==="wechat"?2:1,bossId:K,securityId:O});return[{status:"✅ 交换请求已发送",detail:`已向 ${M.name||M.bossName||K} 请求交换${V(J)}`}]}async function X(z,H){const J=H.type||"phone";Q(`Requesting ${J} exchange for ${H.uid}...`);await q(z);const K=await F(z,H.uid,{checkGreetList:!0});if(!K)throw Error("未找到该候选人");const M=K.name||"候选人",O=new URLSearchParams({type:String(J==="wechat"?2:1),securityId:K.securityId||"",uniqueId:String(K.uid),name:M});await E(z,"https://www.zhipin.com/wapi/zpchat/exchange/request",{method:"POST",body:O.toString()});return[{status:"✅ 交换请求已发送",detail:`已向 ${M} 发送${V(J)}交换请求`}]}Y({site:"boss",name:"exchange",access:"write",description:"BOSS直聘请求交换联系方式(手机/微信,求职端与招聘端都支持)",domain:"www.zhipin.com",strategy:Z.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",required:!0,positional:!0,help:"对方 uid:求职端=Boss 的 friendId/encryptFriendId;招聘端=候选人的 encryptUid"},{name:"type",default:"phone",choices:["phone","wechat"],help:"Exchange type: phone or wechat"},{name:"side",default:"auto",choices:["auto","geek","boss"],help:"Identity side: auto (default), geek (job-seeker), boss (recruiter)"}],columns:["status","detail"],func:async(z,H)=>{$(z);const J=H.side||"auto";if(J==="geek")return W(z,H);if(J==="boss")return X(z,H);return await S(z)?X(z,H):W(z,H)}});
@@ -1 +1 @@
1
- import{cli as K,Strategy as O}from"@jackwener/opencli/registry";import{requirePage as Q,navigateToChat as R,findFriendByUid as V,clickCandidateInList as W,typeAndSendMessage as X,verbose as Y}from"./utils.js";K({site:"boss",name:"greet",access:"write",description:"BOSS直聘向新候选人发送招呼(开始聊天)",domain:"www.zhipin.com",strategy:O.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Encrypted UID of the candidate (from recommend)"},{name:"security-id",required:!0,help:"Security ID of the candidate"},{name:"job-id",required:!0,help:"Encrypted job ID"},{name:"text",fromFile:!0,default:"",help:"Custom greeting message (uses default template if empty)"}],columns:["status","detail"],func:async(z,D)=>{Q(z);Y(`Greeting candidate ${D.uid}...`);await R(z,3);const E=await V(z,D.uid,{maxPages:1,checkGreetList:!0});if(!E)throw Error("未找到该候选人,请确认 uid 是否正确(可从 recommend 命令获取)");const H=E.uid,J=E.name||"候选人";if(!await W(z,H))throw Error("无法在聊天列表中找到该用户,候选人可能不在当前列表中");await z.wait({time:2});const G=D.text||"你好,请问您对这个职位感兴趣吗?";if(!await X(z,G))throw Error("找不到消息输入框");await z.wait({time:1});return[{status:"✅ 招呼已发送",detail:`已向 ${J} 发送: ${G}`}]}});
1
+ import{cli as _,Strategy as $}from"@jackwener/opencli/registry";import{ArgumentError as V}from"@jackwener/opencli/errors";import{requirePage as h,navigateToChat as q,navigateToGeekJob as B,findFriendByUid as J,clickCandidateInList as L,typeAndSendMessage as M,geekAddFriend as N,verbose as W}from"./utils.js";async function X(D,H){const K=H["security-id"];if(!K)throw new V("求职端打招呼需要 --security-id(从 boss search / boss recommend 的 security_id 列拿)");W(`Greeting boss for job ${K}...`);await B(D);const O=await N(D,K,H.lid||"");return[{status:"✅ 打招呼成功",detail:O?.bossName?`已向 ${O.bossName} 打招呼,用 boss chatlist --side geek 看会话`:"已发送打招呼 / 投递,用 boss chatlist --side geek 看会话"}]}async function Y(D,H){if(!H.uid)throw new V("招聘端打招呼需要 uid 参数(候选人 encryptUid,从 boss recommend 拿)");W(`Greeting candidate ${H.uid}...`);await q(D,3);const K=await J(D,H.uid,{maxPages:1,checkGreetList:!0});if(!K)throw Error("未找到该候选人,请确认 uid 是否正确(可从 recommend 命令获取)");const O=K.uid,Z=K.name||"候选人";if(!await L(D,O))throw Error("无法在聊天列表中找到该用户,候选人可能不在当前列表中");await D.wait({time:2});const Q=H.text||"你好,请问您对这个职位感兴趣吗?";if(!await M(D,Q))throw Error("找不到消息输入框");await D.wait({time:1});return[{status:"✅ 招呼已发送",detail:`已向 ${Z} 发送: ${Q}`}]}_({site:"boss",name:"greet",access:"write",description:"BOSS直聘打招呼(求职端向 Boss 投递简历 / 招聘端向候选人发招呼)",domain:"www.zhipin.com",strategy:$.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,default:"",help:"[boss] Encrypted UID of the candidate (from recommend)"},{name:"security-id",required:!0,help:"Security ID:求职端=职位的 securityId(boss search 结果);招聘端=候选人的 securityId"},{name:"side",default:"auto",choices:["auto","geek","boss"],help:"Identity side: auto (default), geek (job-seeker), boss (recruiter)"},{name:"lid",default:"",help:"[geek] Lid parameter from search results (optional)"},{name:"job-id",default:"",help:"[boss] Encrypted job ID"},{name:"text",fromFile:!0,default:"",help:"[boss] Custom greeting message (uses default template if empty)"}],columns:["status","detail"],func:async(D,H)=>{h(D);const K=H.side||"auto";if(K==="geek")return X(D,H);if(K==="boss")return Y(D,H);return H.uid?Y(D,H):X(D,H)}});
@@ -0,0 +1 @@
1
+ import{cli as s,Strategy as a}from"@jackwener/opencli/registry";import{EmptyResultError as n}from"@jackwener/opencli/errors";import{requirePage as m,navigateToGeekJob as l,geekGet as c,verbose as b}from"./utils.js";s({site:"boss",name:"history",access:"read",description:"BOSS直聘查看职位浏览历史(求职端)",domain:"www.zhipin.com",strategy:a.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"page",type:"int",default:1,help:"Page number"},{name:"limit",type:"int",default:30,help:"Number of results to return"}],columns:["job_name","company","salary","area","boss","security_id","url"],func:async(t,i)=>{m(t);b("Fetching browsing history...");await l(t);const o=(await c(t,"/wapi/zpgeek/history/joblist.json",{page:i.page||1})).jobList??[];if(o.length===0)throw new n("boss history","暂无浏览历史。");const r=i.limit||30;return o.slice(0,r).map((e)=>({job_name:e.jobName||"",company:e.brandName||"",salary:e.salaryDesc||"",area:[e.cityName,e.areaDistrict,e.businessDistrict].filter(Boolean).join("·"),boss:[e.bossName,e.bossTitle].filter(Boolean).join(" · "),security_id:e.securityId||"",url:e.encryptJobId?`https://www.zhipin.com/job_detail/${e.encryptJobId}.html`:""}))}});
@@ -0,0 +1 @@
1
+ import{cli as M,Strategy as N}from"@jackwener/opencli/registry";import{EmptyResultError as D}from"@jackwener/opencli/errors";import{requirePage as O,navigateToChat as Q,navigateToGeekJob as U,geekGet as V,bossFetch as W,isRecruiterSide as X,verbose as H}from"./utils.js";function Y(f){return{job_name:f.jobName||"",company:f.brandName||"",time:f.interviewTime||f.startTimeDesc||"",address:f.address||f.addressText||"",contact:[f.contactName,f.contactPhone].filter(Boolean).join(" "),status:f.statusDesc||""}}function Z(f){return{job_name:f.jobName||"",company:f.geekName||"",time:f.interviewTime||f.startTimeDesc||"",address:f.address||f.addressText||"",contact:[f.contactName,f.contactPhone].filter(Boolean).join(" "),status:f.statusDesc||""}}async function K(f){H("Fetching interview invitations (geek side)...");await U(f);const A=await V(f,"/wapi/zpinterview/geek/interview/data.json"),x=A.interviewList??A.list??[];if(x.length===0)throw new D("boss interviews","暂无面试邀请。");return x.map(Y)}async function L(f){H("Fetching interview list (recruiter side)...");await Q(f);const A=await W(f,"https://www.zhipin.com/wapi/zpinterview/boss/interview/valid/list"),x=A.zpData?.list??A.zpData?.interviewList??[];if(x.length===0)throw new D("boss interviews","暂无面试安排。");return x.map(Z)}M({site:"boss",name:"interviews",access:"read",description:"BOSS直聘查看面试(求职端=收到的邀请 / 招聘端=我发出的面试)",domain:"www.zhipin.com",strategy:N.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"side",default:"auto",choices:["auto","geek","boss"],help:"Identity side: auto (default), geek (job-seeker), boss (recruiter)"}],columns:["job_name","company","time","address","contact","status"],func:async(f,A)=>{O(f);const x=A.side||"auto";if(x==="geek")return K(f);if(x==="boss")return L(f);return await X(f)?L(f):K(f)}});
package/clis/boss/mark.js CHANGED
@@ -1 +1 @@
1
- import{cli as b,Strategy as f}from"@jackwener/opencli/registry";import{requirePage as h,navigateToChat as p,bossFetch as w,findFriendByUid as S,verbose as $}from"./utils.js";import{ArgumentError as g,EmptyResultError as v}from"@jackwener/opencli/errors";const a={"新招呼":1,"沟通中":2,"已约面":3,"已获取简历":4,"已交换电话":5,"已交换微信":6,"不合适":7,"牛人发起":8,"收藏":11};b({site:"boss",name:"mark",access:"write",description:"BOSS直聘给候选人添加标签",domain:"www.zhipin.com",strategy:f.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Encrypted UID of the candidate"},{name:"label",required:!0,help:"Label name (新招呼/沟通中/已约面/已获取简历/已交换电话/已交换微信/不合适/收藏) or label ID"},{name:"remove",type:"boolean",default:!1,help:"Remove the label instead of adding"}],columns:["status","detail"],func:async(r,i)=>{h(r);const t=i.label,n=i.remove||!1;let e;if(a[t])e=a[t];else if(!isNaN(Number(t)))e=Number(t);else{const l=Object.entries(a).find(([u])=>u.includes(t));if(l)e=l[1];else throw new g(`未知标签: ${t}。可用标签: ${Object.keys(a).join(", ")}`)}$(`${n?"Removing":"Adding"} label ${e} for ${i.uid}...`);await p(r);const o=await S(r,i.uid,{checkGreetList:!0});if(!o)throw new v("boss candidate search");const s=o.name||"候选人",d=n?"deleteMark":"addMark",c=new URLSearchParams({friendId:String(o.uid),friendSource:String(o.friendSource??0),labelId:String(e)});await w(r,`https://www.zhipin.com/wapi/zprelation/friend/label/${d}?${c.toString()}`);const m=Object.entries(a).find(([,l])=>l===e)?.[0]||String(e);return[{status:n?"✅ 标签已移除":"✅ 标签已添加",detail:`${s}: ${n?"移除":"添加"}标签「${m}」`}]}});
1
+ import{cli as q,Strategy as U}from"@jackwener/opencli/registry";import{ArgumentError as C,EmptyResultError as F}from"@jackwener/opencli/errors";import{requirePage as T,navigateToChat as h,navigateToGeekChat as j,bossFetch as N,findFriendByUid as G,resolveGeekBoss as R,isRecruiterSide as S,verbose as Y}from"./utils.js";const O={"新招呼":1,"沟通中":2,"已约面":3,"已获取简历":4,"已交换电话":5,"已交换微信":6,"不合适":7,"牛人发起":8,"收藏":11};function M(x){if(O[x])return O[x];if(x!==""&&!isNaN(Number(x)))return Number(x);const D=Object.entries(O).find(([z])=>z.includes(x));if(D)return D[1];throw new C(`未知标签: ${x}。可用标签: ${Object.keys(O).join(", ")}`)}async function Z(x,{friendId:D,friendSource:z,labelId:H,remove:Q}){const V=Q?"deleteMark":"addMark",W=new URLSearchParams({friendId:String(D),friendSource:String(z??0),labelId:String(H)});await N(x,`https://www.zhipin.com/wapi/zprelation/friend/label/${V}?${W.toString()}`)}q({site:"boss",name:"mark",access:"write",description:"BOSS直聘给会话打标签(求职端标记 Boss / 招聘端标记候选人)",domain:"www.zhipin.com",strategy:U.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"对方 uid(从 boss chatlist 拿)"},{name:"label",required:!0,help:"Label name (新招呼/沟通中/已约面/已获取简历/已交换电话/已交换微信/不合适/收藏) or label ID"},{name:"remove",type:"boolean",default:!1,help:"Remove the label instead of adding"},{name:"side",default:"auto",choices:["auto","geek","boss"],help:"Identity side: auto (default), geek (job-seeker), boss (recruiter)"}],columns:["status","detail"],func:async(x,D)=>{T(x);const z=D.remove||!1,H=M(D.label),Q=Object.entries(O).find(([,J])=>J===H)?.[0]||String(H),V=D.side||"auto",W=async()=>{await j(x);const{id:J,friend:K}=await R(x,D.uid);Y(`${z?"Removing":"Adding"} label ${H} for boss ${J}...`);await Z(x,{friendId:J,friendSource:K?.friendSource??0,labelId:H,remove:z});return[{status:z?"✅ 标签已移除":"✅ 标签已添加",detail:`Boss ${J}: ${z?"移除":"添加"}标签「${Q}」`}]},X=async(J)=>{const K=J??await G(x,D.uid,{checkGreetList:!0});if(!K)throw new F("boss candidate search");const $=K.name||"候选人";Y(`${z?"Removing":"Adding"} label ${H} for ${D.uid}...`);await Z(x,{friendId:K.uid,friendSource:K.friendSource??0,labelId:H,remove:z});return[{status:z?"✅ 标签已移除":"✅ 标签已添加",detail:`${$}: ${z?"移除":"添加"}标签「${Q}」`}]};if(V==="geek")return W();if(V==="boss"){await h(x);return X(null)}return await S(x)?X(null):W()}});
@@ -0,0 +1,26 @@
1
+ import{cli as $,Strategy as q}from"@jackwener/opencli/registry";import{requirePage as A,navigateTo as D,geekGet as L,verbose as O}from"./utils.js";const M="https://www.zhipin.com/web/geek/resume";async function N(B){return await B.evaluate(`
2
+ (() => {
3
+ // 1. 页面自己发过的请求里带 id=<expectId>
4
+ try {
5
+ for (const entry of performance.getEntriesByType('resource')) {
6
+ if (!entry.name.includes('resume/expect/query')) continue;
7
+ const v = new URL(entry.name).searchParams.get('id');
8
+ if (v) return v;
9
+ }
10
+ } catch (_) {}
11
+ // 2. SSR / 内联 script 里的 expectId 字段
12
+ try {
13
+ for (const s of document.querySelectorAll('script:not([src])')) {
14
+ const t = s.textContent || '';
15
+ const m = t.match(/"(?:encryptExpectId|expectId)":"([^"]+)"/);
16
+ if (m) return m[1];
17
+ }
18
+ } catch (_) {}
19
+ // 3. DOM 上挂的 data-expect-id
20
+ try {
21
+ const el = document.querySelector('[data-expect-id]');
22
+ if (el) return el.getAttribute('data-expect-id');
23
+ } catch (_) {}
24
+ return '';
25
+ })()
26
+ `)||""}$({site:"boss",name:"me",access:"read",description:"BOSS直聘查看我的资料与求职期望(求职端)",domain:"www.zhipin.com",strategy:q.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"expect-id",default:"",help:"encryptExpectId(不传则从简历页自动抠)"}],columns:["name","gender","age","degree","work_years","start_work","hometown","expect_position","expect_city","expect_salary","expect_industry","done_positions","interest_positions","interest_cities"],func:async(B,J)=>{A(B);O("Fetching profile...");await D(B,M,2);const f=await L(B,"/wapi/zpgeek/resume/baseinfo/query.json");let h={};const K=J["expect-id"]||await N(B);if(K){const C=await L(B,"/wapi/zpgeek/resume/expect/query.json",{id:K},{allowNonZero:!0});h=C&&C.code===void 0?C.expect??C:{}}else O("expectId not found — skipping job expectation section");const H=(C)=>(C||[]).map((F)=>typeof F==="string"?F:F?.name||F?.positionName||F?.locationName||F?.cityName).filter(Boolean).join(" / "),Q=f.gender===1?"男":f.gender===2?"女":"",V=f.workYearDesc||(typeof f.workYears==="number"&&f.workYears>0?`${f.workYears}年`:""),W=[h.locationName,h.subLocationName].filter(Boolean).join(" "),X=h.industryDesc&&h.industryDesc!=="行业不限"?h.industryDesc:"";return[{name:f.name||f.nickName||f.geekName||"",gender:Q,age:f.ageDesc||(f.age?`${f.age}岁`:""),degree:f.degreeCategory||"",work_years:V,start_work:f.startWorkDateDesc||"",hometown:f.hometownName||"",expect_position:h.positionName||"",expect_city:W,expect_salary:h.salaryDescNew||h.salaryDesc||"",expect_industry:X,done_positions:H(h.doneWorkPositionList),interest_positions:H(h.interestPositionList),interest_cities:H(h.interestLocationList)}]}});
@@ -1 +1 @@
1
- import{cli as K,Strategy as N}from"@jackwener/opencli/registry";import{requirePage as O,navigateToChat as Q,bossFetch as U,fetchRecommendList as V,verbose as W}from"./utils.js";K({site:"boss",name:"recommend",access:"read",description:"BOSS直聘查看推荐候选人(新招呼列表)",domain:"www.zhipin.com",strategy:N.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"limit",type:"int",default:20,help:"Number of results to return"}],columns:["name","job_name","last_time","labels","encrypt_uid","security_id","encrypt_job_id"],func:async(z,H)=>{O(z);W("Fetching recommended candidates...");await Q(z);const A=await U(z,"https://www.zhipin.com/wapi/zprelation/friend/label/get",{allowNonZero:!0}),B={};if(A.code===0&&A.zpData?.labels)for(const x of A.zpData.labels)B[x.labelId]=x.label;const I=await V(z),J=H.limit||20;return I.slice(0,J).map((x)=>({name:x.name||"",job_name:x.jobName||"",last_time:x.lastTime||"",labels:(x.relationLabelList||[]).map((E)=>B[E]||String(E)).join(", "),encrypt_uid:x.encryptUid||"",security_id:x.securityId||"",encrypt_job_id:x.encryptJobId||""}))}});
1
+ import{cli as K,Strategy as P}from"@jackwener/opencli/registry";import{EmptyResultError as z}from"@jackwener/opencli/errors";import{requirePage as I,navigateToChat as N,navigateToGeekJob as T,bossFetch as U,fetchRecommendList as _,geekGet as h,isRecruiterSide as D,verbose as q}from"./utils.js";const G=["name","salary","company","area","experience","degree","boss","security_id","url"],L=["name","job_name","last_time","labels","encrypt_uid","security_id","encrypt_job_id"];async function B(A,H){q("Fetching recommended jobs (geek side)...");await T(A);const V=await h(A,"/wapi/zprelation/interaction/geekGetJob",{page:H.page||1,tag:5,isActive:"true"}),W=V.jobList??V.cardList??[];if(!Array.isArray(W)||W.length===0)throw new z("boss recommend","推荐职位为空。请先在 BOSS 直聘 App 或网页完善求职期望(岗位 + 城市),推荐流才会有数据。");const Y=H.limit||20;return W.slice(0,Y).map((Q)=>{const x=Q.jobInfo??Q,X=Q.brandInfo??Q,Z=Q.bossInfo??Q,$=x.encryptJobId||Q.encryptJobId||"";return{name:x.jobName||"",salary:x.salaryDesc||"",company:X.brandName||"",area:[x.cityName,x.areaDistrict,x.businessDistrict].filter(Boolean).join("·"),experience:x.jobExperience||x.experienceName||"",degree:x.jobDegree||x.degreeName||"",boss:[Z.bossName,Z.bossTitle].filter(Boolean).join(" · "),security_id:x.securityId||Q.securityId||"",url:$?`https://www.zhipin.com/job_detail/${$}.html`:""}})}async function F(A,H){q("Fetching recommended candidates (recruiter side)...");await N(A);const V=await U(A,"https://www.zhipin.com/wapi/zprelation/friend/label/get",{allowNonZero:!0}),W={};if(V.code===0&&V.zpData?.labels)for(const x of V.zpData.labels)W[x.labelId]=x.label;const Y=await _(A),Q=H.limit||20;return Y.slice(0,Q).map((x)=>({name:x.name||"",job_name:x.jobName||"",last_time:x.lastTime||"",labels:(x.relationLabelList||[]).map((X)=>W[X]||String(X)).join(", "),encrypt_uid:x.encryptUid||"",security_id:x.securityId||"",encrypt_job_id:x.encryptJobId||""}))}K({site:"boss",name:"recommend",access:"read",description:"BOSS直聘查看推荐(求职端=推荐职位 / 招聘端=推荐候选人)",domain:"www.zhipin.com",strategy:P.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"side",default:"auto",choices:["auto","geek","boss"],help:"Identity side: auto (default), geek (job-seeker), boss (recruiter)"},{name:"limit",type:"int",default:20,help:"Number of results to return"},{name:"page",type:"int",default:1,help:"[geek] Page number"}],columns:[...new Set([...G,...L])],func:async(A,H)=>{I(A);const V=H.side||"auto";if(V==="geek")return B(A,H);if(V==="boss")return F(A,H);return await D(A)?F(A,H):B(A,H)}});
@@ -0,0 +1 @@
1
+ import{cli as o,Strategy as n}from"@jackwener/opencli/registry";import{ArgumentError as u}from"@jackwener/opencli/errors";import{requirePage as c,navigateTo as d,geekPost as w,fetchGeekAttachmentResumes as h,verbose as l}from"./utils.js";o({site:"boss",name:"resumedelete",access:"write",description:"BOSS直聘删除账号里的一份附件简历(求职端)",domain:"www.zhipin.com",strategy:n.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"resume-id",positional:!0,required:!0,help:"encryptResumeId(从 boss resumelist 拿)"}],columns:["status","detail"],func:async(t,a)=>{c(t);const e=String(a["resume-id"]);await d(t,"https://www.zhipin.com/web/geek/resume",2);const r=await h(t),m=r.find((s)=>String(s.resumeId)===e);if(!m){const s=r.map((i)=>`${i.resumeId}(${i.showName||"未命名"})`).join(", ")||"(空)";throw new u(`resume-id=${e} 不在账号简历列表 [${s}] 中,没有执行删除`)}l(`Deleting attachment resume ${e}...`);await w(t,"/wapi/zpgeek/resume/attachment/delete.json",{resumeId:e});return[{status:"✅ 简历已删除",detail:`已删除附件简历「${m.showName||e}」`}]}});
@@ -0,0 +1 @@
1
+ import{cli as i,Strategy as m}from"@jackwener/opencli/registry";import{EmptyResultError as r}from"@jackwener/opencli/errors";import{requirePage as a,navigateTo as o,fetchGeekAttachmentResumes as u,verbose as n}from"./utils.js";i({site:"boss",name:"resumelist",access:"read",description:"BOSS直聘列出账号绑定的附件简历(求职端)",domain:"www.zhipin.com",strategy:m.COOKIE,navigateBefore:!1,browser:!0,args:[],columns:["resume_id","name","suffix","size","upload_time"],func:async(s)=>{a(s);n("Fetching attachment resumes...");await o(s,"https://www.zhipin.com/web/geek/resume",2);const t=await u(s);if(t.length===0)throw new r("boss resumelist","账号还没有绑定附件简历,请先在 BOSS 直聘 App 或网页简历页上传。");return t.map((e)=>({resume_id:e.resumeId||"",name:e.showName||"",suffix:e.suffixName||"",size:e.resumeSizeDesc||"",upload_time:e.uploadTime?new Date(e.uploadTime).toLocaleString("zh-CN"):""}))}});
@@ -0,0 +1,53 @@
1
+ import*as C from"node:fs";import*as $ from"node:path";import{cli as D,Strategy as X}from"@jackwener/opencli/registry";import{ArgumentError as O,CommandExecutionError as Q}from"@jackwener/opencli/errors";import{setInputFiles as Y}from"../_shared/file-inject.js";import{requirePage as h,navigateTo as N,geekPost as S,verbose as y}from"./utils.js";const A="https://www.zhipin.com/web/geek/resume",V="pp-boss-resume-upload",j=10485760,q={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg"};function F(J){const H=$.resolve(String(J).replace(/^~(?=$|\/)/,process.env.HOME||"~"));let z;try{z=C.statSync(H)}catch{throw new O(`文件不存在: ${H}`)}if(!z.isFile())throw new O(`不是一个文件: ${H}`);const K=$.extname(H).toLowerCase().replace(/^\./,"");if(!q[K])throw new O(`不支持的文件类型: .${K}(支持 ${Object.keys(q).sort().join("/")})`);if(z.size<=0)throw new O(`文件为空: ${H}`);if(z.size>j)throw new O(`文件过大: ${(z.size/1024/1024).toFixed(1)}MB(上限 ${j/1024/1024}MB)`);return{abs:H,name:$.basename(H),suffix:K,mime:q[K],size:z.size}}D({site:"boss",name:"resumeupload",access:"write",description:"BOSS直聘上传附件简历到账号库(求职端)",domain:"www.zhipin.com",strategy:X.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"file",positional:!0,required:!0,help:"本地简历文件路径(pdf/doc/docx/ppt/pptx/png/jpg,≤10MB)"},{name:"file-type",type:"int",default:1,help:"1=普通简历(默认),2=作品集"},{name:"annex-type",type:"int",default:1,help:"save 时的 annexType,1=普通简历(默认)"}],columns:["status","resume_id","name","detail"],func:async(J,H)=>{h(J);const z=F(H.file);y(`Uploading resume ${z.name} (${(z.size/1024).toFixed(0)}KB)...`);await N(J,A,2);await J.evaluate(`
2
+ (() => {
3
+ let el = document.getElementById(${JSON.stringify(V)});
4
+ if (!el) {
5
+ el = document.createElement('input');
6
+ el.type = 'file';
7
+ el.id = ${JSON.stringify(V)};
8
+ el.style.position = 'fixed';
9
+ el.style.left = '-9999px';
10
+ document.body.appendChild(el);
11
+ }
12
+ return true;
13
+ })()
14
+ `);try{await Y(J,`#${V}`,[z.abs])}catch(Z){throw new Q(`简历文件注入页面失败: ${Z instanceof Error?Z.message:String(Z)}`)}const K=await J.evaluate(`
15
+ (async () => {
16
+ const input = document.getElementById(${JSON.stringify(V)});
17
+ if (!input || !input.files || input.files.length === 0) {
18
+ return { ok: false, error: '文件未进入页面 input' };
19
+ }
20
+ const src = input.files[0];
21
+ const buf = await src.arrayBuffer();
22
+ // 按真实后缀重建 File,保证 MIME 正确(注入通道构造的 File 可能是 octet-stream)
23
+ const file = new File([buf], ${JSON.stringify(z.name)}, { type: ${JSON.stringify(z.mime)} });
24
+ const fd = new FormData();
25
+ fd.append('file', file, ${JSON.stringify(z.name)});
26
+ fd.append('fileType', ${JSON.stringify(String(H["file-type"]??1))});
27
+ const headers = { 'X-Requested-With': 'XMLHttpRequest' };
28
+ const bst = (document.cookie.match(/(?:^|;\\s*)bst=([^;]+)/) || [])[1];
29
+ if (bst) headers['zp_token'] = decodeURIComponent(bst);
30
+ try {
31
+ // 不设 Content-Type:让浏览器自己补 multipart boundary
32
+ const resp = await fetch('https://www.zhipin.com/wapi/zpupload/resume/uploadFile.json', {
33
+ method: 'POST',
34
+ credentials: 'include',
35
+ headers,
36
+ body: fd,
37
+ });
38
+ const text = await resp.text();
39
+ if (text.trim().startsWith('<')) {
40
+ return { ok: false, error: '收到 HTML 响应,登录态可能已失效' };
41
+ }
42
+ return { ok: true, status: resp.status, body: JSON.parse(text) };
43
+ } catch (e) {
44
+ return { ok: false, error: String(e && e.message || e) };
45
+ }
46
+ })()
47
+ `);await J.evaluate(`
48
+ (() => {
49
+ const el = document.getElementById(${JSON.stringify(V)});
50
+ if (el && el.parentNode) el.parentNode.removeChild(el);
51
+ return true;
52
+ })()
53
+ `);if(!K?.ok)throw new Q(`上传简历失败: ${K?.error||"未知错误"}`);const L=K.body||{};if(L.code!==0)throw new Q(`上传简历失败: ${L.message||"未知错误"} (code=${L.code})`);const G=L.zpData?.previewUrl,B=L.zpData?.attachmentName||z.name;if(!G)throw new Q(`上传响应缺少 previewUrl: ${JSON.stringify(L.zpData||{}).slice(0,200)}`);y("Saving attachment resume...");const R=await S(J,"/wapi/zpgeek/resume/attachment/save.json",{previewUrl:G,annexType:H["annex-type"]??1}),W=R?.resumeId;if(!W)throw new Q(`保存简历失败,响应缺少 resumeId: ${JSON.stringify(R||{}).slice(0,200)}`);return[{status:"✅ 简历已上传",resume_id:String(W),name:B,detail:`可用 boss sendresume <uid> --resume-id ${W} 发给 Boss`}]}});
@@ -1 +1 @@
1
- import{cli as O,Strategy as J}from"@jackwener/opencli/registry";import{ArgumentError as M}from"@jackwener/opencli/errors";import{requirePage as T,navigateTo as m,bossFetch as E,verbose as Z}from"./utils.js";const f={"全国":"100010000","北京":"101010100","上海":"101020100","广州":"101280100","深圳":"101280600","杭州":"101210100","成都":"101270100","南京":"101190100","武汉":"101200100","西安":"101110100","苏州":"101190400","长沙":"101250100","天津":"101030100","重庆":"101040100","郑州":"101180100","东莞":"101281600","青岛":"101120200","合肥":"101220100","佛山":"101280800","宁波":"101210400","厦门":"101230200","大连":"101070200","珠海":"101280700","无锡":"101190200","济南":"101120100","福州":"101230100","昆明":"101290100","哈尔滨":"101050100","沈阳":"101070100","石家庄":"101090100","贵阳":"101260100","南宁":"101300100","太原":"101100100","海口":"101310100","兰州":"101160100","乌鲁木齐":"101130100","长春":"101060100","南昌":"101240100","常州":"101191100","温州":"101210700","嘉兴":"101210300","徐州":"101190800","香港":"101320100"},Y={"不限":"0","在校/应届":"108","在校生":"108","在校":"108","应届生":"102","应届":"102","经验不限":"101","1年以内":"103","1-3年":"104","3-5年":"105","5-10年":"106","10年以上":"107"},d={"不限":"0","初中及以下":"209","中专/中技":"208","高中":"206","大专":"202","本科":"203","硕士":"204","博士":"205"},_={"不限":"0","3K以下":"401","3-5K":"402","5-10K":"403","10-15K":"404","15-20K":"405","20-30K":"406","30-50K":"407","50K以上":"408"},b={"不限":"0","互联网":"100020","电子商务":"100021","游戏":"100024","人工智能":"100901","大数据":"100902","金融":"100101","教育培训":"100200","医疗健康":"100300"},x={"不限":"0","全职":"1901","实习":"1902","兼职":"1903"},v=new Set(Object.values(x));function c(H){if(!H)return"101010100";if(/^\d+$/.test(H))return H;if(f[H])return f[H];for(const[Q,F]of Object.entries(f))if(Q.includes(H))return F;return"101010100"}function G(H,Q){if(!H)return"";if(Q[H]!==void 0)return Q[H];for(const[F,L]of Object.entries(Q))if(F.includes(H))return L;return H}function V(H){if(!H)return"";if(x[H]!==void 0)return x[H];if(v.has(H))return H;throw new M(`Invalid jobType: ${H}`,"Use one of: 全职, 兼职, 实习, 不限")}function A(H){if(H===!0)return"Y";if(H===!1)return"N";return""}O({site:"boss",name:"search",access:"read",description:"BOSS直聘搜索职位(不带关键词时返回为你推荐职位)",domain:"www.zhipin.com",strategy:J.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"query",positional:!0,help:"Search keyword (optional, empty = recommended jobs)"},{name:"city",default:"北京",help:"City name or code (e.g. 杭州, 上海, 101010100)"},{name:"experience",default:"",help:"Experience: 在校生(实习)/应届生(校招)/经验不限/1年以内/1-3年/3-5年/5-10年/10年以上"},{name:"degree",default:"",help:"Degree: 大专/本科/硕士/博士"},{name:"salary",default:"",help:"Salary: 3K以下/3-5K/5-10K/10-15K/15-20K/20-30K/30-50K/50K以上"},{name:"industry",default:"",help:"Industry code or name (e.g. 100020, 互联网)"},{name:"jobType",default:"",help:"Job type: 全职/兼职/实习(不传=不限,混合校招与实习)"},{name:"page",type:"int",default:1,help:"Page number"},{name:"limit",type:"int",default:15,help:"Number of results"}],columns:["name","salary","company","area","experience","degree","skills","boss","bossOnline","security_id","url"],func:async(H,Q)=>{T(H);const F=String(Q.query??"").trim(),L=c(Q.city);Z("Navigating to set referrer context...");await m(H,`https://www.zhipin.com/web/geek/job?query=${encodeURIComponent(F)}&city=${L}`);await new Promise((W)=>setTimeout(W,1000));const z=G(Q.experience,Y),U=G(Q.degree,d),X=G(Q.salary,_),R=G(Q.industry,b),h=V(Q.jobType),D=Q.limit||15;let N=Q.page||1,$=[];const I=new Set;while($.length<D){if($.length>0)await new Promise((K)=>setTimeout(K,1000+Math.random()*2000));const W=new URLSearchParams({scene:"1",query:F,city:L,page:String(N),pageSize:"15"});if(z)W.set("experience",z);if(U)W.set("degree",U);if(X)W.set("salary",X);if(R)W.set("industry",R);if(h)W.set("jobType",h);const C=`https://www.zhipin.com/wapi/zpgeek/search/joblist.json?${W.toString()}`;Z(`Fetching page ${N}... (current jobs: ${$.length})`);const q=(await E(H,C)).zpData||{},B=q.jobList||[];if(B.length===0)break;let S=0;for(const K of B){if(!K.encryptJobId||I.has(K.encryptJobId))continue;I.add(K.encryptJobId);$.push({name:K.jobName,salary:K.salaryDesc,company:K.brandName,area:[K.cityName,K.areaDistrict,K.businessDistrict].filter(Boolean).join("·"),experience:K.jobExperience,degree:K.jobDegree,skills:(K.skills||[]).join(","),boss:K.bossName+" · "+K.bossTitle,bossOnline:A(K.bossOnline),security_id:K.securityId||"",url:"https://www.zhipin.com/job_detail/"+K.encryptJobId+".html"});S++;if($.length>=D)break}if(S===0){Z(`API returned duplicate page, stopping pagination at ${$.length} items`);break}if(!q.hasMore)break;N++}return $}});export const __test__={EXP_MAP:Y,resolveMap:G,resolveJobType:V,formatBossOnline:A};
1
+ import{cli as O,Strategy as T}from"@jackwener/opencli/registry";import{ArgumentError as E}from"@jackwener/opencli/errors";import{requirePage as D,navigateTo as x,bossFetch as A,verbose as l,CITY_CODES as d}from"./utils.js";const K={"不限":"0","在校/应届":"108","在校生":"108","在校":"108","应届生":"102","应届":"102","经验不限":"101","1年以内":"103","1-3年":"104","3-5年":"105","5-10年":"106","10年以上":"107"},v={"不限":"0","初中及以下":"209","中专/中技":"208","高中":"206","大专":"202","本科":"203","硕士":"204","博士":"205"},J={"不限":"0","3K以下":"401","3-5K":"402","5-10K":"403","10-15K":"404","15-20K":"405","20-30K":"406","30-50K":"407","50K以上":"408"},z={"不限":"0","互联网":"100020","电子商务":"100021","游戏":"100024","人工智能":"100901","大数据":"100902","金融":"100101","教育培训":"100200","医疗健康":"100300"},f={"不限":"0","全职":"1901","实习":"1902","兼职":"1903"},B=new Set(Object.values(f));function C(e){if(!e)return"101010100";if(/^\d+$/.test(e))return e;if(d[e])return d[e];for(const[r,s]of Object.entries(d))if(r.includes(e))return s;return"101010100"}function a(e,r){if(!e)return"";if(r[e]!==void 0)return r[e];for(const[s,i]of Object.entries(r))if(s.includes(e))return i;return e}function P(e){if(!e)return"";if(f[e]!==void 0)return f[e];if(B.has(e))return e;throw new E(`Invalid jobType: ${e}`,"Use one of: 全职, 兼职, 实习, 不限")}function _(e){if(e===!0)return"Y";if(e===!1)return"N";return""}O({site:"boss",name:"search",access:"read",description:"BOSS直聘搜索职位(不带关键词时返回为你推荐职位)",domain:"www.zhipin.com",strategy:T.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"query",positional:!0,help:"Search keyword (optional, empty = recommended jobs)"},{name:"city",default:"北京",help:"City name or code (e.g. 杭州, 上海, 101010100)"},{name:"experience",default:"",help:"Experience: 在校生(实习)/应届生(校招)/经验不限/1年以内/1-3年/3-5年/5-10年/10年以上"},{name:"degree",default:"",help:"Degree: 大专/本科/硕士/博士"},{name:"salary",default:"",help:"Salary: 3K以下/3-5K/5-10K/10-15K/15-20K/20-30K/30-50K/50K以上"},{name:"industry",default:"",help:"Industry code or name (e.g. 100020, 互联网)"},{name:"jobType",default:"",help:"Job type: 全职/兼职/实习(不传=不限,混合校招与实习)"},{name:"page",type:"int",default:1,help:"Page number"},{name:"limit",type:"int",default:15,help:"Number of results"}],columns:["name","salary","company","area","experience","degree","skills","boss","bossOnline","security_id","url"],func:async(e,r)=>{D(e);const s=String(r.query??"").trim(),i=C(r.city);l("Navigating to set referrer context...");await x(e,`https://www.zhipin.com/web/geek/job?query=${encodeURIComponent(s)}&city=${i}`);await new Promise((n)=>setTimeout(n,1000));const m=a(r.experience,K),u=a(r.degree,v),y=a(r.salary,J),b=a(r.industry,z),p=P(r.jobType),h=r.limit||15;let c=r.page||1,o=[];const g=new Set;while(o.length<h){if(o.length>0)await new Promise((t)=>setTimeout(t,1000+Math.random()*2000));const n=new URLSearchParams({scene:"1",query:s,city:i,page:String(c),pageSize:"15"});if(m)n.set("experience",m);if(u)n.set("degree",u);if(y)n.set("salary",y);if(b)n.set("industry",b);if(p)n.set("jobType",p);const I=`https://www.zhipin.com/wapi/zpgeek/search/joblist.json?${n.toString()}`;l(`Fetching page ${c}... (current jobs: ${o.length})`);const j=(await A(e,I)).zpData||{},w=j.jobList||[];if(w.length===0)break;let S=0;for(const t of w){if(!t.encryptJobId||g.has(t.encryptJobId))continue;g.add(t.encryptJobId);o.push({name:t.jobName,salary:t.salaryDesc,company:t.brandName,area:[t.cityName,t.areaDistrict,t.businessDistrict].filter(Boolean).join("·"),experience:t.jobExperience,degree:t.jobDegree,skills:(t.skills||[]).join(","),boss:t.bossName+" · "+t.bossTitle,bossOnline:_(t.bossOnline),security_id:t.securityId||"",url:"https://www.zhipin.com/job_detail/"+t.encryptJobId+".html"});S++;if(o.length>=h)break}if(S===0){l(`API returned duplicate page, stopping pagination at ${o.length} items`);break}if(!j.hasMore)break;c++}return o}});export const __test__={EXP_MAP:K,resolveMap:a,resolveJobType:P,formatBossOnline:_};
package/clis/boss/send.js CHANGED
@@ -1 +1 @@
1
- import{cli as n,Strategy as o}from"@jackwener/opencli/registry";import{requirePage as d,navigateToChat as c,findFriendByUid as m,clickCandidateInList as u,typeAndSendMessage as l}from"./utils.js";import{EmptyResultError as h,selectorError as a}from"@jackwener/opencli/errors";n({site:"boss",name:"send",access:"write",description:"BOSS直聘发送聊天消息",domain:"www.zhipin.com",strategy:o.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Encrypted UID of the candidate (from chatlist)"},{name:"text",fromFile:!0,required:!0,positional:!0,help:"Message text to send"}],columns:["status","detail"],func:async(t,e)=>{d(t);await c(t,3);const i=await m(t,e.uid,{maxPages:5});if(!i)throw new h("boss candidate search","请确认 uid 是否正确");const r=i.uid,s=i.name||"候选人";if(!await u(t,r))throw a("聊天列表中的用户","请确认聊天列表中有此人");await t.wait({time:2});if(!await l(t,e.text))throw a("消息输入框","聊天页面 UI 可能已改变");await t.wait({time:1});return[{status:"✅ 发送成功",detail:`已向 ${s} 发送: ${e.text}`}]}});
1
+ import{cli as X,Strategy as Y}from"@jackwener/opencli/registry";import{EmptyResultError as Z,selectorError as H}from"@jackwener/opencli/errors";import{requirePage as _,navigateToChat as $,findFriendByUid as q,clickCandidateInList as F,typeAndSendMessage as K,geekOpenChat as L,resolveGeekBossId as N,navigateToGeekChat as O,isRecruiterSide as P,verbose as B}from"./utils.js";async function Q(x,z){await O(x);const D=await N(x,z.uid);B(`Opening geek chat with boss ${D}...`);await L(x,D);if(!await K(x,z.text))throw H("消息输入框","聊天页面 UI 可能已改变,或该会话不存在");await x.wait({time:1});return[{status:"✅ 发送成功",detail:`已向 Boss ${D} 发送: ${z.text}`}]}async function V(x,z){await $(x,3);const D=await q(x,z.uid,{maxPages:5});if(!D)throw new Z("boss candidate search","请确认 uid 是否正确");const J=D.uid,W=D.name||"候选人";if(!await F(x,J))throw H("聊天列表中的用户","请确认聊天列表中有此人");await x.wait({time:2});if(!await K(x,z.text))throw H("消息输入框","聊天页面 UI 可能已改变");await x.wait({time:1});return[{status:"✅ 发送成功",detail:`已向 ${W} 发送: ${z.text}`}]}X({site:"boss",name:"send",access:"write",description:"BOSS直聘发送聊天消息(求职端回复 Boss / 招聘端回复候选人)",domain:"www.zhipin.com",strategy:Y.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"对方 uid(从 boss chatlist 拿)"},{name:"text",fromFile:!0,required:!0,positional:!0,help:"Message text to send"},{name:"side",default:"auto",choices:["auto","geek","boss"],help:"Identity side: auto (default), geek (job-seeker), boss (recruiter)"}],columns:["status","detail"],func:async(x,z)=>{_(x);const D=z.side||"auto";if(D==="geek")return Q(x,z);if(D==="boss")return V(x,z);return await P(x)?V(x,z):Q(x,z)}});
@@ -0,0 +1 @@
1
+ import{cli as a,Strategy as d}from"@jackwener/opencli/registry";import{CommandExecutionError as u}from"@jackwener/opencli/errors";import{requirePage as n,navigateToGeekChat as m,geekPost as c,fetchGeekBossData as y,resolveGeekBossId as p,resolveGeekResumeId as I,verbose as l}from"./utils.js";a({site:"boss",name:"sendresume",access:"write",description:"BOSS直聘向 Boss 发送附件简历(求职端)",domain:"www.zhipin.com",strategy:d.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Boss 的 uid(数字 friendId 或 encryptFriendId,从 boss chatlist --side geek 拿)"},{name:"resume-id",default:"",help:"encryptResumeId(从 boss resumelist 拿)。账号只有一份时可不传"}],columns:["status","detail"],func:async(e,i)=>{n(e);await m(e);const s=await p(e,i.uid),t=await y(e,s),o=t.securityId||"";if(!o)throw new u(`无法获取 securityId (uid=${s}),该会话可能已失效`);const r=await I(e,i["resume-id"]);l(`Sending resume ${r} to boss ${s}...`);await c(e,"/wapi/zpchat/exchange/request",{type:3,bossId:s,securityId:o,mid:"",encryptResumeId:r});return[{status:"✅ 简历已发送",detail:`已向 ${t.name||t.bossName||s} 发送附件简历 ${r}`}]}});
@@ -0,0 +1 @@
1
+ import{cli as $,Strategy as q}from"@jackwener/opencli/registry";import{EmptyResultError as W}from"@jackwener/opencli/errors";import{requirePage as F,navigateToGeekChat as A,readEncryptSystemId as B,fetchGeekFriendLabelList as H,fetchGeekLastMsgs as R,fetchGeekUserInfo as j,readPositiveInteger as v,verbose as C}from"./utils.js";const X=20;$({site:"boss",name:"unread",access:"read",description:"BOSS直聘查看未回复的消息(求职端,Boss 发来但我还没回)",domain:"www.zhipin.com",strategy:q.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"limit",type:"int",default:50,help:"How many recent conversations to scan"}],columns:["name","company","job","last_msg","last_time","uid"],func:async(J,Y)=>{F(J);const Z=v(Y.limit,"unread --limit",50,200);C("Scanning for unreplied conversations...");await A(J);const _=await B(J),K=(await H(J,{encryptSystemId:_})).slice(0,Z);if(K.length===0)throw new W("boss unread","暂无沟通记录。");const O=(await j(J)).userId,Q=[];for(let x=0;x<K.length;x+=X){const z=K.slice(x,x+X).map((D)=>D.friendId).filter(Boolean);Q.push(...await R(J,z))}const V=new Map;for(const x of Q){const z=x.lastMsgInfo||{},D=z.fromId===O?z.toId:z.fromId;if(D)V.set(String(D),x)}const N=[];for(const x of K){const z=V.get(String(x.friendId)),D=z?.lastMsgInfo||{};if(!D.fromId||String(D.fromId)===String(O))continue;N.push({name:x.name||"",company:x.brandName||"",job:x.jobName||"",last_msg:D.showText||"",last_time:z.lastTime||"",uid:String(x.friendId)})}if(N.length===0)throw new W("boss unread","没有未回复的消息。");return N}});
@@ -1,12 +1,12 @@
1
- import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,EmptyResultError as v}from"@jackwener/opencli/errors";const q="www.zhipin.com",k=`https://${q}/web/chat/index`,F=new Set([7,37]),L="Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。",B="该命令仅支持招聘端(BOSS 端)账号,请使用招聘者账号登录后重试。",P=15000;export function requirePage(z){if(!z)throw new Z("Browser page required")}export function readPositiveInteger(z,J,Q,W){const $=z===void 0||z===null||z===""?Q:Number(z);if(!Number.isInteger($)||$<1)throw new H(`boss ${J} must be a positive integer`);if(W!==void 0&&$>W)throw new H(`boss ${J} must be <= ${W}`);return $}export function readRequiredString(z,J){const Q=String(z??"").trim();if(!Q)throw new H(`boss ${J} cannot be empty`);return Q}export async function navigateToChat(z,J=2){await z.goto(k);await z.wait({time:J})}export async function navigateTo(z,J,Q=1){await z.goto(J);await z.wait({time:Q})}export function checkAuth(z){if(F.has(z.code))throw new G(q,L)}function T(z){if(z.code===IDENTITY_MISMATCH_CODE)throw new G(q,B)}export function assertOk(z,J){if(!z||typeof z!=="object")throw new Z(`${J?`${J}: `:""}Boss API returned malformed response`);if(z.code===0)return;checkAuth(z);T(z);const Q=J?`${J}: `:"";throw new Z(`${Q}${z.message||"Unknown error"} (code=${z.code})`)}export async function bossFetch(z,J,Q={}){const W=Q.method??"GET",$=Q.timeout??P,j=Q.body??null,V=`
1
+ import{ArgumentError as P,AuthRequiredError as L,CommandExecutionError as z,EmptyResultError as F}from"@jackwener/opencli/errors";const K="www.zhipin.com",v=`https://${K}/web/chat/index`,G=new Set([7,37]),T="Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。",B="该命令仅支持招聘端(BOSS 端)账号,请使用招聘者账号登录后重试。",N=15000;export function requirePage(Q){if(!Q)throw new z("Browser page required")}export function readPositiveInteger(Q,V,W,J){const $=Q===void 0||Q===null||Q===""?W:Number(Q);if(!Number.isInteger($)||$<1)throw new P(`boss ${V} must be a positive integer`);if(J!==void 0&&$>J)throw new P(`boss ${V} must be <= ${J}`);return $}export function readRequiredString(Q,V){const W=String(Q??"").trim();if(!W)throw new P(`boss ${V} cannot be empty`);return W}export async function navigateToChat(Q,V=2){await Q.goto(v);await Q.wait({time:V})}export async function navigateTo(Q,V,W=1){await Q.goto(V);await Q.wait({time:W})}export function checkAuth(Q){if(G.has(Q.code))throw new L(K,T)}function k(Q){if(Q.code===IDENTITY_MISMATCH_CODE)throw new L(K,B)}export function assertOk(Q,V){if(!Q||typeof Q!=="object")throw new z(`${V?`${V}: `:""}Boss API returned malformed response`);if(Q.code===0)return;checkAuth(Q);k(Q);const W=V?`${V}: `:"";throw new z(`${W}${Q.message||"Unknown error"} (code=${Q.code})`)}export async function bossFetch(Q,V,W={}){const J=W.method??"GET",$=W.timeout??N,j=W.body??null,X=`
2
2
  async () => {
3
3
  return new Promise((resolve, reject) => {
4
4
  const xhr = new XMLHttpRequest();
5
- xhr.open(${JSON.stringify(W)}, ${JSON.stringify(J)}, true);
5
+ xhr.open(${JSON.stringify(J)}, ${JSON.stringify(V)}, true);
6
6
  xhr.withCredentials = true;
7
7
  xhr.timeout = ${$};
8
8
  xhr.setRequestHeader('Accept', 'application/json');
9
- ${W==="POST"?"xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');":""}
9
+ ${J==="POST"?"xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');":""}
10
10
  xhr.onload = () => {
11
11
  try { resolve(JSON.parse(xhr.responseText)); }
12
12
  catch(e) { reject(new Error('JSON parse failed: ' + xhr.responseText.substring(0, 200))); }
@@ -16,9 +16,9 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
16
16
  xhr.send(${j?JSON.stringify(j):"null"});
17
17
  });
18
18
  }
19
- `;let Y;try{Y=await z.evaluate(V)}catch(X){if(X instanceof G||X instanceof Z)throw X;const K=X instanceof Error?X.message:String(X);throw new Z(`Boss API request failed: ${K}`)}if(!Y||typeof Y!=="object")throw new Z("Boss API returned malformed response");if(!Q.allowNonZero&&Y.code!==0)assertOk(Y);return Y}export async function fetchFriendList(z,J={}){const Q=J.pageNum??1,W=J.jobId??"0",$=`https://${q}/wapi/zprelation/friend/getBossFriendListV2.json?page=${Q}&status=0&jobId=${W}`,j=await bossFetch(z,$,{allowNonZero:J.allowNonZero});if(J.allowNonZero&&j.code!==0)return j;const V=j.zpData?.friendList;if(!Array.isArray(V))throw new Z("Boss friend list response did not include zpData.friendList");return V}export async function fetchRecommendList(z){const J=`https://${q}/wapi/zprelation/friend/greetRecSortList`,W=(await bossFetch(z,J)).zpData?.friendList;if(!Array.isArray(W))throw new Z("Boss recommend response did not include zpData.friendList");return W}export async function findFriendByUid(z,J,Q={}){const W=Q.maxPages??1,$=Q.checkGreetList??!1;for(let j=1;j<=W;j++){const V=await fetchFriendList(z,{pageNum:j,allowNonZero:Q.allowNonZero});if(Q.allowNonZero&&!Array.isArray(V))return{friend:null,code:V.code};const Y=Array.isArray(V)?V:[],X=Y.find((K)=>K.encryptUid===J);if(X)return Q.allowNonZero?{friend:X,code:0}:X;if(Y.length===0)break}if($){const V=(await fetchRecommendList(z)).find((Y)=>Y.encryptUid===J);if(V)return Q.allowNonZero?{friend:V,code:0}:V}return Q.allowNonZero?{friend:null,code:0}:null}export async function clickCandidateInList(z,J){const Q=String(J).replace(/[^0-9]/g,"");return(await z.evaluate(`
19
+ `;let Z;try{Z=await Q.evaluate(X)}catch(Y){if(Y instanceof L||Y instanceof z)throw Y;const H=Y instanceof Error?Y.message:String(Y);throw new z(`Boss API request failed: ${H}`)}if(!Z||typeof Z!=="object")throw new z("Boss API returned malformed response");if(!W.allowNonZero&&Z.code!==0)assertOk(Z);return Z}export async function fetchFriendList(Q,V={}){const W=V.pageNum??1,J=V.jobId??"0",$=`https://${K}/wapi/zprelation/friend/getBossFriendListV2.json?page=${W}&status=0&jobId=${J}`,j=await bossFetch(Q,$,{allowNonZero:V.allowNonZero});if(V.allowNonZero&&j.code!==0)return j;const X=j.zpData?.friendList;if(!Array.isArray(X))throw new z("Boss friend list response did not include zpData.friendList");return X}export async function fetchRecommendList(Q){const V=`https://${K}/wapi/zprelation/friend/greetRecSortList`,J=(await bossFetch(Q,V)).zpData?.friendList;if(!Array.isArray(J))throw new z("Boss recommend response did not include zpData.friendList");return J}export async function findFriendByUid(Q,V,W={}){const J=W.maxPages??1,$=W.checkGreetList??!1;for(let j=1;j<=J;j++){const X=await fetchFriendList(Q,{pageNum:j,allowNonZero:W.allowNonZero});if(W.allowNonZero&&!Array.isArray(X))return{friend:null,code:X.code};const Z=Array.isArray(X)?X:[],Y=Z.find((H)=>H.encryptUid===V);if(Y)return W.allowNonZero?{friend:Y,code:0}:Y;if(Z.length===0)break}if($){const X=(await fetchRecommendList(Q)).find((Z)=>Z.encryptUid===V);if(X)return W.allowNonZero?{friend:X,code:0}:X}return W.allowNonZero?{friend:null,code:0}:null}export async function clickCandidateInList(Q,V){const W=String(V).replace(/[^0-9]/g,"");return(await Q.evaluate(`
20
20
  async () => {
21
- const uid = ${JSON.stringify(Q)};
21
+ const uid = ${JSON.stringify(W)};
22
22
  const item = document.querySelector('#_' + uid + '-0') || document.querySelector('[id^="_' + uid + '"]');
23
23
  if (item) {
24
24
  item.click();
@@ -33,7 +33,7 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
33
33
  }
34
34
  return { clicked: false };
35
35
  }
36
- `)).clicked}export async function typeAndSendMessage(z,J){if(!(await z.evaluate(`
36
+ `)).clicked}export async function typeAndSendMessage(Q,V){if(!(await Q.evaluate(`
37
37
  async () => {
38
38
  const selectors = [
39
39
  '.chat-editor [contenteditable="true"]',
@@ -48,12 +48,12 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
48
48
  if (el && el.offsetParent !== null) {
49
49
  el.focus();
50
50
  if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
51
- el.value = ${JSON.stringify(J)};
51
+ el.value = ${JSON.stringify(V)};
52
52
  el.dispatchEvent(new Event('input', { bubbles: true }));
53
53
  } else {
54
54
  el.textContent = '';
55
55
  el.focus();
56
- document.execCommand('insertText', false, ${JSON.stringify(J)});
56
+ document.execCommand('insertText', false, ${JSON.stringify(V)});
57
57
  el.dispatchEvent(new Event('input', { bubbles: true }));
58
58
  }
59
59
  return { found: true };
@@ -61,7 +61,7 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
61
61
  }
62
62
  return { found: false };
63
63
  }
64
- `)).found)return!1;await z.wait({time:0.5});if(!(await z.evaluate(`
64
+ `)).found)return!1;await Q.wait({time:0.5});if(!(await Q.evaluate(`
65
65
  async () => {
66
66
  const btn = document.querySelector('.conversation-editor .submit')
67
67
  || document.querySelector('.submit-content .submit')
@@ -72,7 +72,7 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
72
72
  }
73
73
  return { clicked: false };
74
74
  }
75
- `)).clicked)await z.pressKey("Enter");return!0}export function verbose(z){if(process.env.OPENCLI_VERBOSE)console.error(`[ppcli:boss] ${z}`)}export const IDENTITY_MISMATCH_CODE=24;const N=`https://${q}/web/geek/chat`;export async function navigateToGeekChat(z,J=2){await z.goto(N);await z.wait({time:J})}export async function readEncryptSystemId(z){return await z.evaluate(`
75
+ `)).clicked)await Q.pressKey("Enter");return!0}export function verbose(Q){if(process.env.OPENCLI_VERBOSE)console.error(`[ppcli:boss] ${Q}`)}export const IDENTITY_MISMATCH_CODE=24;const q=`https://${K}/web/geek/chat`;export async function navigateToGeekChat(Q,V=2){await Q.goto(q);await Q.wait({time:V})}export async function readEncryptSystemId(Q){return await Q.evaluate(`
76
76
  (() => {
77
77
  // 1. Vue app state / Pinia / $route.query
78
78
  // The chat component reads encryptSystemId from the app runtime to build
@@ -158,4 +158,4 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
158
158
  } catch (_) {}
159
159
  return '';
160
160
  })()
161
- `)||""}export async function fetchGeekFriendLabelList(z,J={}){const Q=J.labelId??0,W=J.encryptSystemId??"",$=`https://${q}/wapi/zprelation/friend/geekFilterByLabel?labelId=${Q}&encryptSystemId=${encodeURIComponent(W)}`,j=await bossFetch(z,$,{allowNonZero:J.allowNonZero});if(J.allowNonZero&&j.code!==0)return j;const V=j.zpData?.friendList;if(!Array.isArray(V))throw new Z("Boss geek chat list response did not include zpData.friendList");return V}export async function fetchGeekFriendInfoList(z,J=[]){if(!J.length)return[];const Q=50,W=[];for(let $=0;$<J.length;$+=Q){const V=`friendIds=${J.slice($,$+Q).map(String).join(",")}`,X=(await bossFetch(z,`https://${q}/wapi/zprelation/friend/getGeekFriendList.json`,{method:"POST",body:V})).zpData?.result;if(!Array.isArray(X))throw new Z("Boss geek friend enrichment response did not include zpData.result");W.push(...X)}return W}export async function findGeekFriendByUid(z,J,Q={}){const $=(await fetchGeekFriendLabelList(z,{encryptSystemId:Q.encryptSystemId})).find((V)=>V.encryptFriendId===J||String(V.uid)===String(J)||String(V.friendId)===String(J));if(!$)return null;const j=await fetchGeekFriendInfoList(z,[$.friendId]);return{...$,...j[0]||{}}}export async function fetchGeekHistoryMsg(z,J,Q={}){const W=Q.page??1,$=J.uid,j=encodeURIComponent(J.securityId||""),V=`https://${q}/wapi/zpchat/geek/historyMsg?bossId=${$}&securityId=${j}&page=${W}&c=20&src=0`,Y=await bossFetch(z,V),X=Y.zpData?.messages??Y.zpData?.historyMsgList;if(!Array.isArray(X))throw new Z("Boss geek history response did not include a message list");if(X.length===0)throw new v("boss chatmsg","Boss returned no messages for this chat.");return X}
161
+ `)||""}export async function fetchGeekFriendLabelList(Q,V={}){const W=V.labelId??0,J=V.encryptSystemId??"",$=`https://${K}/wapi/zprelation/friend/geekFilterByLabel?labelId=${W}&encryptSystemId=${encodeURIComponent(J)}`,j=await bossFetch(Q,$,{allowNonZero:V.allowNonZero});if(V.allowNonZero&&j.code!==0)return j;const X=j.zpData?.friendList;if(!Array.isArray(X))throw new z("Boss geek chat list response did not include zpData.friendList");return X}export async function fetchGeekFriendInfoList(Q,V=[]){if(!V.length)return[];const W=50,J=[];for(let $=0;$<V.length;$+=W){const X=`friendIds=${V.slice($,$+W).map(String).join(",")}`,Y=(await bossFetch(Q,`https://${K}/wapi/zprelation/friend/getGeekFriendList.json`,{method:"POST",body:X})).zpData?.result;if(!Array.isArray(Y))throw new z("Boss geek friend enrichment response did not include zpData.result");J.push(...Y)}return J}export async function findGeekFriendByUid(Q,V,W={}){const $=(await fetchGeekFriendLabelList(Q,{encryptSystemId:W.encryptSystemId})).find((X)=>X.encryptFriendId===V||String(X.uid)===String(V)||String(X.friendId)===String(V));if(!$)return null;const j=await fetchGeekFriendInfoList(Q,[$.friendId]);return{...$,...j[0]||{}}}export async function fetchGeekHistoryMsg(Q,V,W={}){const J=W.page??1,$=V.uid,j=encodeURIComponent(V.securityId||""),X=`https://${K}/wapi/zpchat/geek/historyMsg?bossId=${$}&securityId=${j}&page=${J}&c=20&src=0`,Z=await bossFetch(Q,X),Y=Z.zpData?.messages??Z.zpData?.historyMsgList;if(!Array.isArray(Y))throw new z("Boss geek history response did not include a message list");if(Y.length===0)throw new F("boss chatmsg","Boss returned no messages for this chat.");return Y}const M=`https://${K}/web/geek/job`;export async function navigateToGeekJob(Q,V=2){await Q.goto(M);await Q.wait({time:V})}export async function geekGet(Q,V,W={},J={}){const $=new URLSearchParams;for(const[Y,H]of Object.entries(W)){if(H===void 0||H===null||H==="")continue;$.set(Y,String(H))}const j=$.toString(),X=`https://${K}${V}${j?`?${j}`:""}`,Z=await bossFetch(Q,X,J);if(J.allowNonZero&&Z.code!==0)return Z;return Z.zpData??{}}export async function geekPost(Q,V,W={},J={}){const $=new URLSearchParams;for(const[X,Z]of Object.entries(W)){if(Z===void 0||Z===null)continue;$.set(X,String(Z))}const j=await bossFetch(Q,`https://${K}${V}`,{...J,method:"POST",body:$.toString()});if(J.allowNonZero&&j.code!==0)return j;return j.zpData??{}}export async function fetchGeekUserInfo(Q){return geekGet(Q,"/wapi/zpuser/wap/getUserInfo.json")}export async function fetchGeekBossData(Q,V){const W=await geekGet(Q,"/wapi/zpchat/geek/getBossData",{bossId:V});return W?.data??W??{}}export async function fetchGeekLastMsgs(Q,V=[]){if(!V.length)return[];const W=await geekGet(Q,"/wapi/zpchat/geek/userLastMsg",{friendIds:V.map(String).join(",")});return Array.isArray(W)?W:W?.result??[]}export async function fetchGeekAttachmentResumes(Q){return(await geekGet(Q,"/wapi/zpgeek/resume/attachment/checkbox.json"))?.resumeList??[]}export async function resolveGeekResumeId(Q,V){const W=await fetchGeekAttachmentResumes(Q);if(V){if(!W.find((j)=>String(j.resumeId)===String(V))){const j=W.map((X)=>X.resumeId||"?").join(", ")||"(空)";throw new P(`指定的 --resume-id=${V} 不在账号简历列表 [${j}] 中`)}return String(V)}if(W.length===0)throw new F("boss resumelist","账号还没有绑定附件简历,请先用 boss resumeupload 上传,或在 BOSS 直聘 App 里上传。");if(W.length===1){const $=W[0].resumeId;if(!$)throw new z("唯一附件简历缺失 resumeId 字段");return String($)}const J=W.map(($)=>`${$.resumeId}(${$.showName||"未命名"})`).join(", ");throw new P(`账号绑定了 ${W.length} 份附件简历,请用 --resume-id 指定其中一份:${J}`)}export async function resolveGeekBoss(Q,V){const W=String(V??"").trim();if(!W)throw new P("boss id cannot be empty");if(/^\d+$/.test(W))return{id:W,friend:null};const J=await findGeekFriendByUid(Q,W);if(!J)throw new F("boss chatlist",`未在沟通列表中找到 ${W},请先用 boss chatlist --side geek 查 uid。`);const $=J.uid??J.friendId;if(!$)throw new z("沟通列表条目缺失 uid / friendId");return{id:String($),friend:J}}export async function resolveGeekBossId(Q,V){return(await resolveGeekBoss(Q,V)).id}export async function geekAddFriend(Q,V,W=""){return geekGet(Q,"/wapi/zpgeek/friend/add.json",{securityId:V,lid:W})}export async function geekOpenChat(Q,V,W=3){await Q.goto(`${q}?id=${encodeURIComponent(String(V))}`);await Q.wait({time:W})}export const CITY_CODES={"全国":"100010000","北京":"101010100","上海":"101020100","广州":"101280100","深圳":"101280600","杭州":"101210100","成都":"101270100","南京":"101190100","武汉":"101200100","西安":"101110100","苏州":"101190400","长沙":"101250100","天津":"101030100","重庆":"101040100","郑州":"101180100","东莞":"101281600","青岛":"101120200","合肥":"101220100","佛山":"101280800","宁波":"101210400","厦门":"101230200","大连":"101070200","珠海":"101280700","无锡":"101190200","济南":"101120100","福州":"101230100","昆明":"101290100","哈尔滨":"101050100","沈阳":"101070100","石家庄":"101090100","贵阳":"101260100","南宁":"101300100","太原":"101100100","海口":"101310100","兰州":"101160100","乌鲁木齐":"101130100","长春":"101060100","南昌":"101240100","常州":"101191100","温州":"101210700","嘉兴":"101210300","徐州":"101190800","香港":"101320100"};export async function isRecruiterSide(Q){await navigateToChat(Q);const V=await bossFetch(Q,`https://${K}/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0`,{allowNonZero:!0});if(V.code===0)return!0;if(V.code===IDENTITY_MISMATCH_CODE)return!1;assertOk(V,"boss 身份探测");return!1}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false