publishport-opencli 1.0.0 → 1.0.1
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 +452 -2
- package/clis/quora/__fixtures__/dom.html +39 -0
- package/clis/quora/answer-comments.js +9 -0
- package/clis/quora/answer-detail.js +1 -0
- package/clis/quora/answer-queue.js +1 -0
- package/clis/quora/comment.js +23 -0
- package/clis/quora/create-answer.js +4 -4
- package/clis/quora/create-question.js +3 -3
- package/clis/quora/follow.js +15 -0
- package/clis/quora/followers.js +1 -0
- package/clis/quora/question.js +1 -0
- package/clis/quora/search.js +1 -0
- package/clis/quora/shared.js +26 -11
- package/clis/quora/stats.js +1 -0
- package/clis/quora/upvote.js +14 -0
- package/clis/quora/user-answers.js +1 -0
- package/clis/quora/user.js +1 -0
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -32312,6 +32312,145 @@
|
|
|
32312
32312
|
"navigateBefore": false,
|
|
32313
32313
|
"siteSession": "persistent"
|
|
32314
32314
|
},
|
|
32315
|
+
{
|
|
32316
|
+
"site": "quora",
|
|
32317
|
+
"name": "answer-comments",
|
|
32318
|
+
"description": "Read visible comments on one Quora answer.",
|
|
32319
|
+
"access": "read",
|
|
32320
|
+
"domain": "www.quora.com",
|
|
32321
|
+
"strategy": "ui",
|
|
32322
|
+
"browser": true,
|
|
32323
|
+
"args": [
|
|
32324
|
+
{
|
|
32325
|
+
"name": "url",
|
|
32326
|
+
"type": "string",
|
|
32327
|
+
"required": true,
|
|
32328
|
+
"positional": true,
|
|
32329
|
+
"help": "Quora answer URL"
|
|
32330
|
+
},
|
|
32331
|
+
{
|
|
32332
|
+
"name": "limit",
|
|
32333
|
+
"type": "int",
|
|
32334
|
+
"default": 20,
|
|
32335
|
+
"required": false,
|
|
32336
|
+
"help": "Number of comments (max 50)"
|
|
32337
|
+
}
|
|
32338
|
+
],
|
|
32339
|
+
"columns": [
|
|
32340
|
+
"rank",
|
|
32341
|
+
"author",
|
|
32342
|
+
"content",
|
|
32343
|
+
"upvotes",
|
|
32344
|
+
"published",
|
|
32345
|
+
"authorUrl"
|
|
32346
|
+
],
|
|
32347
|
+
"type": "js",
|
|
32348
|
+
"modulePath": "quora/answer-comments.js",
|
|
32349
|
+
"sourceFile": "quora/answer-comments.js",
|
|
32350
|
+
"navigateBefore": true
|
|
32351
|
+
},
|
|
32352
|
+
{
|
|
32353
|
+
"site": "quora",
|
|
32354
|
+
"name": "answer-detail",
|
|
32355
|
+
"description": "Read one complete Quora answer with question, author and engagement metrics.",
|
|
32356
|
+
"access": "read",
|
|
32357
|
+
"domain": "www.quora.com",
|
|
32358
|
+
"strategy": "cookie",
|
|
32359
|
+
"browser": true,
|
|
32360
|
+
"args": [
|
|
32361
|
+
{
|
|
32362
|
+
"name": "url",
|
|
32363
|
+
"type": "string",
|
|
32364
|
+
"required": true,
|
|
32365
|
+
"positional": true,
|
|
32366
|
+
"help": "Quora answer URL"
|
|
32367
|
+
}
|
|
32368
|
+
],
|
|
32369
|
+
"columns": [
|
|
32370
|
+
"question",
|
|
32371
|
+
"author",
|
|
32372
|
+
"content",
|
|
32373
|
+
"upvotes",
|
|
32374
|
+
"comments",
|
|
32375
|
+
"shares",
|
|
32376
|
+
"published",
|
|
32377
|
+
"url"
|
|
32378
|
+
],
|
|
32379
|
+
"type": "js",
|
|
32380
|
+
"modulePath": "quora/answer-detail.js",
|
|
32381
|
+
"sourceFile": "quora/answer-detail.js",
|
|
32382
|
+
"navigateBefore": "https://www.quora.com"
|
|
32383
|
+
},
|
|
32384
|
+
{
|
|
32385
|
+
"site": "quora",
|
|
32386
|
+
"name": "answer-queue",
|
|
32387
|
+
"description": "List personalized Questions for You from Quora's Answer queue.",
|
|
32388
|
+
"access": "read",
|
|
32389
|
+
"domain": "www.quora.com",
|
|
32390
|
+
"strategy": "cookie",
|
|
32391
|
+
"browser": true,
|
|
32392
|
+
"args": [
|
|
32393
|
+
{
|
|
32394
|
+
"name": "limit",
|
|
32395
|
+
"type": "int",
|
|
32396
|
+
"default": 10,
|
|
32397
|
+
"required": false,
|
|
32398
|
+
"help": "Number of questions (max 50)"
|
|
32399
|
+
}
|
|
32400
|
+
],
|
|
32401
|
+
"columns": [
|
|
32402
|
+
"rank",
|
|
32403
|
+
"title",
|
|
32404
|
+
"answers",
|
|
32405
|
+
"url"
|
|
32406
|
+
],
|
|
32407
|
+
"type": "js",
|
|
32408
|
+
"modulePath": "quora/answer-queue.js",
|
|
32409
|
+
"sourceFile": "quora/answer-queue.js",
|
|
32410
|
+
"navigateBefore": "https://www.quora.com"
|
|
32411
|
+
},
|
|
32412
|
+
{
|
|
32413
|
+
"site": "quora",
|
|
32414
|
+
"name": "comment",
|
|
32415
|
+
"description": "Post a top-level comment on a Quora answer through the visible editor.",
|
|
32416
|
+
"access": "write",
|
|
32417
|
+
"domain": "www.quora.com",
|
|
32418
|
+
"strategy": "ui",
|
|
32419
|
+
"browser": true,
|
|
32420
|
+
"args": [
|
|
32421
|
+
{
|
|
32422
|
+
"name": "url",
|
|
32423
|
+
"type": "string",
|
|
32424
|
+
"required": true,
|
|
32425
|
+
"positional": true,
|
|
32426
|
+
"help": "Quora answer URL"
|
|
32427
|
+
},
|
|
32428
|
+
{
|
|
32429
|
+
"name": "text",
|
|
32430
|
+
"type": "string",
|
|
32431
|
+
"required": true,
|
|
32432
|
+
"positional": true,
|
|
32433
|
+
"help": "Comment text"
|
|
32434
|
+
},
|
|
32435
|
+
{
|
|
32436
|
+
"name": "execute",
|
|
32437
|
+
"type": "boolean",
|
|
32438
|
+
"required": false,
|
|
32439
|
+
"help": "Actually post the comment"
|
|
32440
|
+
}
|
|
32441
|
+
],
|
|
32442
|
+
"columns": [
|
|
32443
|
+
"status",
|
|
32444
|
+
"outcome",
|
|
32445
|
+
"targetType",
|
|
32446
|
+
"target",
|
|
32447
|
+
"message"
|
|
32448
|
+
],
|
|
32449
|
+
"type": "js",
|
|
32450
|
+
"modulePath": "quora/comment.js",
|
|
32451
|
+
"sourceFile": "quora/comment.js",
|
|
32452
|
+
"navigateBefore": true
|
|
32453
|
+
},
|
|
32315
32454
|
{
|
|
32316
32455
|
"site": "quora",
|
|
32317
32456
|
"name": "create-answer",
|
|
@@ -32331,9 +32470,21 @@
|
|
|
32331
32470
|
{
|
|
32332
32471
|
"name": "content",
|
|
32333
32472
|
"type": "string",
|
|
32334
|
-
"required":
|
|
32473
|
+
"required": false,
|
|
32335
32474
|
"positional": true,
|
|
32336
|
-
"help": "Answer body text
|
|
32475
|
+
"help": "Answer body text, or use --file for long content"
|
|
32476
|
+
},
|
|
32477
|
+
{
|
|
32478
|
+
"name": "file",
|
|
32479
|
+
"type": "string",
|
|
32480
|
+
"required": false,
|
|
32481
|
+
"help": "Read answer body from a local UTF-8 text file"
|
|
32482
|
+
},
|
|
32483
|
+
{
|
|
32484
|
+
"name": "execute",
|
|
32485
|
+
"type": "boolean",
|
|
32486
|
+
"required": false,
|
|
32487
|
+
"help": "Actually post the answer"
|
|
32337
32488
|
}
|
|
32338
32489
|
],
|
|
32339
32490
|
"columns": [
|
|
@@ -32361,6 +32512,12 @@
|
|
|
32361
32512
|
"required": true,
|
|
32362
32513
|
"positional": true,
|
|
32363
32514
|
"help": "The question title (a clear, complete question)"
|
|
32515
|
+
},
|
|
32516
|
+
{
|
|
32517
|
+
"name": "execute",
|
|
32518
|
+
"type": "boolean",
|
|
32519
|
+
"required": false,
|
|
32520
|
+
"help": "Actually post the question"
|
|
32364
32521
|
}
|
|
32365
32522
|
],
|
|
32366
32523
|
"columns": [
|
|
@@ -32373,6 +32530,77 @@
|
|
|
32373
32530
|
"sourceFile": "quora/create-question.js",
|
|
32374
32531
|
"navigateBefore": true
|
|
32375
32532
|
},
|
|
32533
|
+
{
|
|
32534
|
+
"site": "quora",
|
|
32535
|
+
"name": "follow",
|
|
32536
|
+
"description": "Follow a Quora profile or question through the visible UI.",
|
|
32537
|
+
"access": "write",
|
|
32538
|
+
"domain": "www.quora.com",
|
|
32539
|
+
"strategy": "ui",
|
|
32540
|
+
"browser": true,
|
|
32541
|
+
"args": [
|
|
32542
|
+
{
|
|
32543
|
+
"name": "url",
|
|
32544
|
+
"type": "string",
|
|
32545
|
+
"required": true,
|
|
32546
|
+
"positional": true,
|
|
32547
|
+
"help": "Quora profile or question URL"
|
|
32548
|
+
},
|
|
32549
|
+
{
|
|
32550
|
+
"name": "execute",
|
|
32551
|
+
"type": "boolean",
|
|
32552
|
+
"required": false,
|
|
32553
|
+
"help": "Actually follow the target"
|
|
32554
|
+
}
|
|
32555
|
+
],
|
|
32556
|
+
"columns": [
|
|
32557
|
+
"status",
|
|
32558
|
+
"outcome",
|
|
32559
|
+
"targetType",
|
|
32560
|
+
"target",
|
|
32561
|
+
"message"
|
|
32562
|
+
],
|
|
32563
|
+
"type": "js",
|
|
32564
|
+
"modulePath": "quora/follow.js",
|
|
32565
|
+
"sourceFile": "quora/follow.js",
|
|
32566
|
+
"navigateBefore": true
|
|
32567
|
+
},
|
|
32568
|
+
{
|
|
32569
|
+
"site": "quora",
|
|
32570
|
+
"name": "followers",
|
|
32571
|
+
"description": "List followers of a Quora profile.",
|
|
32572
|
+
"access": "read",
|
|
32573
|
+
"domain": "www.quora.com",
|
|
32574
|
+
"strategy": "cookie",
|
|
32575
|
+
"browser": true,
|
|
32576
|
+
"args": [
|
|
32577
|
+
{
|
|
32578
|
+
"name": "user",
|
|
32579
|
+
"type": "string",
|
|
32580
|
+
"required": true,
|
|
32581
|
+
"positional": true,
|
|
32582
|
+
"help": "Profile slug or Quora profile URL"
|
|
32583
|
+
},
|
|
32584
|
+
{
|
|
32585
|
+
"name": "limit",
|
|
32586
|
+
"type": "int",
|
|
32587
|
+
"default": 20,
|
|
32588
|
+
"required": false,
|
|
32589
|
+
"help": "Number of followers (max 50)"
|
|
32590
|
+
}
|
|
32591
|
+
],
|
|
32592
|
+
"columns": [
|
|
32593
|
+
"rank",
|
|
32594
|
+
"name",
|
|
32595
|
+
"headline",
|
|
32596
|
+
"followers",
|
|
32597
|
+
"url"
|
|
32598
|
+
],
|
|
32599
|
+
"type": "js",
|
|
32600
|
+
"modulePath": "quora/followers.js",
|
|
32601
|
+
"sourceFile": "quora/followers.js",
|
|
32602
|
+
"navigateBefore": "https://www.quora.com"
|
|
32603
|
+
},
|
|
32376
32604
|
{
|
|
32377
32605
|
"site": "quora",
|
|
32378
32606
|
"name": "login",
|
|
@@ -32402,6 +32630,228 @@
|
|
|
32402
32630
|
"siteSession": "persistent",
|
|
32403
32631
|
"defaultWindowMode": "foreground"
|
|
32404
32632
|
},
|
|
32633
|
+
{
|
|
32634
|
+
"site": "quora",
|
|
32635
|
+
"name": "question",
|
|
32636
|
+
"description": "Read a Quora question and its visible answers, including engagement metrics.",
|
|
32637
|
+
"access": "read",
|
|
32638
|
+
"domain": "www.quora.com",
|
|
32639
|
+
"strategy": "cookie",
|
|
32640
|
+
"browser": true,
|
|
32641
|
+
"args": [
|
|
32642
|
+
{
|
|
32643
|
+
"name": "url",
|
|
32644
|
+
"type": "string",
|
|
32645
|
+
"required": true,
|
|
32646
|
+
"positional": true,
|
|
32647
|
+
"help": "Quora question URL"
|
|
32648
|
+
},
|
|
32649
|
+
{
|
|
32650
|
+
"name": "limit",
|
|
32651
|
+
"type": "int",
|
|
32652
|
+
"default": 10,
|
|
32653
|
+
"required": false,
|
|
32654
|
+
"help": "Number of answers (max 50)"
|
|
32655
|
+
}
|
|
32656
|
+
],
|
|
32657
|
+
"columns": [
|
|
32658
|
+
"rank",
|
|
32659
|
+
"question",
|
|
32660
|
+
"author",
|
|
32661
|
+
"excerpt",
|
|
32662
|
+
"upvotes",
|
|
32663
|
+
"comments",
|
|
32664
|
+
"shares",
|
|
32665
|
+
"published",
|
|
32666
|
+
"url"
|
|
32667
|
+
],
|
|
32668
|
+
"type": "js",
|
|
32669
|
+
"modulePath": "quora/question.js",
|
|
32670
|
+
"sourceFile": "quora/question.js",
|
|
32671
|
+
"navigateBefore": "https://www.quora.com"
|
|
32672
|
+
},
|
|
32673
|
+
{
|
|
32674
|
+
"site": "quora",
|
|
32675
|
+
"name": "search",
|
|
32676
|
+
"description": "Search Quora questions or answers from the visible logged-in search results.",
|
|
32677
|
+
"access": "read",
|
|
32678
|
+
"domain": "www.quora.com",
|
|
32679
|
+
"strategy": "cookie",
|
|
32680
|
+
"browser": true,
|
|
32681
|
+
"args": [
|
|
32682
|
+
{
|
|
32683
|
+
"name": "query",
|
|
32684
|
+
"type": "string",
|
|
32685
|
+
"required": true,
|
|
32686
|
+
"positional": true,
|
|
32687
|
+
"help": "Search query"
|
|
32688
|
+
},
|
|
32689
|
+
{
|
|
32690
|
+
"name": "type",
|
|
32691
|
+
"type": "string",
|
|
32692
|
+
"default": "question",
|
|
32693
|
+
"required": false,
|
|
32694
|
+
"help": "Result type: question or answer",
|
|
32695
|
+
"choices": [
|
|
32696
|
+
"question",
|
|
32697
|
+
"answer"
|
|
32698
|
+
]
|
|
32699
|
+
},
|
|
32700
|
+
{
|
|
32701
|
+
"name": "limit",
|
|
32702
|
+
"type": "int",
|
|
32703
|
+
"default": 10,
|
|
32704
|
+
"required": false,
|
|
32705
|
+
"help": "Number of results (max 50)"
|
|
32706
|
+
}
|
|
32707
|
+
],
|
|
32708
|
+
"columns": [
|
|
32709
|
+
"rank",
|
|
32710
|
+
"type",
|
|
32711
|
+
"title",
|
|
32712
|
+
"author",
|
|
32713
|
+
"excerpt",
|
|
32714
|
+
"answers",
|
|
32715
|
+
"upvotes",
|
|
32716
|
+
"comments",
|
|
32717
|
+
"shares",
|
|
32718
|
+
"published",
|
|
32719
|
+
"url"
|
|
32720
|
+
],
|
|
32721
|
+
"type": "js",
|
|
32722
|
+
"modulePath": "quora/search.js",
|
|
32723
|
+
"sourceFile": "quora/search.js",
|
|
32724
|
+
"navigateBefore": "https://www.quora.com"
|
|
32725
|
+
},
|
|
32726
|
+
{
|
|
32727
|
+
"site": "quora",
|
|
32728
|
+
"name": "stats",
|
|
32729
|
+
"description": "Read the logged-in account's Quora content overview for the current stats period.",
|
|
32730
|
+
"access": "read",
|
|
32731
|
+
"domain": "www.quora.com",
|
|
32732
|
+
"strategy": "cookie",
|
|
32733
|
+
"browser": true,
|
|
32734
|
+
"args": [],
|
|
32735
|
+
"columns": [
|
|
32736
|
+
"period",
|
|
32737
|
+
"views",
|
|
32738
|
+
"upvotes",
|
|
32739
|
+
"comments",
|
|
32740
|
+
"shares"
|
|
32741
|
+
],
|
|
32742
|
+
"type": "js",
|
|
32743
|
+
"modulePath": "quora/stats.js",
|
|
32744
|
+
"sourceFile": "quora/stats.js",
|
|
32745
|
+
"navigateBefore": "https://www.quora.com"
|
|
32746
|
+
},
|
|
32747
|
+
{
|
|
32748
|
+
"site": "quora",
|
|
32749
|
+
"name": "upvote",
|
|
32750
|
+
"description": "Upvote one Quora answer through the visible logged-in UI. Idempotent when already upvoted.",
|
|
32751
|
+
"access": "write",
|
|
32752
|
+
"domain": "www.quora.com",
|
|
32753
|
+
"strategy": "ui",
|
|
32754
|
+
"browser": true,
|
|
32755
|
+
"args": [
|
|
32756
|
+
{
|
|
32757
|
+
"name": "url",
|
|
32758
|
+
"type": "string",
|
|
32759
|
+
"required": true,
|
|
32760
|
+
"positional": true,
|
|
32761
|
+
"help": "Quora answer URL"
|
|
32762
|
+
},
|
|
32763
|
+
{
|
|
32764
|
+
"name": "execute",
|
|
32765
|
+
"type": "boolean",
|
|
32766
|
+
"required": false,
|
|
32767
|
+
"help": "Actually apply the upvote"
|
|
32768
|
+
}
|
|
32769
|
+
],
|
|
32770
|
+
"columns": [
|
|
32771
|
+
"status",
|
|
32772
|
+
"outcome",
|
|
32773
|
+
"targetType",
|
|
32774
|
+
"target",
|
|
32775
|
+
"message"
|
|
32776
|
+
],
|
|
32777
|
+
"type": "js",
|
|
32778
|
+
"modulePath": "quora/upvote.js",
|
|
32779
|
+
"sourceFile": "quora/upvote.js",
|
|
32780
|
+
"navigateBefore": true
|
|
32781
|
+
},
|
|
32782
|
+
{
|
|
32783
|
+
"site": "quora",
|
|
32784
|
+
"name": "user",
|
|
32785
|
+
"description": "Read a Quora profile with follower, content and view totals.",
|
|
32786
|
+
"access": "read",
|
|
32787
|
+
"domain": "www.quora.com",
|
|
32788
|
+
"strategy": "cookie",
|
|
32789
|
+
"browser": true,
|
|
32790
|
+
"args": [
|
|
32791
|
+
{
|
|
32792
|
+
"name": "user",
|
|
32793
|
+
"type": "string",
|
|
32794
|
+
"required": true,
|
|
32795
|
+
"positional": true,
|
|
32796
|
+
"help": "Profile slug or Quora profile URL"
|
|
32797
|
+
}
|
|
32798
|
+
],
|
|
32799
|
+
"columns": [
|
|
32800
|
+
"name",
|
|
32801
|
+
"bio",
|
|
32802
|
+
"followers",
|
|
32803
|
+
"following",
|
|
32804
|
+
"answers",
|
|
32805
|
+
"questions",
|
|
32806
|
+
"posts",
|
|
32807
|
+
"contentViews",
|
|
32808
|
+
"url"
|
|
32809
|
+
],
|
|
32810
|
+
"type": "js",
|
|
32811
|
+
"modulePath": "quora/user.js",
|
|
32812
|
+
"sourceFile": "quora/user.js",
|
|
32813
|
+
"navigateBefore": "https://www.quora.com"
|
|
32814
|
+
},
|
|
32815
|
+
{
|
|
32816
|
+
"site": "quora",
|
|
32817
|
+
"name": "user-answers",
|
|
32818
|
+
"description": "List answers published by a Quora profile with engagement metrics.",
|
|
32819
|
+
"access": "read",
|
|
32820
|
+
"domain": "www.quora.com",
|
|
32821
|
+
"strategy": "cookie",
|
|
32822
|
+
"browser": true,
|
|
32823
|
+
"args": [
|
|
32824
|
+
{
|
|
32825
|
+
"name": "user",
|
|
32826
|
+
"type": "string",
|
|
32827
|
+
"required": true,
|
|
32828
|
+
"positional": true,
|
|
32829
|
+
"help": "Profile slug or Quora profile URL"
|
|
32830
|
+
},
|
|
32831
|
+
{
|
|
32832
|
+
"name": "limit",
|
|
32833
|
+
"type": "int",
|
|
32834
|
+
"default": 10,
|
|
32835
|
+
"required": false,
|
|
32836
|
+
"help": "Number of answers (max 50)"
|
|
32837
|
+
}
|
|
32838
|
+
],
|
|
32839
|
+
"columns": [
|
|
32840
|
+
"rank",
|
|
32841
|
+
"question",
|
|
32842
|
+
"author",
|
|
32843
|
+
"excerpt",
|
|
32844
|
+
"upvotes",
|
|
32845
|
+
"comments",
|
|
32846
|
+
"shares",
|
|
32847
|
+
"published",
|
|
32848
|
+
"url"
|
|
32849
|
+
],
|
|
32850
|
+
"type": "js",
|
|
32851
|
+
"modulePath": "quora/user-answers.js",
|
|
32852
|
+
"sourceFile": "quora/user-answers.js",
|
|
32853
|
+
"navigateBefore": "https://www.quora.com"
|
|
32854
|
+
},
|
|
32405
32855
|
{
|
|
32406
32856
|
"site": "quora",
|
|
32407
32857
|
"name": "whoami",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html><head><title>Fixture User - Quora</title></head><body>
|
|
3
|
+
<section id="fixture-question">
|
|
4
|
+
<div class="puppeteer_test_question_title">How does a frozen Quora fixture work?</div>
|
|
5
|
+
<div class="dom_annotate_question_answer_item_0"><a href="/profile/Ada-Lovelace">Ada Lovelace</a><a class="answer_timestamp" href="/How-does-a-frozen-Quora-fixture-work/answer/Ada-Lovelace">2h</a><div class="puppeteer_test_answer_content"><p>It preserves the visible semantic hooks.</p></div><button class="puppeteer_test_votable_upvote_button" aria-label="Upvote">Upvote 1.2K</button><button aria-label="12 comments">12</button><button aria-label="3 shares">3</button></div>
|
|
6
|
+
<div class="dom_annotate_question_answer_item_1"><a href="/profile/Grace-Hopper">Grace Hopper</a><a class="answer_timestamp" href="/How-does-a-frozen-Quora-fixture-work/answer/Grace-Hopper">1d</a><div class="puppeteer_test_answer_content">Selectors are tested against real page shapes. (more)</div><button class="puppeteer_test_votable_upvote_button" aria-label="Upvote">Upvote 42</button><button aria-label="Comment"></button><button aria-label="Share"></button></div>
|
|
7
|
+
</section>
|
|
8
|
+
<section id="fixture-search-question">
|
|
9
|
+
<div class="search-card"><a href="/What-is-artificial-intelligence"><div class="puppeteer_test_question_title">What is artificial intelligence?</div></a><a href="/What-is-artificial-intelligence">993 answers</a></div>
|
|
10
|
+
<div class="search-card"><a href="/Is-AI-safe"><div class="puppeteer_test_question_title">Is AI safe?</div></a><a href="/Is-AI-safe">1.5K answers</a></div>
|
|
11
|
+
</section>
|
|
12
|
+
<section id="fixture-answer-detail">
|
|
13
|
+
<a href="/What-is-artificial-intelligence"><div class="puppeteer_test_question_title">What is artificial intelligence?</div></a>
|
|
14
|
+
<a href="/profile/Joel-4940">Joel</a><a class="answer_timestamp" href="/What-is-artificial-intelligence/answer/Joel-4940">1y</a>
|
|
15
|
+
<p>Artificial intelligence creates systems that perform cognitive tasks.</p><p>It includes learning, reasoning, and perception.</p>
|
|
16
|
+
<button class="puppeteer_test_votable_upvote_button" aria-label="Upvote">Upvote 16</button><button aria-label="1 comment">1</button><button aria-label="12 shares">12</button>
|
|
17
|
+
<div><p>This paragraph is after the action bar and must not pollute the answer.</p></div>
|
|
18
|
+
</section>
|
|
19
|
+
<section id="fixture-answer-queue">
|
|
20
|
+
<div class="puppeteer_test_question_component_base"><a href="/unanswered/How-can-I-build-trust"><div class="puppeteer_test_question_title">How can I build trust?</div></a><a href="/unanswered/How-can-I-build-trust">No answer yet</a><button>Answer</button></div>
|
|
21
|
+
<div class="puppeteer_test_question_component_base"><a href="/How-does-Rust-work"><div class="puppeteer_test_question_title">How does Rust work?</div></a><a href="/How-does-Rust-work">5 answers</a><button>Answer</button></div>
|
|
22
|
+
</section>
|
|
23
|
+
<section id="fixture-profile">Fixture User
|
|
24
|
+
<div>Builds reliable browser automation.</div><div>72,173 followers</div><div>42 following</div>
|
|
25
|
+
<div role="tab">15.9K Answers</div><div role="tab">2 Questions</div><div role="tab">3 Posts</div>
|
|
26
|
+
<div>102.2M content views</div><div>206.7K this month</div>
|
|
27
|
+
</section>
|
|
28
|
+
<section id="fixture-followers">
|
|
29
|
+
<div class="follower-card"><a href="/profile/Ada-Lovelace">Ada Lovelace</a><div>Computing pioneer</div><div>3.5K followers</div><button>Follow</button></div>
|
|
30
|
+
<div class="follower-card"><a href="/profile/Grace-Hopper">Grace Hopper</a><div>Computer scientist</div><div>42 followers</div><button>Follow</button></div>
|
|
31
|
+
</section>
|
|
32
|
+
<section id="fixture-stats">Your content & stats
|
|
33
|
+
<div>Overview</div><div>Last 30 days</div><div>Views<br>–<br>120</div><div>Upvotes<br>–<br>8</div><div>Comments<br>–<br>3</div><div>Shares (external)<br>–<br>2</div>
|
|
34
|
+
</section>
|
|
35
|
+
<section id="fixture-comments">
|
|
36
|
+
<div class="comment-card"><a href="/profile/Commenter-One">Commenter One</a><a href="/comment/1">10h</a><p>A useful comment.</p><button aria-label="Upvote">Upvote 7</button><button>Reply</button></div>
|
|
37
|
+
<div class="comment-card"><a href="/profile/Commenter-Two">Commenter Two</a><a href="/comment/2">Mon</a><p>Another useful comment.</p><button aria-label="Upvote">Upvote</button><button>Reply</button></div>
|
|
38
|
+
</section>
|
|
39
|
+
</body></html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{CommandExecutionError as J,EmptyResultError as K}from"@jackwener/opencli/errors";import{cli as M,Strategy as N}from"@jackwener/opencli/registry";import{evaluateQuoraDom as O,normalizeLimit as T,normalizeQuoraUrl as V,openQuoraPage as W}from"./shared.js";M({site:"quora",name:"answer-comments",access:"read",description:"Read visible comments on one Quora answer.",domain:"www.quora.com",strategy:N.UI,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Quora answer URL"},{name:"limit",type:"int",default:20,help:"Number of comments (max 50)"}],columns:["rank","author","content","upvotes","published","authorUrl"],func:async(b,B)=>{const H=V(B.url,{allowProfile:!1,allowQuestion:!1,allowAnswer:!0}),I=T(B.limit,20,50);await W(b,H.url,"answer comments");if(!(await b.evaluate(`(() => {
|
|
2
|
+
const button = document.querySelector('#mainContent .dom_annotate_answer_action_bar_comment button')
|
|
3
|
+
|| Array.from(document.querySelectorAll('#mainContent button[aria-label]'))
|
|
4
|
+
.find((item) => /comment|评论|comentario|commentaire|kommentar/i.test(item.getAttribute('aria-label') || ''));
|
|
5
|
+
if (!button) return { ok: false };
|
|
6
|
+
button.click();
|
|
7
|
+
return { ok: true, label: button.getAttribute('aria-label') || '' };
|
|
8
|
+
})()`))?.ok)throw new J("Quora answer comment control was not found.");for(let A=0;A<15;A++){if(await b.evaluate(`(() => Array.from(document.querySelectorAll('#mainContent button'))
|
|
9
|
+
.some((button) => /^(reply|回复|responder|répondre|antworten)$/i.test((button.innerText || button.textContent || '').trim())))()`))break;await b.wait(1)}const F=((await O(b,"comments"))?.rows||[]).slice(0,I).map((A,G)=>({...A,rank:G+1}));if(!F.length)throw new K("quora answer-comments","This answer has no visible comments.");return F}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CommandExecutionError as o,EmptyResultError as n}from"@jackwener/opencli/errors";import{cli as s,Strategy as i}from"@jackwener/opencli/registry";import{evaluateQuoraDom as u,normalizeQuoraUrl as l,openQuoraPage as w,waitForSelector as m}from"./shared.js";s({site:"quora",name:"answer-detail",access:"read",description:"Read one complete Quora answer with question, author and engagement metrics.",domain:"www.quora.com",strategy:i.COOKIE,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Quora answer URL"}],columns:["question","author","content","upvotes","comments","shares","published","url"],func:async(e,t)=>{const a=l(t.url,{allowProfile:!1,allowQuestion:!1,allowAnswer:!0});await w(e,a.url,"answer detail");await m(e,".puppeteer_test_question_title","answer question title");const r=(await u(e,"answer-detail"))?.rows?.[0];if(!r)throw new n("quora answer-detail","The answer was not found.");if(!r.content)throw new o("Quora answer body was empty; the visible answer DOM may have changed.");return[r]}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EmptyResultError as u}from"@jackwener/opencli/errors";import{cli as n,Strategy as i}from"@jackwener/opencli/registry";import{evaluateQuoraDom as m,loadVisibleItems as w,normalizeLimit as l,openQuoraPage as c}from"./shared.js";n({site:"quora",name:"answer-queue",access:"read",description:"List personalized Questions for You from Quora's Answer queue.",domain:"www.quora.com",strategy:i.COOKIE,browser:!0,args:[{name:"limit",type:"int",default:10,help:"Number of questions (max 50)"}],columns:["rank","title","answers","url"],func:async(e,o)=>{const r=l(o.limit,10,50);await c(e,"https://www.quora.com/answer","answer queue");await w(e,".puppeteer_test_question_component_base",r);const t=((await m(e,"answer-queue"))?.rows||[]).slice(0,r).map((a,s)=>({...a,rank:s+1}));if(!t.length)throw new u("quora answer-queue","Quora returned no Questions for You.");return t}});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import{ArgumentError as m,CommandExecutionError as e}from"@jackwener/opencli/errors";import{cli as c,Strategy as l}from"@jackwener/opencli/registry";import{assertLiteralContent as d}from"../_shared/content-guard.js";import{insertEditorText as w,normalizeQuoraUrl as b,openQuoraPage as f,readEditorTextJs as p,requireExecute as y}from"./shared.js";c({site:"quora",name:"comment",access:"write",description:"Post a top-level comment on a Quora answer through the visible editor.",domain:"www.quora.com",strategy:l.UI,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Quora answer URL"},{name:"text",type:"string",required:!0,positional:!0,help:"Comment text"},{name:"execute",type:"boolean",help:"Actually post the comment"}],columns:["status","outcome","targetType","target","message"],func:async(t,r)=>{y(r);const a=b(r.url,{allowProfile:!1,allowQuestion:!1,allowAnswer:!0}),o=String(r.text??"");if(!o.trim())throw new m("Comment text cannot be empty");d(o,{label:"comment text"});await f(t,a.url,"comment");if(!await t.evaluate(`(() => {
|
|
2
|
+
const button = document.querySelector('#mainContent .dom_annotate_answer_action_bar_comment button')
|
|
3
|
+
|| Array.from(document.querySelectorAll('#mainContent button[aria-label]'))
|
|
4
|
+
.find((item) => /comment|评论|comentario|commentaire|kommentar/i.test(item.getAttribute('aria-label') || ''));
|
|
5
|
+
if (!button) return false;
|
|
6
|
+
button.click();
|
|
7
|
+
return true;
|
|
8
|
+
})()`))throw new e("Quora answer comment control was not found.");let n=!1;for(let u=0;u<15;u++){n=await t.evaluate(`(() => {
|
|
9
|
+
const editor = document.querySelector('[contenteditable="true"][data-kind="doc"]');
|
|
10
|
+
if (!editor) return false;
|
|
11
|
+
editor.setAttribute('data-pp-quora-editor', '1');
|
|
12
|
+
editor.focus();
|
|
13
|
+
return true;
|
|
14
|
+
})()`);if(n)break;await t.wait(1)}if(!n)throw new e("Quora comment editor did not appear.");const i=await w(t,o);if(!i?.ok)throw new e(i?.message||"Failed to write the Quora comment.");if(!await t.evaluate(`(() => {
|
|
15
|
+
const editor = document.querySelector('[data-pp-quora-editor="1"]');
|
|
16
|
+
let scope = editor;
|
|
17
|
+
const isPost = (button) => /^(post|发布|publicar|publier|veröffentlichen)$/i.test((button.innerText || '').trim());
|
|
18
|
+
while (scope && !Array.from(scope.querySelectorAll('button')).some(isPost)) scope = scope.parentElement;
|
|
19
|
+
const button = scope && Array.from(scope.querySelectorAll('button')).find(isPost);
|
|
20
|
+
if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return false;
|
|
21
|
+
button.click();
|
|
22
|
+
return true;
|
|
23
|
+
})()`))throw new e("Quora comment Post button was missing or disabled.");await t.wait(1);const s=await t.evaluate(p());if(String(s??"").trim()===o.trim())throw new e("Quora did not confirm the comment; check the answer before retrying.");return[{status:"success",outcome:"created",targetType:"answer",target:a.url,message:"Comment posted."}]}});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ArgumentError as
|
|
1
|
+
import{ArgumentError as $,AuthRequiredError as f,CommandExecutionError as G}from"@jackwener/opencli/errors";import{cli as h,Strategy as B}from"@jackwener/opencli/registry";import{assertLiteralContent as I}from"../_shared/content-guard.js";import{tagEditorJs as P,readEditorTextJs as j,insertEditorText as N,clickSubmitJs as M,requireExecute as b,resolveTextInput as J}from"./shared.js";const O=20,A=15,T=["\\banswer\\b","add answer","回答","写回答","répondre","responder","antworten","回答する"],U=["\\bpost\\b","submit","发布","提交","publier","publicar","veröffentlichen","投稿"],y=["\\bpost\\b","done","save","continue","confirm","确定","完成","保存","继续"];function R(){return`(() => {
|
|
2
2
|
const path = location.pathname || '';
|
|
3
3
|
if (/\\blogin\\b/i.test(path + (location.search || ''))) return 'login';
|
|
4
4
|
const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
|
|
@@ -9,14 +9,14 @@ import{ArgumentError as Q,AuthRequiredError as M,CommandExecutionError as z}from
|
|
|
9
9
|
.find((el) => (el.getBoundingClientRect().height || 0) >= 24);
|
|
10
10
|
if (editor) return 'editor';
|
|
11
11
|
// 否则找「Answer」入口按钮点开
|
|
12
|
-
const re = new RegExp(${JSON.stringify(
|
|
12
|
+
const re = new RegExp(${JSON.stringify(T.join("|"))}, 'i');
|
|
13
13
|
const btn = Array.from(document.querySelectorAll('button, [role="button"], a'))
|
|
14
14
|
.filter(visible)
|
|
15
15
|
.find((el) => re.test((el.getAttribute('aria-label') || '') + ' ' + (el.textContent || '')));
|
|
16
16
|
if (btn) { btn.click(); return 'clicked'; }
|
|
17
17
|
return 'wait';
|
|
18
|
-
})()`}
|
|
18
|
+
})()`}h({site:"quora",name:"create-answer",access:"write",description:"Publish an answer to a Quora question from your logged-in account. Pass the target question URL and the answer body (multi-line allowed). Write a genuine, substantive answer — Quora aggressively flags low-effort/automated content.",domain:"quora.com",strategy:B.UI,browser:!0,args:[{name:"questionUrl",type:"string",required:!0,positional:!0,help:"Target Quora question URL (https://www.quora.com/<question-slug>)"},{name:"content",type:"string",positional:!0,help:"Answer body text, or use --file for long content"},{name:"file",type:"string",help:"Read answer body from a local UTF-8 text file"},{name:"execute",type:"boolean",help:"Actually post the answer"}],columns:["status","questionUrl","message"],func:async(z,K)=>{if(!z)throw new G("Browser session required for quora create-answer");b(K);const H=String(K.questionUrl??"").trim();if(!H)throw new $("Question URL is required.");let v;try{v=new URL(H)}catch{throw new $(`Invalid question URL: ${H}`)}if(!/(^|\.)quora\.com$/.test(v.hostname))throw new $("URL must be a quora.com question URL.");const V=await J(K,{field:"content",label:"Answer content"});if(!K.file)I(V,{label:"回答正文",fileFlag:"--file"});await z.goto(H,{waitUntil:"none"});let D=!1;for(let Q=0;Q<O;Q++){const X=await z.evaluate(R());if(X==="login")throw new f("www.quora.com","Quora 重定向到登录页——未登录");if(X==="editor"){D=!0;break}await z.wait(1)}if(!D)throw new G("未能打开回答编辑器——问题可能不可回答,或页面结构已变(需真机 recon 收紧锚点)。");if(await z.evaluate(P())!=="ready")throw new G("回答编辑器打标失败——未定位到可编辑正文区。");const F=await N(z,V);if(!F?.ok)throw new G(F?.message??"无法把正文写入 Quora 回答编辑器。");const W=await z.evaluate(M(U));if(!W?.ok)throw new G(W?.message??"未找到 Quora 回答提交按钮。");await z.wait(2);if((await z.evaluate(`(() => {
|
|
19
19
|
const dlg = document.querySelector('[role="dialog"]');
|
|
20
20
|
if (!dlg || (dlg.offsetParent === null && dlg.getClientRects().length === 0)) return { present: false };
|
|
21
21
|
return { present: true };
|
|
22
|
-
})()`))?.present){await
|
|
22
|
+
})()`))?.present){await z.evaluate(M(y));await z.wait(2)}const Y=(Q)=>String(Q||"").replace(/\s+/g," ").trim(),Z=await z.evaluate(j());if(Z!=null&&Y(Z).length>0&&Y(Z)===Y(V))throw new G("Quora 回答提交后正文仍在编辑器中——提交被拦(限流/风控/无权限,或 credential 弹窗未确认)。回查后再决定是否重试。");return[{status:"success",questionUrl:H,message:"Answer posted."}]}});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ArgumentError as
|
|
1
|
+
import{ArgumentError as X,AuthRequiredError as q,CommandExecutionError as F}from"@jackwener/opencli/errors";import{cli as D,Strategy as M}from"@jackwener/opencli/registry";import{assertLiteralContent as P}from"../_shared/content-guard.js";import{QUORA_ORIGIN as Q,insertEditorText as f,readEditorTextJs as h,clickSubmitJs as B,requireExecute as J}from"./shared.js";const N=20,U=["add question","ask question","\\bask\\b","提问","添加问题","poser une question","preguntar","frage stellen"],j=["add question","\\bnext\\b","submit","done","\\bpost\\b","提交","下一步","发布","完成","添加问题"];function I(){return`(() => {
|
|
2
2
|
if (/\\blogin\\b/i.test((location.pathname || '') + (location.search || ''))) return 'login';
|
|
3
3
|
const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
|
|
4
4
|
const dlg = document.querySelector('[role="dialog"]');
|
|
@@ -15,10 +15,10 @@ import{ArgumentError as G,AuthRequiredError as X,CommandExecutionError as F}from
|
|
|
15
15
|
}
|
|
16
16
|
return 'wait';
|
|
17
17
|
}
|
|
18
|
-
const re = new RegExp(${JSON.stringify(
|
|
18
|
+
const re = new RegExp(${JSON.stringify(U.join("|"))}, 'i');
|
|
19
19
|
const btn = Array.from(document.querySelectorAll('button, [role="button"], a'))
|
|
20
20
|
.filter(visible)
|
|
21
21
|
.find((el) => re.test((el.getAttribute('aria-label') || '') + ' ' + (el.textContent || '')));
|
|
22
22
|
if (btn) { btn.click(); return 'clicked'; }
|
|
23
23
|
return 'wait';
|
|
24
|
-
})()`}D({site:"quora",name:"create-question",access:"write",description:"Post a new question to Quora from your logged-in account. Pass the question title. Ask a clear, genuine question — Quora aggressively flags low-effort/automated content.",domain:"quora.com",strategy:M.UI,browser:!0,args:[{name:"title",type:"string",required:!0,positional:!0,help:"The question title (a clear, complete question)"}],columns:["status","title","message"],func:async(v,
|
|
24
|
+
})()`}D({site:"quora",name:"create-question",access:"write",description:"Post a new question to Quora from your logged-in account. Pass the question title. Ask a clear, genuine question — Quora aggressively flags low-effort/automated content.",domain:"quora.com",strategy:M.UI,browser:!0,args:[{name:"title",type:"string",required:!0,positional:!0,help:"The question title (a clear, complete question)"},{name:"execute",type:"boolean",help:"Actually post the question"}],columns:["status","title","message"],func:async(v,W)=>{if(!v)throw new F("Browser session required for quora create-question");J(W);const z=String(W.title??"").trim();if(!z)throw new X("Question title is required.");P(z,{label:"问题标题"});await v.goto(`${Q}/`,{waitUntil:"none"});let Y=!1;for(let H=0;H<N;H++){const G=await v.evaluate(I());if(G==="login")throw new q("www.quora.com","Quora 重定向到登录页——未登录");if(G==="editor"){Y=!0;break}await v.wait(1)}if(!Y)throw new F("未能打开 Quora 提问弹窗——顶栏入口或弹窗结构已变(需真机 recon 收紧锚点)。");const Z=await f(v,z);if(!Z?.ok)throw new F(Z?.message??"无法把标题写入 Quora 提问框。");const $=await v.evaluate(B(j));if(!$?.ok)throw new F($?.message??"未找到 Quora 提问提交按钮。");await v.wait(2);const K=(H)=>String(H||"").replace(/\s+/g," ").trim(),V=await v.evaluate(h());if(V!=null&&K(V).length>0&&K(V)===K(z))throw new F("Quora 提问提交后标题仍在框中——提交被拦(限流/风控/需选主题或身份未确认)。回查后再决定是否重试。");return[{status:"success",title:z,message:"Question posted."}]}});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import{CommandExecutionError as n}from"@jackwener/opencli/errors";import{cli as l,Strategy as i}from"@jackwener/opencli/registry";import{normalizeQuoraUrl as s,openQuoraPage as u,requireExecute as c}from"./shared.js";const f=["follow","关注","seguir","suivre","folgen"],w=["following","unfollow","已关注","取消关注","siguiendo","abonné","gefolgt"];function a(e){return`(() => {
|
|
2
|
+
const clean = (value) => String(value || '').replace(/\\s+/g, ' ').trim().toLowerCase();
|
|
3
|
+
const following = new RegExp(${JSON.stringify(w.join("|"))}, 'i');
|
|
4
|
+
const follow = new RegExp('^(' + ${JSON.stringify(f.join("|"))} + ')(?:\\s*·.*)?$', 'i');
|
|
5
|
+
const candidates = Array.from(document.querySelectorAll('#mainContent button')).filter((button) => {
|
|
6
|
+
const text = clean((button.getAttribute('aria-label') || '') + ' ' + (button.innerText || ''));
|
|
7
|
+
return following.test(text) || follow.test(text);
|
|
8
|
+
});
|
|
9
|
+
const button = candidates[0];
|
|
10
|
+
if (!button) return { ok: false, message: 'Follow control missing' };
|
|
11
|
+
const state = clean((button.getAttribute('aria-label') || '') + ' ' + (button.innerText || ''));
|
|
12
|
+
if (following.test(state)) return { ok: true, already: true };
|
|
13
|
+
if (${JSON.stringify(e)}) button.click();
|
|
14
|
+
return { ok: true, already: false };
|
|
15
|
+
})()`}l({site:"quora",name:"follow",access:"write",description:"Follow a Quora profile or question through the visible UI.",domain:"www.quora.com",strategy:i.UI,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Quora profile or question URL"},{name:"execute",type:"boolean",help:"Actually follow the target"}],columns:["status","outcome","targetType","target","message"],func:async(e,o)=>{c(o);const t=s(o.url,{allowProfile:!0,allowQuestion:!0,allowAnswer:!1});await u(e,t.url,"follow");const r=await e.evaluate(a(!0));if(!r?.ok)throw new n(`Quora ${t.kind} follow control was not found.`);if(r.already)return[{status:"success",outcome:"already_applied",targetType:t.kind,target:t.url,message:`${t.kind} was already followed.`}];await e.wait(1);if(!(await e.evaluate(a(!1)))?.already)throw new n(`Quora did not confirm the ${t.kind} follow; check the target before retrying.`);return[{status:"success",outcome:"applied",targetType:t.kind,target:t.url,message:`${t.kind} followed.`}]}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EmptyResultError as s}from"@jackwener/opencli/errors";import{cli as n,Strategy as m}from"@jackwener/opencli/registry";import{evaluateQuoraDom as u,loadVisibleItems as f,normalizeLimit as w,normalizeProfileTarget as c,openQuoraPage as p}from"./shared.js";n({site:"quora",name:"followers",access:"read",description:"List followers of a Quora profile.",domain:"www.quora.com",strategy:m.COOKIE,browser:!0,args:[{name:"user",type:"string",required:!0,positional:!0,help:"Profile slug or Quora profile URL"},{name:"limit",type:"int",default:20,help:"Number of followers (max 50)"}],columns:["rank","name","headline","followers","url"],func:async(r,o)=>{const e=c(o.user),l=w(o.limit,20,50);await p(r,`${e.url}/followers`,"followers");await f(r,'#mainContent a[href*="/profile/"]',l);const t=((await u(r,"followers"))?.rows||[]).slice(0,l).map((a,i)=>({...a,rank:i+1}));if(!t.length)throw new s("quora followers",`${e.slug} has no visible followers.`);return t}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EmptyResultError as u}from"@jackwener/opencli/errors";import{cli as l,Strategy as m}from"@jackwener/opencli/registry";import{evaluateQuoraDom as c,loadVisibleItems as w,normalizeLimit as q,normalizeQuoraUrl as d,openQuoraPage as p}from"./shared.js";l({site:"quora",name:"question",access:"read",description:"Read a Quora question and its visible answers, including engagement metrics.",domain:"www.quora.com",strategy:m.COOKIE,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Quora question URL"},{name:"limit",type:"int",default:10,help:"Number of answers (max 50)"}],columns:["rank","question","author","excerpt","upvotes","comments","shares","published","url"],func:async(e,t)=>{const a=d(t.url,{allowProfile:!1,allowQuestion:!0,allowAnswer:!1}),o=q(t.limit,10,50);await p(e,a.url,"question");await w(e,'[class*="dom_annotate_question_answer_item_"]',o);const r=await c(e,"question"),i=(r?.rows||[]).slice(0,o).map((s,n)=>({...s,rank:n+1,question:r.title||s.question}));if(!i.length)throw new u("quora question","No visible answers were found for this question.");return i}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ArgumentError as i,EmptyResultError as l}from"@jackwener/opencli/errors";import{cli as c,Strategy as m}from"@jackwener/opencli/registry";import{evaluateQuoraDom as p,normalizeLimit as h,openQuoraPage as q,loadVisibleItems as w}from"./shared.js";c({site:"quora",name:"search",access:"read",description:"Search Quora questions or answers from the visible logged-in search results.",domain:"www.quora.com",strategy:m.COOKIE,browser:!0,args:[{name:"query",type:"string",required:!0,positional:!0,help:"Search query"},{name:"type",type:"string",default:"question",choices:["question","answer"],help:"Result type: question or answer"},{name:"limit",type:"int",default:10,help:"Number of results (max 50)"}],columns:["rank","type","title","author","excerpt","answers","upvotes","comments","shares","published","url"],func:async(r,s)=>{const n=String(s.query??"").trim();if(!n)throw new i("Search query is required");const e=String(s.type??"question");if(!["question","answer"].includes(e))throw new i("--type must be question or answer");const a=h(s.limit,10,50),o=`https://www.quora.com/search?q=${encodeURIComponent(n)}&type=${e}`;await q(r,o,"search");await w(r,".puppeteer_test_question_title",a);const u=((await p(r,e==="answer"?"search-answer":"search-question"))?.rows||[]).slice(0,a).map((t)=>({rank:t.rank,type:e,title:e==="answer"?t.question:t.title,author:e==="answer"?t.author:null,excerpt:e==="answer"?t.excerpt:null,answers:e==="question"?t.answers:null,upvotes:e==="answer"?t.upvotes:null,comments:e==="answer"?t.comments:null,shares:e==="answer"?t.shares:null,published:e==="answer"?t.published:null,url:t.url}));if(!u.length)throw new l("quora search",`No ${e} results for "${n}"; the page structure may have changed.`);return u}});
|
package/clis/quora/shared.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AuthRequiredError as
|
|
1
|
+
import{ArgumentError as M,AuthRequiredError as T,CommandExecutionError as _}from"@jackwener/opencli/errors";import{readFile as C}from"node:fs/promises";export const QUORA_ORIGIN="https://www.quora.com";export async function hasQuoraAuthCookie(H){return(await H.getCookies({url:QUORA_ORIGIN})).some((Y)=>Y.name==="m-b"&&Y.value)}export async function verifyQuoraAuth(H){if(!await hasQuoraAuthCookie(H))throw new T("www.quora.com","Quora m-b cookie 缺失——当前是游客/未登录会话");await H.goto(QUORA_ORIGIN);await H.wait(2);const J=await H.evaluate("location.pathname + location.search");if(/\blogin\b/i.test(String(J||"")))throw new T("www.quora.com",`Quora 停在登录页 (${J})——已登出`);if(!await hasQuoraAuthCookie(H))throw new T("www.quora.com","Quora m-b cookie 在导航后消失——会话失效");return{logged_in:!0}}export const EDITOR_MARK='[data-pp-quora-editor="1"]';export function tagEditorJs(){return`(() => {
|
|
2
2
|
const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
|
|
3
3
|
const scope = document.querySelector('[role="dialog"]') || document;
|
|
4
4
|
const cands = Array.from(scope.querySelectorAll('[contenteditable="true"]'))
|
|
@@ -12,32 +12,47 @@ import{AuthRequiredError as P}from"@jackwener/opencli/errors";export const QUORA
|
|
|
12
12
|
return 'ready';
|
|
13
13
|
})()`}export function readEditorTextJs(){return`(() => {
|
|
14
14
|
const ed = document.querySelector('${EDITOR_MARK}');
|
|
15
|
-
return ed ? (ed.innerText || ed.textContent || '') : null;
|
|
16
|
-
})()`}export function execInsertJs(
|
|
15
|
+
return ed ? ('value' in ed ? ed.value : (ed.innerText || ed.textContent || '')) : null;
|
|
16
|
+
})()`}export function execInsertJs(H){return`(() => {
|
|
17
17
|
try {
|
|
18
18
|
const ed = document.querySelector('${EDITOR_MARK}');
|
|
19
19
|
if (!ed) return { ok: false, message: '编辑器已消失,无法写入。' };
|
|
20
20
|
ed.focus();
|
|
21
21
|
const normalize = (s) => String(s || '').replace(/\\u00a0/g, ' ').replace(/\\s+/g, ' ').trim();
|
|
22
|
-
if (!document.execCommand('insertText', false, ${JSON.stringify(
|
|
22
|
+
if (!document.execCommand('insertText', false, ${JSON.stringify(H)})) {
|
|
23
23
|
const dt = new DataTransfer();
|
|
24
|
-
dt.setData('text/plain', ${JSON.stringify(
|
|
24
|
+
dt.setData('text/plain', ${JSON.stringify(H)});
|
|
25
25
|
ed.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true }));
|
|
26
26
|
}
|
|
27
|
-
const actual = ed.innerText || ed.textContent || '';
|
|
28
|
-
if (normalize(actual).includes(normalize(${JSON.stringify(
|
|
27
|
+
const actual = 'value' in ed ? ed.value : (ed.innerText || ed.textContent || '');
|
|
28
|
+
if (normalize(actual).includes(normalize(${JSON.stringify(H)}))) return { ok: true };
|
|
29
29
|
return { ok: false, message: '写入后未在编辑器中校验到正文。', actualText: actual };
|
|
30
30
|
} catch (e) { return { ok: false, message: String(e) }; }
|
|
31
|
-
})()`}export async function insertEditorText(
|
|
31
|
+
})()`}export async function insertEditorText(H,J){const Y=(L)=>String(L||"").replace(/ /g," ").replace(/\s+/g," ").trim(),X=[H.nativeType?.bind(H),H.insertText?.bind(H)].filter(Boolean);for(const L of X)try{await L(J);const $=await H.evaluate(readEditorTextJs());if($!=null&&Y($).includes(Y(J)))return{ok:!0}}catch($){const U=($ instanceof Error?$.message:String($)).toLowerCase();if(!(U.includes("unknown action")||U.includes("not supported")||U.includes("no inserted flag")))throw $}return H.evaluate(execInsertJs(J))}export function clickSubmitJs(H){return`(() => {
|
|
32
32
|
const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
|
|
33
|
-
const re = new RegExp(${JSON.stringify(
|
|
33
|
+
const re = new RegExp(${JSON.stringify(H.join("|"))}, 'i');
|
|
34
34
|
const scope = document.querySelector('[role="dialog"]') || document;
|
|
35
35
|
const btns = Array.from(scope.querySelectorAll('button, [role="button"]')).filter(visible);
|
|
36
36
|
// 文案匹配至多兜底:优先取未禁用且文案命中的按钮
|
|
37
37
|
const enabled = (el) => !el.disabled && el.getAttribute('aria-disabled') !== 'true';
|
|
38
|
-
const hit =
|
|
38
|
+
const hit = scope.querySelector('button.puppeteer_test_modal_submit:not([disabled])')
|
|
39
|
+
|| btns.find((el) => enabled(el)
|
|
39
40
|
&& re.test((el.getAttribute('aria-label') || '') + ' ' + (el.textContent || '')));
|
|
40
41
|
if (!hit) return { ok: false, message: '未找到可用的提交按钮(可能未渲染/被禁用/文案不在兜底名单)。' };
|
|
41
42
|
hit.click();
|
|
42
43
|
return { ok: true };
|
|
43
|
-
})()`}
|
|
44
|
+
})()`}export function requireExecute(H){if(H.execute!==!0)throw new M("Refusing to write without --execute. Review the target and content, then rerun with --execute.")}export async function resolveTextInput(H,{field:J="content",label:Y="content"}={}){const X=H[J]==null?"":String(H[J]),L=H.file==null?"":String(H.file).trim();if(X&&L)throw new M(`Use either <${J}> or --file, not both`);let $=X;if(L)try{$=await C(L,"utf8")}catch(D){throw new M(`Cannot read --file ${L}: ${D instanceof Error?D.message:String(D)}`)}if(!$.trim())throw new M(`${Y} cannot be empty`);return $}export function normalizeLimit(H,J=10,Y=50){const X=H==null?J:Number(H);if(!Number.isInteger(X)||X<1)throw new M("--limit must be a positive integer");if(X>Y)throw new M(`--limit must be <= ${Y}`);return X}export function parseCompactCount(H){const Y=String(H??"").trim().replace(/,/g,"").match(/(-?\d+(?:\.\d+)?)\s*([KMB])?/i);if(!Y)return null;const X={K:1000,M:1e6,B:1e9}[String(Y[2]||"").toUpperCase()]||1;return Math.round(Number(Y[1])*X)}export function normalizeQuoraUrl(H,{allowProfile:J=!0,allowQuestion:Y=!0,allowAnswer:X=!0}={}){const L=String(H??"").trim();if(!L)throw new M("A Quora URL is required");let $;try{$=new URL(L.startsWith("/")?`${QUORA_ORIGIN}${L}`:L)}catch{throw new M(`Invalid Quora URL: ${L}`)}if($.protocol!=="https:"||$.username||$.password||$.port||!/(^|\.)quora\.com$/i.test($.hostname))throw new M("URL must be an https://*.quora.com URL");const D=$.pathname.replace(/\/+$/,"")||"/",U=/^\/profile\/[^/]+$/i.test(D),y=/^\/(?!profile\/|topic\/|search$|answer$|stats$)[^/]+\/answers?\/[^/]+$/i.test(D),j=/^\/(?:unanswered\/)?(?!profile\/|topic\/|search$|answer$|stats$)[^/]+$/i.test(D);if((!J||!U)&&(!X||!y)&&(!Y||!j))throw new M("Expected a Quora profile, question, or answer URL supported by this command");$.hash="";return{url:$.toString(),path:D,kind:U?"profile":y?"answer":"question"}}export function normalizeProfileTarget(H){const J=String(H??"").trim();if(!J)throw new M("Profile slug or URL is required");if(/^[^/?#\s]+$/.test(J))return{slug:J,url:`${QUORA_ORIGIN}/profile/${encodeURIComponent(J)}`};const Y=normalizeQuoraUrl(J,{allowProfile:!0,allowQuestion:!1,allowAnswer:!1});return{slug:decodeURIComponent(Y.path.split("/").pop()),url:Y.url}}export async function waitForSelector(H,J,Y,X=20){for(let L=0;L<X;L++){if(await H.evaluate(`(() => !!document.querySelector(${JSON.stringify(J)}))()`))return;await H.wait(1)}throw new _(`Timed out waiting for ${Y}; Quora's visible page structure may have changed.`)}export async function openQuoraPage(H,J,Y){if(!H)throw new _(`Browser session required for quora ${Y}`);try{await H.goto(J,{waitUntil:"none"})}catch(L){throw new _(`Failed to open Quora ${Y}: ${L instanceof Error?L.message:String(L)}`)}const X=new URL(J).pathname.replace(/\/+$/,"")||"/";for(let L=0;L<20;L++){const $=await H.evaluate(`(() => ({
|
|
45
|
+
login: /\\blogin\\b/i.test((location.pathname || '') + (location.search || '')),
|
|
46
|
+
ready: !!document.querySelector('#mainContent'),
|
|
47
|
+
path: (location.pathname || '/').replace(/\\/+$/, '') || '/',
|
|
48
|
+
}))()`);if($?.login)throw new T("www.quora.com",`Quora login is required for ${Y}`);if($?.ready&&$?.path===X)return;await H.wait(1)}throw new _(`Timed out opening Quora ${Y}`)}export async function loadVisibleItems(H,J,Y){let X=-1;for(let L=0;L<12;L++){const $=await H.evaluate(`(() => document.querySelectorAll(${JSON.stringify(J)}).length)()`);if($>=Y)return $;if($===X&&L>=2)return $;X=$;await H.evaluate("(() => { window.scrollTo(0, document.body.scrollHeight); return true; })()");await H.wait(1)}return H.evaluate(`(() => document.querySelectorAll(${JSON.stringify(J)}).length)()`)}export function extractQuoraDom(H,J="#mainContent"){const Y=document.querySelector(J);if(!Y)return{ok:!1,reason:"main-content-missing",rows:[]};const X=(j)=>String(j??"").replace(/\u00a0/g," ").replace(/[ \t]+/g," ").replace(/\n{3,}/g,`
|
|
49
|
+
|
|
50
|
+
`).trim(),L=(j)=>{const W=X(j).replace(/,/g,"").match(/(-?\d+(?:\.\d+)?)\s*([KMB])?/i);if(!W)return null;const Z={K:1000,M:1e6,B:1e9};return Math.round(Number(W[1])*(Z[String(W[2]||"").toUpperCase()]||1))},$=(j)=>{if(!j)return"";try{return new URL(j,location.origin).toString()}catch{return""}},D=(j,W)=>{const Z=W==="upvotes"?"button.puppeteer_test_votable_upvote_button":W==="comments"?".dom_annotate_answer_action_bar_comment button":".dom_annotate_answer_action_bar_share button",B=j.querySelector(Z)||Array.from(j.querySelectorAll("button[aria-label]")).find((G)=>{const q=G.getAttribute("aria-label")||"";return W==="upvotes"?/upvot|赞同|votar|vote/i.test(q):W==="comments"?/comment|评论|comentario|commentaire|kommentar/i.test(q):/share|分享|compartir|partager|teilen/i.test(q)});if(!B)return 0;const K=B.getAttribute("aria-label")||"";return L(`${K} ${B.innerText||B.textContent||""}`)||0},U=(j,W)=>{let Z=j;while(Z.parentElement&&Z.parentElement!==Y&&Z.parentElement.querySelectorAll(W).length===1)Z=Z.parentElement;return Z},y=()=>Array.from(Y.querySelectorAll(".puppeteer_test_answer_content")).map((j,W)=>{const Z=U(j,".puppeteer_test_answer_content"),z=Array.from(Z.querySelectorAll('a[href*="/profile/"]')).find((q)=>X(q.innerText||q.textContent)),B=Z.querySelector("a.answer_timestamp[href]"),K=Z.querySelector(".puppeteer_test_question_title"),G=X(Z.innerText||Z.textContent).split(`
|
|
51
|
+
`).filter(Boolean);return{rank:W+1,question:X(K?.innerText||K?.textContent),author:X(z?.innerText||z?.textContent||G[0]||"Anonymous"),excerpt:X(j.innerText||j.textContent).replace(/\s*\(more\)\s*$/i,""),upvotes:D(Z,"upvotes"),comments:D(Z,"comments"),shares:D(Z,"shares"),published:X(B?.innerText||B?.textContent),url:$(B?.getAttribute("href")||"")}});if(H==="question"||H==="answers"||H==="search-answer")return{ok:!0,title:X(Y.querySelector(".puppeteer_test_question_title")?.innerText||Y.querySelector(".puppeteer_test_question_title")?.textContent),rows:y()};if(H==="search-question"){const j=new Set,W=[];for(const Z of Y.querySelectorAll(".puppeteer_test_question_title")){const z=Z.closest("a[href]")||Z.parentElement?.closest("a[href]"),B=$(z?.getAttribute("href")||"");if(!B||j.has(B))continue;j.add(B);const K=U(Z,".puppeteer_test_question_title"),G=Array.from(K.querySelectorAll("a[href]")).map((q)=>X(q.innerText||q.textContent)).find((q)=>/answers?/i.test(q))||"";W.push({rank:W.length+1,title:X(Z.innerText||Z.textContent),answers:L(G)||0,url:B})}return{ok:!0,rows:W}}if(H==="answer-detail"){const j=Y.querySelector("a.answer_timestamp[href]"),W=Y.querySelector("button.puppeteer_test_votable_upvote_button[aria-label]"),Z=Array.from(Y.querySelectorAll('a[href*="/profile/"]')).find((G)=>X(G.innerText||G.textContent)),z=Y.querySelector(".puppeteer_test_question_title"),B=Array.from(Y.querySelectorAll("p, li")).filter((G)=>{if(!j||!W)return!1;return Boolean(j.compareDocumentPosition(G)&4)&&Boolean(G.compareDocumentPosition(W)&4)}).map((G)=>X(G.innerText||G.textContent)).filter(Boolean),K=B.length?B.join(`
|
|
52
|
+
|
|
53
|
+
`):X(Y.querySelector(".puppeteer_test_answer_content")?.innerText||Y.querySelector(".puppeteer_test_answer_content")?.textContent);return{ok:!0,rows:[{question:X(z?.innerText||z?.textContent),author:X(Z?.innerText||Z?.textContent||"Anonymous"),content:K,upvotes:D(Y,"upvotes"),comments:D(Y,"comments"),shares:D(Y,"shares"),published:X(j?.innerText||j?.textContent),url:$(j?.getAttribute("href")||location.href)}]}}if(H==="answer-queue")return{ok:!0,rows:Array.from(Y.querySelectorAll(".puppeteer_test_question_component_base")).map((W,Z)=>{const z=W.querySelector(".puppeteer_test_question_title"),B=z?.closest("a[href]")||z?.parentElement?.closest("a[href]"),K=Array.from(W.querySelectorAll("a[href]")).map((G)=>X(G.innerText||G.textContent)).find((G)=>/answers?|no answer/i.test(G))||"";return{rank:Z+1,title:X(z?.innerText||z?.textContent),answers:/no answer/i.test(K)?0:L(K)||0,url:$(B?.getAttribute("href")||"")}})};if(H==="profile"){const j=X(Y.innerText||Y.textContent),W=J==="#mainContent"?X(document.body.innerText||document.body.textContent):j,Z=j.split(`
|
|
54
|
+
`).map(X).filter(Boolean),z=X(document.title.replace(/\s*-\s*Quora\s*$/i,""))||Z[0]||"",B=Array.from(Y.querySelectorAll("div, span")).map((q)=>X(q.innerText||q.textContent)).filter((q)=>q&&q.length<=300),K=(q)=>L(j.match(q)?.[1]||""),G=L(W.match(/([\d.,]+\s*[KMB]?)\s+content views/i)?.[1]||"");return{ok:!0,rows:[{name:z,bio:B.find((q)=>q!==z&&!/followers?|following|answers?|questions?|posts?|profile|content views|this month/i.test(q))||Z.find((q)=>q!==z&&!/followers?|following|answers?|questions?|posts?|profile|content views|this month/i.test(q))||"",followers:K(/([\d.,]+\s*[KMB]?)\s+followers/i),following:K(/([\d.,]+\s*[KMB]?)\s+following/i),answers:K(/([\d.,]+\s*[KMB]?)\s+answers/i),questions:K(/([\d.,]+\s*[KMB]?)\s+questions/i),posts:K(/([\d.,]+\s*[KMB]?)\s+posts/i),contentViews:G,url:location.href.split(/[?#]/)[0].replace(/\/(?:answers|questions|posts|followers|following)\/?$/,"")}]}}if(H==="followers"){const j=new Set,W=[];for(const Z of Y.querySelectorAll('a[href*="/profile/"]')){const z=X(Z.innerText||Z.textContent),B=$(Z.getAttribute("href")||"");if(!z||!B||j.has(B))continue;let K=Z;while(K.parentElement&&K.parentElement!==Y){const F=K.parentElement;if(new Set(Array.from(F.querySelectorAll('a[href*="/profile/"]'),(I)=>$(I.getAttribute("href")||""))).size>1)break;K=F;if(/\d[\d.,]*\s*[KMB]?\s+followers?/i.test(X(K.innerText||K.textContent)))break}const G=X(K.innerText||K.textContent),q=G.match(/([\d.,]+\s*[KMB]?)\s+followers?/i),V=G.split(`
|
|
55
|
+
`).map(X).filter(Boolean),S=Array.from(K.querySelectorAll("div, span")).map((F)=>X(F.innerText||F.textContent)).filter((F)=>F&&F.length<=300);W.push({rank:W.length+1,name:z,headline:S.find((F)=>F!==z&&!/followers?|^follow$/i.test(F))||V.find((F)=>F!==z&&!/followers?|^follow$/i.test(F))||"",followers:L(q?.[1]||"")||0,url:B});j.add(B)}return{ok:!0,rows:W}}if(H==="stats"){const j=X(Y.innerText||Y.textContent),W=(z)=>{const B=j.match(new RegExp(`${z}\\s*[–—-]?\\s*([\\d.,]+\\s*[KMB]?)`,"i"));return L(B?.[1]||"")||0};return{ok:!0,rows:[{period:j.match(/Last\s+\d+\s+days|All time/i)?.[0]||"Current",views:W("Views"),upvotes:W("Upvotes"),comments:W("Comments"),shares:W("Shares \\(external\\)")}]}}if(H==="comments"){const j=new Set,W=[],Z=Array.from(Y.querySelectorAll("button")).filter((z)=>/^reply$/i.test(X(z.innerText||z.textContent)));for(const z of Z){let B=z;while(B.parentElement&&B.parentElement!==Y){const P=B.parentElement;if(Array.from(P.querySelectorAll("button")).filter((Q)=>/^reply$/i.test(X(Q.innerText||Q.textContent))).length!==1)break;B=P;if(B.querySelector('a[href*="/profile/"]'))break}const K=Array.from(B.querySelectorAll('a[href*="/profile/"]')).find((P)=>X(P.innerText||P.textContent)),G=X(K?.innerText||K?.textContent),q=$(K?.getAttribute("href")||""),V=X(B.innerText||B.textContent),S=`${q}:${V}`;if(!G||j.has(S))continue;const F=Array.from(B.querySelectorAll("a")).map((P)=>X(P.innerText||P.textContent)).find((P)=>/^(?:updated\s+)?(?:\d+[smhdwy]|mon|tue|wed|thu|fri|sat|sun|[A-Z][a-z]{2}\s+\d{1,2})$/i.test(P))||V.match(/(?:^|\s)(\d+[smhdwy]|mon|tue|wed|thu|fri|sat|sun)(?:\s|$)/i)?.[1]||"",O=Array.from(B.querySelectorAll("p")).map((P)=>X(P.innerText||P.textContent)).filter(Boolean),I=O.length?O.join(`
|
|
56
|
+
`):V.split(`
|
|
57
|
+
`).map(X).filter((P)=>P&&P!==G&&P!==F&&!/^reply$/i.test(P)).join(`
|
|
58
|
+
`);W.push({rank:W.length+1,author:G,content:I,upvotes:D(B,"upvotes"),published:F,authorUrl:q});j.add(S)}return{ok:!0,rows:W}}return{ok:!1,reason:`unsupported-mode:${H}`,rows:[]}}export async function evaluateQuoraDom(H,J,Y="#mainContent"){return H.evaluate(`(${extractQuoraDom.toString()})(${JSON.stringify(J)}, ${JSON.stringify(Y)})`)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CommandExecutionError as r}from"@jackwener/opencli/errors";import{cli as e,Strategy as a}from"@jackwener/opencli/registry";import{evaluateQuoraDom as s,openQuoraPage as n,waitForSelector as c}from"./shared.js";e({site:"quora",name:"stats",access:"read",description:"Read the logged-in account's Quora content overview for the current stats period.",domain:"www.quora.com",strategy:a.COOKIE,browser:!0,args:[],columns:["period","views","upvotes","comments","shares"],func:async(t)=>{await n(t,"https://www.quora.com/stats","content stats");await c(t,"#mainContent","stats content");const o=(await s(t,"stats"))?.rows?.[0];if(!o)throw new r("Quora stats overview was not found.");return[o]}});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import{CommandExecutionError as a}from"@jackwener/opencli/errors";import{cli as u,Strategy as n}from"@jackwener/opencli/registry";import{normalizeQuoraUrl as s,openQuoraPage as i,requireExecute as l,waitForSelector as p}from"./shared.js";u({site:"quora",name:"upvote",access:"write",description:"Upvote one Quora answer through the visible logged-in UI. Idempotent when already upvoted.",domain:"www.quora.com",strategy:n.UI,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Quora answer URL"},{name:"execute",type:"boolean",help:"Actually apply the upvote"}],columns:["status","outcome","targetType","target","message"],func:async(t,o)=>{l(o);const e=s(o.url,{allowProfile:!1,allowQuestion:!1,allowAnswer:!0});await i(t,e.url,"upvote");await p(t,"button.puppeteer_test_votable_upvote_button","answer upvote button");const r=await t.evaluate(`(() => {
|
|
2
|
+
const button = document.querySelector('button.puppeteer_test_votable_upvote_button');
|
|
3
|
+
if (!button) return { ok: false, message: 'Upvote button missing' };
|
|
4
|
+
const state = ((button.getAttribute('aria-label') || '') + ' ' + (button.innerText || '')).trim();
|
|
5
|
+
const selected = button.getAttribute('aria-pressed') === 'true' || /upvoted|remove upvote/i.test(state);
|
|
6
|
+
if (selected) return { ok: true, already: true };
|
|
7
|
+
button.click();
|
|
8
|
+
return { ok: true, already: false };
|
|
9
|
+
})()`);if(!r?.ok)throw new a(r?.message||"Failed to find the Quora upvote control.");if(r.already)return[{status:"success",outcome:"already_applied",targetType:"answer",target:e.url,message:"Answer was already upvoted."}];await t.wait(1);if(!await t.evaluate(`(() => {
|
|
10
|
+
const button = document.querySelector('button.puppeteer_test_votable_upvote_button');
|
|
11
|
+
if (!button) return false;
|
|
12
|
+
const state = ((button.getAttribute('aria-label') || '') + ' ' + (button.innerText || '')).trim();
|
|
13
|
+
return button.getAttribute('aria-pressed') === 'true' || /upvoted|remove upvote/i.test(state);
|
|
14
|
+
})()`))throw new a("Quora did not confirm the upvote; check the answer before retrying.");return[{status:"success",outcome:"applied",targetType:"answer",target:e.url,message:"Answer upvoted."}]}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EmptyResultError as n}from"@jackwener/opencli/errors";import{cli as u,Strategy as l}from"@jackwener/opencli/registry";import{evaluateQuoraDom as m,loadVisibleItems as w,normalizeLimit as c,normalizeProfileTarget as p,openQuoraPage as f}from"./shared.js";u({site:"quora",name:"user-answers",access:"read",description:"List answers published by a Quora profile with engagement metrics.",domain:"www.quora.com",strategy:l.COOKIE,browser:!0,args:[{name:"user",type:"string",required:!0,positional:!0,help:"Profile slug or Quora profile URL"},{name:"limit",type:"int",default:10,help:"Number of answers (max 50)"}],columns:["rank","question","author","excerpt","upvotes","comments","shares","published","url"],func:async(e,r)=>{const t=p(r.user),s=c(r.limit,10,50);await f(e,`${t.url}/answers`,"user answers");await w(e,".puppeteer_test_answer_content",s);const a=((await m(e,"answers"))?.rows||[]).slice(0,s).map((o,i)=>({...o,rank:i+1}));if(!a.length)throw new n("quora user-answers",`${t.slug} has no visible answers.`);return a}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CommandExecutionError as t}from"@jackwener/opencli/errors";import{cli as i,Strategy as n}from"@jackwener/opencli/registry";import{evaluateQuoraDom as s,normalizeProfileTarget as l,openQuoraPage as u}from"./shared.js";i({site:"quora",name:"user",access:"read",description:"Read a Quora profile with follower, content and view totals.",domain:"www.quora.com",strategy:n.COOKIE,browser:!0,args:[{name:"user",type:"string",required:!0,positional:!0,help:"Profile slug or Quora profile URL"}],columns:["name","bio","followers","following","answers","questions","posts","contentViews","url"],func:async(r,o)=>{const a=l(o.user);await u(r,a.url,"profile");await r.wait(2);const e=(await s(r,"profile"))?.rows?.[0];if(!e?.name)throw new t("Quora profile header was not found; the page may be unavailable or changed.");return[e]}});
|