gedoor / legado

Legado 3.0 Book Reader with powerful controls & full functions❤️阅读3.0, 阅读是一款可以自定义来源阅读网络内容的工具,为广大网络文学爱好者提供一种方便、快捷舒适的试读体验。
GNU General Public License v3.0
29.42k stars 3.88k forks source link

[Feature Request] 让阅读能够变得神似正版的段落评论功能 #1963

Closed Seidko closed 2 years ago

Seidko commented 2 years ago

开发进度

功能描述

  1. 像起点,QQ阅读,纵横等网站一样的段评
  2. 还希望有段评缓存功能,像QQ阅读一样能够保存评论离线阅读

MKB%QA22YLP}BP(U8SDFFU8

image

然后,正版网站的评论api是开放的,无论是不是收费章节,所以不要质疑可行性

期望实现方式

起点的api结构是:

先通过reviewSummary获取段评的数量 然后通过reviewList获取具体的段评 如果要获取评论的回复,则要使用quoteReviewList

据了解,QQ阅读,纵横的api结构也大差不差,因此可以作为通用的框架来判断

还有一个细节,起点的reviewList有一个quoteContent,这个quoteContent是该段评所评论的段落的内容,可以通过字符串比较来判断这个段落是否属于这个段落 ~甚至有可能通过这个手段来从正版获得内容(幻想~

附加信息

我已经把起点有关的api扒下来了,下面是api获取到的内容demo,可以作为框架参考:

希望大佬能教教我手机程序的请求拦截,只会用devtools的菜鸟哭了,扒不了QQ阅读的api

reviewSummary: 获取段评的数量信息,就像是刚刚打开起点阅读界面的时候,每个段落评论数量的信息

URL:https://read.qidian.com/ajax/chapterReview/reviewSummary?_csrfToken={_csrfToken}&bookId={bookId}&chapterId={chapterId}

{
    "code": 0,
    "msg": "success",
    "data": {
        "list": [
            {
                "segmentId": -1, 
                "reviewNum": 98,
                "containSelf": false,
                "isHotSegment": false
            },
            {
                "segmentId": 1,
                "reviewNum": 44,
                "containSelf": false,
                "isHotSegment": true
            },
            {
                "segmentId": 2,
                "reviewNum": 45,
                "containSelf": false,
                "isHotSegment": true
            }
        ],
        "total": 3
    }
}

-1:章节名的段落id

reviewList:具体的评论列表

URL:https://read.qidian.com/ajax/chapterReview/reviewList?_csrfToken={_csrfToken}&bookId={bookId}&chapterId=${chapterId}&segmentId={segmentId}&type=2&page={page}&pageSize={maxReviewCount}

起点有一次性请求的段评数量的限制,可以把 maxReviewCount 设置的比较大来解除这个限制 也可以分次请求,page从1开始计数,如果一个段落的评论已经获取完了,那么isEnd将会变成1

{
    "code": 0,
    "msg": "success",
    "data": {
        "list": [
            {
                "reviewId": "737964112289726464",
                "cbid": "21960119108488104",
                "ccid": "61191279283257335",
                "guid": "247430484",
                "userId": "313229665",
                "nickName": "朕的几何染着血",
                "avatar": "https://qidian.qpic.cn/qd_face/349573/8432018/100",
                "segmentId": 106,
                "content": "查水表的",
                "status": 1,
                "createTime": "05-01 10:02:11",
                "updateTime": "2022-06-06 17:38:48",
                "quoteReviewId": "0",
                "quoteContent": "“哪位!”",
                "quoteGuid": "0",
                "quoteUserId": "0",
                "quoteNickName": "",
                "type": 2,
                "likeCount": 6,
                "dislikeCount": 0,
                "userLike": false,
                "userDislike": false,
                "isSelf": false,
                "essenceStatus": false,
                "riseStatus": false,
                "level": 5,
                "imagePre": "",
                "imageDetail": "",
                "rootReviewId": "737964112289726464",
                "rootReviewReplyCount": 0
            }
        ],
        "total": 1,
        "isEnd": 1
    }
}

quoteReviewList:评论的回复

URL: https://read.qidian.com/ajax/chapterReview/quoteReviewList?_csrfToken={_csrfToken}&reviewId={reviewId}&page={page}&pageSize={maxReviewCount}

reviewList差不多

{
    "code": 0,
    "msg": "success",
    "data": {
        "quoteReviewInfo": {
            "reviewId": "744860259612557312",
            "cbid": "21960119108488104",
            "ccid": "61191279283257335",
            "guid": "800104803528",
            "userId": "104803528",
            "nickName": "等待消失的爱",
            "avatar": "https://qidian.qpic.cn/qd_face/349573/5411122/100",
            "segmentId": 100,
            "content": "?????????",
            "status": 1,
            "createTime": "05-20 10:45:01",
            "updateTime": "2022-06-10 16:29:20",
            "quoteReviewId": "0",
            "quoteContent": "  “要你何用!!”",
            "quoteGuid": "0",
            "quoteUserId": "0",
            "quoteNickName": "",
            "type": 2,
            "likeCount": 22,
            "dislikeCount": 0,
            "userLike": false,
            "userDislike": false,
            "isSelf": false,
            "essenceStatus": false,
            "riseStatus": false,
            "level": 94,
            "imagePre": "",
            "imageDetail": "",
            "rootReviewId": "744860259612557312",
            "rootReviewReplyCount": 1
        },
        "list": [
            {
                "reviewId": "745021049921470464",
                "cbid": "21960119108488104",
                "ccid": "61191279283257335",
                "guid": "800005294937",
                "userId": "5294937",
                "nickName": "蜉蚴特",
                "avatar": "https://qidian.qpic.cn/qd_face/349573/25/100",
                "segmentId": 100,
                "content": "回复 等待消失的爱:",
                "status": 1,
                "createTime": "05-20 21:23:56",
                "updateTime": "2022-06-10 20:04:47",
                "quoteReviewId": "744860259612557312",
                "quoteContent": "  “要你何用!!”",
                "quoteGuid": "800104803528",
                "quoteUserId": "104803528",
                "quoteNickName": "等待消失的爱",
                "type": 10,
                "likeCount": 6,
                "dislikeCount": 0,
                "userLike": false,
                "userDislike": false,
                "isSelf": false,
                "essenceStatus": false,
                "riseStatus": false,
                "level": 95,
                "imagePre": "",
                "imageDetail": "",
                "rootReviewId": "744860259612557312",
                "rootReviewReplyCount": 0
            }
        ],
        "total": 1,
        "isEnd": 1
    }
}

效果演示

QQ阅读的界面:

2f39ffdbe79ec2d98c7f4d9424027d82

正如上图,大佬们应该都用过QQ阅读或者起点吧(滑稽)如果加上了这功能,是不是就几乎和正版差不多了?(笑

这个功能我已经构思了很久,本来想自己开发的,结果发现了阅读这个宝玉,就希望能够借助这块宝玉,让自己的设想能够得以实现,我很愿意和大佬们探讨这个功能的实现,希望阅读能够变得更好!

qianfanguojin commented 2 years ago
  1. 手机端请求拦截使用可以使用 fidder
  2. 你可以先做个原型小应用,样式随意,就是简单的样板,看看效果,然后再考虑嵌入进去
Seidko commented 2 years ago

@qianfanguojin 可以,我写一个flutter应用试试? 还是说要用kotlin写?

1552980358 commented 2 years ago

这个需要书源的相关适配,工作量预测会很大

qianfanguojin commented 2 years ago

@qianfanguojin 可以,我写一个flutter应用试试? 还是说要用kotlin写?

都可以啊,你甚至可以用 Java ,只要能写出来大致的展示就行了,验证以下你的想法即可。

不过有一点,你是想正文内的划线评论还是每章后的评论?正文内的工作量估计就大的多了

Seidko commented 2 years ago

@qianfanguojin 其实不要局限于一本书一个书源的思维。 可以一本书正文用笔趣阁等书源,而评论用起点等网站,可以在右上角加一个“段评书源选择”,让正文书源和评论书源分开。我的构思就是这个样子。

个人感觉其实工作量并没有那么大,每个段落后加个控件?应该是吧?

还有阅读的字符串都是硬编码吗?还是有做本地化的api?

qianfanguojin commented 2 years ago

@qianfanguojin 其实不要局限于一本书一个书源的思维。 可以一本书正文用笔趣阁等书源,而评论用起点等网站,我的构思就是这个样子。

个人感觉其实工作量并没有那么大,每个段落后加个控件?应该是吧?

你的意思还是在段落内添加评论吗?那你需要考虑如何区别某段内容,因为不同的书源参差不齐,每段的内容,换行也可能不一样。。

Seidko commented 2 years ago

@qianfanguojin

还有一个细节,起点的reviewList有一个quoteContent,这个quoteContent是该段评所评论的段落的内容,可以通过字符串比较来判断这个段落是否属于这个段落 ~甚至有可能通过这个手段来从正版获得内容(幻想~

原文我提到了这一点,起点的api里有该段评所评论的段落的内容。所以,可以根据字符串比较来校验该段落评价是不是属于这一行的。

正因为我发现了这一点,才让我的想法成为了可能。

qianfanguojin commented 2 years ago

@qianfanguojin

还有一个细节,起点的reviewList有一个quoteContent,这个quoteContent是该段评所评论的段落的内容,可以通过字符串比较来判断这个段落是否属于这个段落 ~甚至有可能通过这个手段来从正版获得内容(幻想~

原文我提到了这一点,起点的api里有该段评所评论的段落的内容。所以,可以根据字符串比较来校验该段落评价是不是属于这一行的。

正因为我发现了这一点,才让我的想法成为了可能。

所以我说按照字符串比较可能会根据书源的差异而不同,不过试了请求吗,修改 quoteContent 中的内容然后请求可以生效吗,如果可以的话,貌似也可以实现,就是可能不同书源看到的评论结果不一样

Seidko commented 2 years ago

@qianfanguojin 😂 不,quoteContent是起点api返回的内容啊,仔细看看我写的api样例:

"quoteContent": "“哪位!”"

这个“哪位!”就是该段落的内容

所以一比较,就知道这个书源的段落有没有错位了

qianfanguojin commented 2 years ago

@qianfanguojin 😂 不,quoteContent是起点api返回的内容啊,仔细看看我写的api样例:

"quoteContent": "“哪位!”"

这个“哪位!”就是该段落的内容

所以一比较,就知道这个书源的段落有没有错位了

嗯,那是否可以这样理解,在已经登录账号的前提下,假如是起点。

显示阶段: 打开阅读界面,根据 bookid 和 chapterid 获取该页的段落评论数量,然后获取到每段对应的 quoteContent ,并和当前正文进行匹配,匹配正确的则进入显示该段落标签(即查看评论列表的按钮),如不匹配则跳过。

评论阶段: 对当前正文某段点击(或者其他方式),弹出添加评论的界面,确认则调用 对应的 API 进行评论...

这里我还有两个问题,如何知道最开始的 bookId 和 chapterId ?书源自带?

Seidko commented 2 years ago

@qianfanguojin 这是我的想法中很重要的一环:

首先,一本书开始阅读以后,在这里可以弄一个设置让用户选择是否开启段评 自动搜索所有支持段评的书源中是否有这本书,然后根据获取到的 bookIdchapterId 获取reviewSummary,用户点击该段落后获取 ReviewList 获得该段落的评论,最后让书源设置的解析规则解析从支持段评的书源获取到的内容并呈现出来

qianfanguojin commented 2 years ago

这一段 让书源设置的解析规则解析从支持段评的书源获取到的内容并呈现出来 没看懂,根据评论获取正文?

Seidko commented 2 years ago

@qianfanguojin 就是把解析api的任务交给书源的意思

qianfanguojin commented 2 years ago

也行,那阅读的工作就是设计一套 API 来给书源使用咯

Seidko commented 2 years ago

是的,大概就是这个意思

Seidko commented 2 years ago

@qianfanguojin

显示阶段: 打开阅读界面,根据 bookid 和 chapterid 获取该页的段落评论数量,然后获取到每段对应的 quoteContent ,并和当前正文进行匹配,匹配正确的则进入显示该段落标签(即查看评论列表的按钮),如不匹配则跳过。

对了,对于字符串模糊匹配算法,我已经搞到了几个:

1. 字符串向量化

这个算法我不是很理解,所以我放不了代码,大佬可以上网搜索一下

2. 莱温斯坦编辑距离算法

我已经有了一个js的代码,java的算法在这里

function editDistance (strA, strB) {
  // Levenshtein Edit Distance
  if (strA === strB) {
    return 1.0
  }
  if (!strA || !strB) {
    return 0.0
  }
  const arr = new Array(strA.length + 1)
  for (let i1 = 0; i1 <= strA.length; i1++) {
    arr[i1] = new Array(strB.length + 1)
  }
  for (let i1 = 0; i1 <= strA.length; i1++) {
    for (let i2 = 0; i2 <= strB.length; i2++) {
      if (i1 === 0) {
        arr[0][i2] = i2
      } else if (i2 === 0) {
        arr[i1][0] = i1
      } else if (strA.charAt(i1 - 1) === strB.charAt(i2 - 1)) {
        arr[i1][i2] = arr[i1 - 1][i2 - 1]
      } else {
        arr[i1][i2] = 1 + Math.min(arr[i1 - 1][i2 - 1], Math.min(arr[i1][i2 - 1], arr[i1 - 1][i2]))
      }
    }
  }
  return 1 - (arr[strA.length][strB.length] / Math.max(strA.length, strB.length))
}

3. 随机匹配算法

如果书源有广告之类的污染了文本,那么如果截取字符串(比如笔趣阁等书源)中的一段,看看截取的这一小段字符串在不在要比较的字符串(比如起点api返回的内容)里,就可以判断这个字符串与原字符串的相似度了。

这个算法是我自己折腾出来的,可能有些缺点,比如计算量大,耗费资源多,但是可以通过将maxMatchCount的值设小一点来解决

上源码:

function randomlyMatch (strA, strB, minWordSize = 3, maxWordSize = 8, maxMatchCount = 100) {
  if (strA === strB) {
    return 1.0
  }
  if (!strA || !strB) {
    return 0.0
  }
  minWordSize = minWordSize / 2
  maxWordSize = maxWordSize / 2
  let counter = 0
  let m = 0
  let o = 0
  let temp
  for (let i = 0; i < maxMatchCount; i++) {
    do {
      m = Math.random() * (strA.length - 1)
      o = Math.random() * (maxWordSize - 0.25 - minWordSize) + 0.25 + minWordSize
      temp = strA.slice(m - o, m + o)
    } while (!temp)

    if (strB.includes(temp)) {
      counter++
    }
  }
  return counter / maxMatchCount
}
gedoor commented 2 years ago

可以,我看看

Seidko commented 2 years ago

可以,我看看

@gedoor 大佬有计划了吗?

dalaoha commented 2 years ago

你是想要全网付费网站律师函, (段评,章评,书评)是付费网站付费用户付费的核心,动这个,难道要舌战群儒。 上架换源看是阅读的核心(白嫖模式), (段评,章评,书评)需要付费订阅书章节才能获取数据,能获取这个了,正文也可以获取(你出钱订阅啊,全网几亿本书。) 你要是叫用户自己付费订阅书(白嫖党要是会订阅书,还会白嫖看。)

dalaoha commented 2 years ago

正版网站以外的用户看书,只需要正文啦,其他都是不需要的(最主要是没钱) 要是有钱,直接包养作者按照自己的要求写,不香吗。

Seidko commented 2 years ago

首先,我没钱,如果我有钱肯定会去看正版(当然也不会出现在这里),然后,阅读的工作仅仅是设计书源框架,任何其他行为都属于个人行为,与阅读无关,最后,关于你提出的:

(段评,章评,书评)需要付费订阅书章节才能获取数据,能获取这个了,正文也可以获取(你出钱订阅啊,全网几亿本书。)

可以先看看前面吗?

github-actions[bot] commented 2 years ago

由于长期没有状态更新,该问题将于5天后自动关闭。如有需要可重新打开。

nEdAy commented 2 years ago

防止关闭

xiaoxing1992 commented 2 years ago

防止关闭

github-actions[bot] commented 2 years ago

由于长期没有状态更新,该问题将于5天后自动关闭。如有需要可重新打开。

Seidko commented 2 years ago

本功能已经在开发中

lmst2 commented 1 year ago

image

已经看到书源中段评的功能了,不过可否给一个demo,比如起点的。这样我去趴其他网站的段评的时候就知道怎么填了。

lmst2 commented 1 year ago

@Seidko 我确实已经找到了各个功能的api,但是不清楚你是怎么实现的,所以里面的一些参数不知道怎么填进去(比如reviewId这种),你可以帮忙完善一下吗 image

    "ruleReview": {
        "reviewUrl": "https://read.qidian.com/ajax/chapterReview/reviewList?_csrfToken={{cookie.getKey(\"https://qidian.com\",\"_csrfToken\")}}&bookId={{baseUrl.match(/bookId=(\\d+)/)[1]}}&chapterId={{chapter.url.match(/chapterId=(\\d+)/)[1]}}&segmentId={segmentId}&type=2&page={page}&pageSize={maxReviewCount}",
        "postReviewUrl": "POST https://vipreader.qidian.com/ajax/chapterReview/createSegment {_csrfToken}{bookId}{chapterId}{segmentId}{content}",
        "voteUpUrl": "POST https://vipreader.qidian.com/ajax/chapterReview/userInteract {_csrfToken}{reviewId}{status:1}",
        "voteDownUrl": "POST https://vipreader.qidian.com/ajax/chapterReview/userInteract {_csrfToken}{reviewId}{status:2}",
        "postQuoteUrl": "POST https://vipreader.qidian.com/ajax/chapterReview/createSegmentReply {_csrfToken}{reviewId}{content}",
        "reviewQuoteUrl": "https://vipreader.qidian.com/ajax/chapterReview/quoteReviewList?_csrfToken={_csrfToken}&reviewId={reviewId}&page={page}",
        "deleteUrl": "POST https://vipreader.qidian.com/ajax/chapterReview/delReview {_csrfToken}{reviewId}",
        "avatarRule": "avatar:",
        "contentRule": "content:",
        "postTimeRule": "createTime:"
    }