Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

修复晋江文学城 token渠道下载 #613

Merged
merged 21 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/rules/special/original/gongzicp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ export class Gongzicp extends BaseRuleClass {
})
.join("\n");

if (chapterInfo.postscript.length === 0) {
if (chapterInfo.postscript === null || chapterInfo.postscript.length === 0) {
contentHTML = _contentHTML;
} else {
contentHTML = document.createElement("div");
Expand Down
123 changes: 95 additions & 28 deletions src/rules/special/original/jjwxc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import * as csstree from "css-tree";
import * as CryptoJS from "crypto-js";

type JJWindow = UnsafeWindow & { getCookie: (key: string) => string };

Check warning on line 26 in src/rules/special/original/jjwxc.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

'JJWindow' is defined but never used

export class Jjwxc extends BaseRuleClass {
public constructor() {
Expand Down Expand Up @@ -578,8 +578,8 @@
char3 = str.charCodeAt(i++);
out += String.fromCharCode(
((c & 0x0f) << 12) |
((char2 & 0x3f) << 6) |
((char3 & 0x3f) << 0)
((char2 & 0x3f) << 6) |
((char3 & 0x3f) << 0)
);
break;
}
Expand All @@ -599,9 +599,9 @@
const data: Record<string, string> = {};
Array.from(children).forEach(
(item) =>
(data[item.getAttribute("name") as string] = item.getAttribute(
"value"
) as string)
(data[item.getAttribute("name") as string] = item.getAttribute(
"value"
) as string)
);

const novelid = parseInt(data["novelid"]);
Expand Down Expand Up @@ -655,7 +655,7 @@
if (new Date()["getTime"]() / 1000 - obj["time"] > 86400) {
throw new Error(
"章节内容解码失败,内容生成时间与当前设备时间相差过大,请刷新页面或校准当前设备时间。内容生成时间为:" +
new Date(obj["time"] * 100).toLocaleString()
new Date(obj["time"] * 100).toLocaleString()
);
}
};
Expand Down Expand Up @@ -710,8 +710,8 @@
sc.type,
(
sc as
| csstree.ClassSelector
| csstree.PseudoElementSelector
| csstree.ClassSelector
| csstree.PseudoElementSelector
).name,
])
);
Expand Down Expand Up @@ -758,8 +758,8 @@
sc.type,
(
sc as
| csstree.ClassSelector
| csstree.PseudoElementSelector
| csstree.ClassSelector
| csstree.PseudoElementSelector
).name,
])
);
Expand Down Expand Up @@ -887,6 +887,61 @@
message: string; //"[本章节已锁定]"
}
let retryTime = 0;
function decodeVIPResopnce(responseHeader: string, responseText: string) {
let v43, v38, dest;
let accesskey = "accesskey", keyString = "keystring";
const arr = responseHeader.trim().split(/[\r\n]+/);
const headerMap = { "accesskey": "0", "keystring": "0" };
arr.forEach((line) => {
const parts = line.split(": ");
const header = parts.shift();
const value = parts.join(": ");
if (header == "accesskey")
accesskey = value;
else if (header == "keystring")
keyString = value;
});
const content = String(responseText);
const accesskeyLen = accesskey.length;
let v9 = 0;
const v6 = String(accesskey[accesskeyLen - 1]).charCodeAt(0);
for (let i = 0; i < accesskeyLen; i++) {
v9 += accesskey[i].charCodeAt(0);
}
const v15 = v9 % keyString.length;
const v17 = v9 / 65;
const v18 = keyString.length;
if (v17 + v15 > v18) {
v43 = keyString.substring(v15, (v18 - v15) + v15)
} else {
v43 = keyString.substring(v15, v17 + v15)
}
const v32 = content.length;
if ((v6 & 1) != 0) {
v38 = content.substring(v32 - 12, v32)
dest = content.substring(0, v32 - 12)
} else {
v38 = content.substring(0, 12);
dest = content.substring(12, content.length);
}
const key = CryptoJS.MD5(v43 + v38).toString().substring(0, 8);
const iv = CryptoJS.MD5(v38).toString().substring(0, 8);
const keyHex = CryptoJS.enc.Utf8.parse(key);
const ivHex = CryptoJS.enc.Utf8.parse(iv);
let result = '{"message":"try again!"}';
try {
const decrypted = CryptoJS.DES.decrypt(dest, keyHex, {
iv: ivHex,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
result = decrypted.toString(CryptoJS.enc.Utf8);
} catch (e) {
// log.debug(`请求content:${content}`);
result = '{"message":"try again!"}';
}
return result;
}
function decodeVIPText(text: string) {
const keyHex = CryptoJS.enc.Utf8.parse("KW8Dvm2N");
const ivHex = CryptoJS.enc.Utf8.parse("1ae2c94b");
Expand All @@ -897,6 +952,7 @@
});
return decrypted.toString(CryptoJS.enc.Utf8);
}

function getCookieObj(pairKey: string) {
const cookieStr = document.cookie;
const pairList = cookieStr.split(";");
Expand All @@ -918,7 +974,7 @@
);
chapterGetInfoUrl = chapterGetInfoUrl.replace(
"http://my.jjwxc.net/onebook_vip.php?",
"https://android.jjwxc.net/androidapi/androidChapterBatchDownload?"
"https://app.jjwxc.net/androidapi/chapterContent?"
);
//let sid = getCookieObj("token");
if (isVIP) {
Expand All @@ -931,19 +987,20 @@
if (typeof (unsafeWindow as UnsafeWindow).tokenOptions === "object") {
const sid = (unsafeWindow as UnsafeWindow).tokenOptions?.Jjwxc;
//sid = self.atob(decodeURIComponent(sid)).replace(/\|\|.*/, '').replace(/\|/, '_').replace(/\|.*/, '');
chapterGetInfoUrl = chapterGetInfoUrl.replace(
"chapterId",
"chapterIds"
);
// chapterGetInfoUrl = chapterGetInfoUrl.replace(
// "chapterId",
// "chapterIds"
// );
chapterGetInfoUrl +=
"&versionCode=287&token=" + sid + "&noteislock=1";
"&versionCode=349&token=" + sid;
} else {
throw new Error(
`当前需要手动捕获android版app token,详见github主页说明`
);
}
//}
}

async function getChapterInfo(url: string): Promise<ChapterInfo> {
log.debug(
`请求地址: ${url}, Referrer: ${chapterUrl}, 重试次数: ${retryTime}`
Expand All @@ -952,22 +1009,31 @@
_GM_xmlhttpRequest({
url: url,
headers: {
accept: "application/json",
referer: "http://android.jjwxc.net?v=287",
not_tip: "updateTime",
"user-agent":
"Mozilla/ 5.0(Linux; Android 12; Pixel 3 XL Build / SP1A.210812.016.C1; wv) AppleWebKit / 537.36(KHTML, like Gecko) Version / 4.0 Chrome / 108.0.5359.128 Mobile Safari / 537.36 / JINJIANG - Android / 287(Pixel3XL; Scale / 3.5)",
"accept-encoding": "gzip",
// accept: "application/json",
referer: "http://android.jjwxc.net?v=349",
// not_tip: "updateTime",
"user-agent": "Dalvik/2.1.0",
// "accept-encoding": "gzip",
},
method: "GET",
onload: function (response) {
if (response.status === 200) {
retryTime = 0;
if (isVIP) {
const resultI: vipChapterInfo = JSON.parse(
response.responseText
);
resolve(resultI.downloadContent[0]);
let decodeResponseText = String(response.responseText);
let resultI = JSON.parse('{"message":"try again!"}');
try {
resultI = JSON.parse(decodeResponseText);
} catch (e) {
decodeResponseText = decodeVIPResopnce(response.responseHeaders, String(response.responseText));
}
try {
resultI = JSON.parse(decodeResponseText);
} catch (e) {
log.debug(`json:${decodeResponseText}`);
resultI = JSON.parse('{"message":"try again!"}');
}
resolve(resultI);
} else {
const resultI: ChapterInfo = JSON.parse(
response.responseText
Expand All @@ -989,8 +1055,8 @@
retryTime++;
if (retryTime > retryLimit) {
retryTime = 0;
log.error(`请求 (不可见url) 失败`);
throw new Error(`请求 (不可见url) 失败`);
log.error(`请求${chapterGetInfoUrl.toString()}$失败`);
throw new Error(`请求${chapterGetInfoUrl.toString()}$失败`);
}
result = await getChapterInfo(chapterGetInfoUrl.toString());
}
Expand All @@ -999,6 +1065,7 @@
let content = result.content;
if (isVIP) content = decodeVIPText(content);
let postscript = result.sayBody;
if (isVIP) postscript
if (result.sayBody == null) postscript = " ";
const contentRaw = document.createElement("pre");
contentRaw.innerHTML = content;
Expand Down
Loading