1943 lines
93 KiB
JavaScript
1943 lines
93 KiB
JavaScript
const scriptName = "哲也同学";
|
||
const blockedUsersKey = "zhihu_blocked_users";
|
||
const currentUserInfoKey = "zhihu_current_userinfo";
|
||
const keywordBlockKey = "zhihu_keyword_block";
|
||
const blackAnswersIdKey = "zhihu_black_answers";
|
||
const userCreditScoreKey = "zhihu_credit_score";
|
||
const zheyeServerKey = "zheye_server_url";
|
||
const defaultAnswerBlockedUsers = ["会员推荐", "盐选推荐"];
|
||
const keywordMaxCount = 1e3;
|
||
const $ = MagicJS(scriptName, "INFO");
|
||
|
||
function getUserInfo() {
|
||
let defaultUserInfo = {id: "default", is_vip: false};
|
||
try {
|
||
let userInfo = $.data.read(currentUserInfoKey);
|
||
if (typeof userInfo === "string") userInfo = JSON.parse(userInfo);
|
||
if (!!userInfo && userInfo.hasOwnProperty("id")) {
|
||
return userInfo
|
||
} else {
|
||
return defaultUserInfo
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`获取用户信息出现异常:${err}`);
|
||
return defaultUserInfo
|
||
}
|
||
}
|
||
|
||
function modifyAppConfig() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
obj["config"]["homepage_feed_tab"]["tab_infos"] = obj["config"]["homepage_feed_tab"]["tab_infos"].filter(e => {
|
||
if (e["tab_type"] === "activity_tab") {
|
||
e["end_time"] = (new Date - 12e4).toString().slice(0, 10);
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
});
|
||
obj["config"]["zvideo_max_number"] = 1;
|
||
obj["config"]["is_show_followguide_alert"] = false;
|
||
delete obj["config"]["hp_channel_tab"];
|
||
if (obj["config"]["zombie_conf"]) {
|
||
obj["config"]["zombie_conf"]["zombieEnable"] = false
|
||
}
|
||
if (obj["config"]["gray_mode"]) {
|
||
obj["config"]["gray_mode"]["enable"] = false;
|
||
obj["config"]["gray_mode"]["start_time"] = "4092566400";
|
||
obj["config"]["gray_mode"]["end_time"] = "4092566400"
|
||
}
|
||
if (obj["config"].hasOwnProperty("zhcnh_thread_sync")) {
|
||
$.logger.debug(JSON.stringify(obj["config"]["zhcnh_thread_sync"]));
|
||
obj["config"]["zhcnh_thread_sync"]["LocalDNSSetHostWhiteList"] = [];
|
||
obj["config"]["zhcnh_thread_sync"]["isOpenLocalDNS"] = "0";
|
||
obj["config"]["zhcnh_thread_sync"]["ZHBackUpIP_Switch_Open"] = "0";
|
||
obj["config"]["zhcnh_thread_sync"]["dns_ip_detector_operation_lock"] = "1";
|
||
obj["config"]["zhcnh_thread_sync"]["ZHHTTPSessionManager_setupZHHTTPHeaderField"] = "1"
|
||
}
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`优化软件配置出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function modifyMCloudConfig() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
if (obj.data && obj.data["configs"]) {
|
||
obj.data["configs"].forEach(element => {
|
||
if (element["configKey"] === "feed_gray_theme") {
|
||
element["configValue"].start_time = "1669824000";
|
||
element["configValue"].end_time = "1669824001";
|
||
element.status = false
|
||
}
|
||
})
|
||
}
|
||
const body = JSON.stringify(obj);
|
||
$.logger.debug(body);
|
||
response = {body: body}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`优化软件配置出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function unlockBlockedKeywords() {
|
||
let response = null;
|
||
try {
|
||
const userInfo = getUserInfo();
|
||
if ($.request.method === "GET") {
|
||
let keywords = $.data.read(keywordBlockKey, null, userInfo.id);
|
||
if (!keywords) {
|
||
keywords = []
|
||
}
|
||
let headers = {
|
||
"Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0",
|
||
Connection: "keep-alive",
|
||
"Content-Type": "application/json;charset=utf-8",
|
||
Pragma: "no-cache",
|
||
"Referrer-Policy": "no-referrer-when-downgrade",
|
||
Server: "CLOUD ELB 1.0.0",
|
||
Vary: "Accept-Encoding",
|
||
"X-Cache-Lookup": "Cache Miss",
|
||
"x-cdn-provider": "tencent"
|
||
};
|
||
let body = JSON.stringify({
|
||
success: true,
|
||
is_vip: true,
|
||
kw_min_length: 2,
|
||
kw_max_length: 100,
|
||
kw_max_count: keywordMaxCount,
|
||
data: keywords
|
||
});
|
||
if ($.env.isQuanX) {
|
||
response = {body: body, headers: headers, status: "HTTP/1.1 200 OK"}
|
||
} else {
|
||
response = {response: {body: body, headers: headers, status: 200}}
|
||
}
|
||
$.logger.debug(`获取本地脚本屏蔽关键词:\n${keywords.join("、")}`)
|
||
} else if ($.request.method === "POST") {
|
||
if (!!$.request.body) {
|
||
let headers = {
|
||
"Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0",
|
||
Connection: "keep-alive",
|
||
"Content-Type": "application/json;charset=utf-8",
|
||
Pragma: "no-cache",
|
||
"Referrer-Policy": "no-referrer-when-downgrade",
|
||
Server: "CLOUD ELB 1.0.0",
|
||
Vary: "Accept-Encoding",
|
||
"X-Cache-Lookup": "Cache Miss",
|
||
"x-cdn-provider": "tencent"
|
||
};
|
||
let keyword = decodeURIComponent($.request.body).match(/keyword=(.*)/)[1];
|
||
let keywords = $.data.read(keywordBlockKey, null, userInfo.id);
|
||
if (!keywords) {
|
||
keywords = []
|
||
}
|
||
let keywordExists = false;
|
||
for (let i = 0; i < keywords.length; i++) {
|
||
if (keyword === keywords[i]) {
|
||
keywordExists = true;
|
||
break
|
||
}
|
||
}
|
||
if (keywordExists === false) {
|
||
keywords.push(keyword);
|
||
$.data.write(keywordBlockKey, keywords, userInfo.id);
|
||
let body = JSON.stringify({success: true});
|
||
if ($.env.isQuanX) {
|
||
response = {body: body, headers: headers, status: "HTTP/1.1 200 OK"}
|
||
} else {
|
||
response = {response: {body: body, headers: headers, status: 200}}
|
||
}
|
||
$.logger.debug(`添加本地脚本屏蔽关键词“${keyword}”`)
|
||
} else {
|
||
let body = JSON.stringify({error: {message: "关键词已存在", code: 100002}});
|
||
if ($.env.isQuanX) {
|
||
response = {body: body, headers: headers, status: "HTTP/1.1 400 Bad Request"}
|
||
} else {
|
||
response = {response: {body: body, headers: headers, status: 400}}
|
||
}
|
||
}
|
||
}
|
||
} else if ($.request.method === "DELETE") {
|
||
let keyword = decodeURIComponent($.request.url).match(/keyword=(.*)/)[1];
|
||
let keywords = $.data.read(keywordBlockKey, null, userInfo.id);
|
||
if (!keywords) {
|
||
keywords = []
|
||
}
|
||
keywords = keywords.filter(e => {
|
||
return e !== keyword
|
||
});
|
||
$.data.write(keywordBlockKey, keywords, userInfo.id);
|
||
let headers = {
|
||
"Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0",
|
||
Connection: "keep-alive",
|
||
"Content-Type": "application/json;charset=utf-8",
|
||
Pragma: "no-cache",
|
||
"Referrer-Policy": "no-referrer-when-downgrade",
|
||
Server: "CLOUD ELB 1.0.0",
|
||
Vary: "Accept-Encoding",
|
||
"X-Cache-Lookup": "Cache Miss",
|
||
"x-cdn-provider": "tencent"
|
||
};
|
||
let body = JSON.stringify({success: true});
|
||
if ($.env.isQuanX) {
|
||
response = {body: body, headers: headers, status: "HTTP/1.1 200 OK"}
|
||
} else {
|
||
response = {response: {body: body, headers: headers, status: 200}}
|
||
}
|
||
$.logger.debug(`删除本地脚本屏蔽关键词:“${keyword}”`)
|
||
}
|
||
} catch (err) {
|
||
$.logger.debug(`关键词屏蔽操作出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function processUserInfo() {
|
||
let response = null;
|
||
try {
|
||
let obj = JSON.parse($.response.body);
|
||
$.data.write(blackAnswersIdKey, []);
|
||
$.logger.debug(`用户登录用户信息,接口响应:${$.response.body}`);
|
||
if (obj && obj["id"] && obj.hasOwnProperty("vip_info") && obj["vip_info"].hasOwnProperty("is_vip")) {
|
||
const userInfo = {
|
||
id: obj["id"],
|
||
is_vip: obj["vip_info"]["is_vip"] ? obj["vip_info"]["is_vip"] !== undefined : false
|
||
};
|
||
$.logger.debug(`当前用户id:${obj["id"]},是否为VIP:${obj["vip_info"]["is_vip"]}`);
|
||
$.data.write(currentUserInfoKey, userInfo);
|
||
if ($.data.read("zhihu_settings_fake_vip") !== false && obj["vip_info"]["is_vip"] === false) {
|
||
obj["vip_info"]["is_vip"] = true;
|
||
obj["vip_info"]["vip_type"] = 2;
|
||
obj["vip_info"]["vip_icon"] = {
|
||
url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae",
|
||
night_mode_url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae"
|
||
};
|
||
obj["vip_info"]["vip_icon_v2"] = {
|
||
url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae",
|
||
night_mode_url: "https://picx.zhimg.com/v2-aa8a1823abfc46f14136f01d55224925.jpg?source=88ceefae"
|
||
};
|
||
obj["vip_info"]["entrance"] = {
|
||
icon: {
|
||
url: "https://pic3.zhimg.com/v2-5b7012c8c22fd520f5677305e1e551bf.png",
|
||
night_mode_url: "https://pic4.zhimg.com/v2-e51e3252d7a2cb016a70879defd5da0b.png"
|
||
},
|
||
title: "盐选会员 为你严选好内容",
|
||
expires_day: "2099-12-31",
|
||
sub_title: null,
|
||
button_text: "首月 9 元",
|
||
jump_url: "zhihu://vip/purchase",
|
||
button_jump_url: "zhihu://vip/purchase",
|
||
sub_title_new: null,
|
||
identity: "super_svip"
|
||
};
|
||
obj["vip_info"]["entrance_new"] = {
|
||
left_button: {
|
||
title: "精选会员内容",
|
||
description: "为您严选好内容",
|
||
jump_url: "zhihu://market/home"
|
||
},
|
||
right_button: {
|
||
title: "开通盐选会员",
|
||
description: "畅享 10w+ 场优质内容等特权",
|
||
jump_url: "zhihu://vip/purchase"
|
||
}
|
||
};
|
||
obj["vip_info"]["entrance_v2"] = {
|
||
title: "我的超级盐选会员",
|
||
sub_title: "畅享 5000W+ 优质内容",
|
||
jump_url: "zhihu://market/home",
|
||
button_text: "查看会员",
|
||
sub_title_color: "#F8E2C4",
|
||
sub_title_list: ["畅享 5000W+ 优质内容"],
|
||
card_jump_url: "zhihu://market/home"
|
||
};
|
||
$.logger.debug("设置用户为本地盐选会员");
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} else {
|
||
$.logger.warning(`没有获取到本次登录用户信息,如未对功能造成影响,请忽略此日志。`)
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`获取当前用户信息出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function manageBlackUser() {
|
||
const userInfo = getUserInfo();
|
||
let defaultBlockedUsers = {};
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id);
|
||
customBlockedUsers = typeof customBlockedUsers === "object" && !!customBlockedUsers ? customBlockedUsers : {};
|
||
defaultAnswerBlockedUsers.forEach(element => {
|
||
customBlockedUsers[element] = "00000000000000000000000000000000";
|
||
defaultBlockedUsers[element] = "00000000000000000000000000000000"
|
||
});
|
||
$.logger.debug(`当前用户id:${userInfo.id},脚本黑名单:${JSON.stringify(customBlockedUsers)}`);
|
||
if ($.request.method === "GET") {
|
||
try {
|
||
if ($.request.url.indexOf("offset") < 0) {
|
||
customBlockedUsers = defaultBlockedUsers;
|
||
$.logger.debug("脚本黑名单已清空,请滑动至黑名单末尾保证重新获取完成。");
|
||
$.notification.post("开始同步黑名单数据,请滑动至黑名单末尾,直至弹出“同步成功”的通知。")
|
||
}
|
||
let obj = JSON.parse($.response.body);
|
||
if (!!obj["data"]) {
|
||
$.logger.debug(`本次滑动获取的黑名单信息:${JSON.stringify(obj["data"])}`);
|
||
obj["data"].forEach(element => {
|
||
if (element["name"] !== "[已重置]") {
|
||
customBlockedUsers[element["name"]] = element["id"]
|
||
}
|
||
});
|
||
$.data.write(blockedUsersKey, customBlockedUsers, userInfo.id);
|
||
if (obj["paging"]["is_end"] === true) {
|
||
$.notification.post(`同步黑名单数据成功!当前黑名单共${Object.keys(customBlockedUsers).length - defaultAnswerBlockedUsers.length}人。\n脚本内置黑名单${defaultAnswerBlockedUsers.length}人。`);
|
||
$.logger.debug(`脚本黑名单内容:${JSON.stringify(customBlockedUsers)}。`)
|
||
}
|
||
} else {
|
||
$.logger.warning(`获取黑名单失败,接口响应不合法:${$.response.body}`)
|
||
}
|
||
} catch (err) {
|
||
$.data.del(blockedUsersKey);
|
||
$.logger.error(`获取黑名单失败,异常信息:${err}`);
|
||
$.notification.post("获取黑名单失败,执行异常,已清空黑名单。")
|
||
}
|
||
} else if ($.request.method === "POST") {
|
||
try {
|
||
let obj = JSON.parse($.response.body);
|
||
if (obj.hasOwnProperty("name") && obj.hasOwnProperty("id")) {
|
||
$.logger.debug(`当前需要加入黑名单的用户Id:${obj["id"]},用户名:${obj["name"]}`);
|
||
if (obj["id"]) {
|
||
customBlockedUsers[obj["name"]] = obj["id"];
|
||
$.data.write(blockedUsersKey, customBlockedUsers, userInfo.id);
|
||
$.logger.debug(`${obj["name"]}写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify(customBlockedUsers)}`);
|
||
$.notification.post(`已将用户“${obj["name"]}”写入脚本黑名单。`)
|
||
} else {
|
||
$.logger.error(`${obj["name"]}写入脚本黑名单失败,没有获取到用户Id。`);
|
||
$.notification.post(`将用户“${obj["name"]}”写入脚本黑名单失败!`)
|
||
}
|
||
} else {
|
||
$.logger.warning(`写入黑名单失败,接口响应不合法:${$.response.body}`);
|
||
$.notification.post("写入脚本黑名单失败,接口返回不合法。")
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`写入黑名单失败,异常信息:${err}`);
|
||
$.notification.post("写入脚本黑名单失败,执行异常,请查阅日志。")
|
||
}
|
||
} else if ($.request.method === "DELETE") {
|
||
try {
|
||
let obj = JSON.parse($.response.body);
|
||
if (obj.success) {
|
||
let user_id = $.request.url.match(/^https?:\/\/api\.zhihu\.com\/settings\/blocked_users\/([0-9a-zA-Z]*)/)[1];
|
||
if (user_id) {
|
||
$.logger.debug(`当前需要移出黑名单的用户Id:${user_id}`);
|
||
for (let username in customBlockedUsers) {
|
||
if (customBlockedUsers[username] === user_id) {
|
||
delete customBlockedUsers[username];
|
||
$.data.write(blockedUsersKey, customBlockedUsers, userInfo.id);
|
||
$.logger.debug(`${username}移出脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify(customBlockedUsers)}`);
|
||
$.notification.post(`已将用户“${username}”移出脚本黑名单!`);
|
||
break
|
||
}
|
||
}
|
||
} else {
|
||
$.logger.error("将用户移出脚本黑名单失败!\n建议从设置中刷新黑名单数据。");
|
||
$.notification.post(`将用户移出脚本黑名单失败,没有获取到用户Id。\n建议从设置中刷新黑名单数据。`)
|
||
}
|
||
} else {
|
||
$.logger.warning(`移出黑名单失败,接口响应不合法:${$.response.body}`);
|
||
$.notification.post("移出脚本黑名单失败,接口返回不合法。")
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`移出黑名单失败,异常信息:${err}`);
|
||
$.notification.post("移出脚本黑名单失败,执行异常,请查阅日志。")
|
||
}
|
||
}
|
||
}
|
||
|
||
function autoInsertBlackList() {
|
||
let response = null;
|
||
try {
|
||
let obj = JSON.parse($.response.body);
|
||
delete obj["mcn_user_info"];
|
||
response = {body: JSON.stringify(obj)};
|
||
if (obj.name && obj.id && obj["is_blocking"] === true) {
|
||
const userInfo = getUserInfo();
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id);
|
||
customBlockedUsers = typeof customBlockedUsers === "object" && !!customBlockedUsers ? customBlockedUsers : {};
|
||
if (!customBlockedUsers[obj.name]) {
|
||
$.logger.debug(`当前需要加入黑名单的用户Id:${obj["id"]},用户名:${obj["name"]}`);
|
||
customBlockedUsers[obj["name"]] = obj["id"];
|
||
$.data.write(blockedUsersKey, customBlockedUsers, userInfo.id);
|
||
$.logger.debug(`${obj["name"]}写入脚本黑名单成功,当前脚本黑名单数据:${JSON.stringify(customBlockedUsers)}`);
|
||
$.notification.post(`已自动将用户“${obj["name"]}”写入脚本黑名单。`)
|
||
}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`去除MCN信息出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeMoments() {
|
||
let response = null;
|
||
try {
|
||
let obj = JSON.parse($.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g, '$1:"$2"'));
|
||
const user_info = getUserInfo();
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id);
|
||
customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {};
|
||
let data;
|
||
const settings_remove_stream = $.data.read("zhihu_settings_moments_stream", false);
|
||
const settings_remove_recommend = $.data.read("zhihu_settings_moments_recommend", false);
|
||
const settings_remove_activity = $.data.read("zhihu_settings_moments_activity", false);
|
||
const settings_blocked_users = $.data.read("zhihu_settings_blocked_users", false);
|
||
data = obj.data.filter(item => {
|
||
const isBlackUserPin = settings_blocked_users && item.target && item.target["origin_pin"] && item.target["origin_pin"].author && typeof customBlockedUsers[item.target["origin_pin"].author.name] != "undefined";
|
||
const isStream = settings_remove_stream && item["target_type"] === "zvideo";
|
||
const isRecommend = settings_remove_recommend && item.type === "recommend_user_card_list";
|
||
const isActivity = settings_remove_activity && item.type === "message_activity_card";
|
||
return !(isBlackUserPin || isStream || isRecommend || isActivity)
|
||
});
|
||
obj["data"] = data;
|
||
response = {body: JSON.stringify(obj)}
|
||
} catch (err) {
|
||
$.logger.error(`关注列表去广告出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function _setRecommendTag(item, text, color = "GBK02A") {
|
||
if (!item["common_card"]["footline"]) {
|
||
item["common_card"]["footline"] = {elements: []}
|
||
}
|
||
item["common_card"]["footline"]["elements"].unshift({
|
||
tag: {
|
||
text: text,
|
||
color: color,
|
||
type: "MASK_ROUNDED_RECTANGLE"
|
||
}
|
||
})
|
||
}
|
||
|
||
async function _checkPaidContentByCloud(filteredUrls, newData) {
|
||
const zheyeServer = $.data.read(zheyeServerKey);
|
||
if (!zheyeServer) {
|
||
$.notification.post("未设置服务端地址,无法进行付费/推广内容探测。\n请配置服务端地址,或使用本地探测。")
|
||
} else {
|
||
$.logger.debug(`向云端请求以下链接\n${filteredUrls.join("\n")}`);
|
||
const url = `${zheyeServer}/api/v1/answer/links`;
|
||
$.logger.debug(`服务端地址\n${url}`);
|
||
await $.http.post({url: url, headers: {"Content-Type": "application/json"}, body: filteredUrls}).then(resp => {
|
||
$.logger.debug(`云端探测结果<${typeof resp.body}>\n${JSON.stringify(resp.body)}`);
|
||
for (let i = 0; i < resp.body.length; i++) {
|
||
try {
|
||
let tagText = resp.body[i];
|
||
if (tagText !== "") {
|
||
_setRecommendTag(newData[i], tagText, "GBK02A")
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(err)
|
||
}
|
||
}
|
||
}).catch(err => {
|
||
$.logger.error(`云端请求出现异常\n${JSON.stringify(err)}`)
|
||
})
|
||
}
|
||
}
|
||
|
||
async function _checkPaidContentByLocal(filteredUrls, newData) {
|
||
$.logger.debug(`将在本地请求以下链接\n${filteredUrls.join("\n")}`);
|
||
let requestPromise = [];
|
||
|
||
function _requestAnswerContent(i) {
|
||
return new Promise(resolve => {
|
||
const url = filteredUrls[i];
|
||
if (!url || url === "" || !url.startsWith("https://www.zhihu.com/appview/v2/answer")) {
|
||
resolve("")
|
||
} else {
|
||
$.http.get({url: filteredUrls[i], timeout: 1e3}).then(resp => {
|
||
const body = resp.body;
|
||
if ((body.indexOf("查看完整内容") >= 0 || body.indexOf("查看全部章节") >= 0) && body.indexOf("paid") >= 0) {
|
||
resolve("付费内容")
|
||
} else if (body.indexOf("ad-link-card") >= 0 || body.indexOf("xg.zhihu.com") >= 0 || body.indexOf("营销平台") >= 0) {
|
||
resolve("营销推广")
|
||
} else if (body.indexOf("mcn-link-card") >= 0) {
|
||
resolve("购物推广")
|
||
} else {
|
||
resolve("")
|
||
}
|
||
}).catch(err => {
|
||
$.logger.error(`本地请求出现异常\n${JSON.stringify(err)}`);
|
||
resolve("")
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
for (let i = 0; i < filteredUrls.length; i++) {
|
||
requestPromise.push(_requestAnswerContent(i))
|
||
}
|
||
await Promise.all(requestPromise).then(result => {
|
||
$.logger.debug(`本地探测结果<${result.length}>\n${JSON.stringify(result)}`);
|
||
for (let i = 0; i < result.length; i++) {
|
||
try {
|
||
let tagText = result[i];
|
||
if (tagText !== "") {
|
||
_setRecommendTag(newData[i], tagText, "GBK02A")
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(err)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
async function removeRecommend() {
|
||
let response = null;
|
||
try {
|
||
const settings_remove_pin = $.data.read("zhihu_settings_recommend_pin", false);
|
||
const settings_recommend_stream = $.data.read("zhihu_settings_recommend_stream", false);
|
||
const settings_remove_article = $.data.read("zhihu_settings_remove_article", false);
|
||
const settings_blocked_users = $.data.read("zhihu_settings_blocked_users", false);
|
||
const settings_blocked_keywords = $.data.read("zhihu_settings_blocked_keywords", true);
|
||
const settings_check_paid_content = $.data.read("zhihu_settings_check_paid_content", false);
|
||
const settings_request_content = $.data.read("zhihu_settings_request_content", "local");
|
||
const user_info = getUserInfo();
|
||
let keywords = $.data.read(keywordBlockKey, "", user_info.id);
|
||
keywords = settings_blocked_keywords && !!keywords ? keywords : [];
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id);
|
||
customBlockedUsers = settings_blocked_users && !!customBlockedUsers ? customBlockedUsers : {};
|
||
const dataFilter = element => {
|
||
const elementStr = JSON.stringify(element);
|
||
const isAd = element["card_type"] === "slot_event_card" || element["card_type"] === "slot_video_event_card" || element.hasOwnProperty("ad") || element["brief"] && element["brief"].indexOf("slot_card") >= 0 || element["extra"] && element["extra"]["type"] === "Training";
|
||
const isStream = isAd !== true && elementStr.search(/"(type|style)+"\s?:\s?"(drama|zvideo|Video|BIG_IMAGE)+"/i) >= 0;
|
||
const removeStream = isStream && settings_recommend_stream;
|
||
const isPin = isStream !== true && elementStr.search(/"(type|style)+"\s?:\s?"pin"/i) >= 0;
|
||
const removePin = isPin && settings_remove_pin;
|
||
const isArticle = elementStr.search(/"(type|style)+"\s?:\s?"article"/i) >= 0;
|
||
const removeArticle = isArticle && settings_remove_article;
|
||
let matchKeyword = false;
|
||
if (isStream !== true && settings_blocked_keywords) {
|
||
for (let i = 0; i < keywords.length; i++) {
|
||
if (elementStr.search(keywords[i]) >= 0) {
|
||
if ($.isDebug) {
|
||
let elementTitle = element["common_card"]["feed_content"]["title"]["panel_text"];
|
||
let elementContent = element["common_card"]["feed_content"]["content"]["panel_text"];
|
||
let actionUrl = "";
|
||
try {
|
||
actionUrl = element["common_card"]["feed_content"]["title"]["action"]["intent_url"]
|
||
} catch {
|
||
}
|
||
$.logger.debug(`匹配关键字:\n${keywords[i]}\n标题:\n${elementTitle}\n内容:\n${elementContent}`);
|
||
$.notification.debug(scriptName, `关键字:${keywords[i]}`, `${elementTitle}\n${elementContent}`, actionUrl)
|
||
}
|
||
matchKeyword = true;
|
||
break
|
||
}
|
||
}
|
||
}
|
||
let isBlockedUser;
|
||
try {
|
||
isBlockedUser = matchKeyword !== true && settings_blocked_users && customBlockedUsers && element["common_card"]["feed_content"]["source_line"]["elements"][1]["text"]["panel_text"] in customBlockedUsers
|
||
} catch {
|
||
isBlockedUser = false
|
||
}
|
||
return !(isAd || removePin || removeArticle || removeStream || matchKeyword || isBlockedUser)
|
||
};
|
||
let obj = JSON.parse($.response.body.replace(/(\w+"+\s?):\s?(\d{15,})/g, '$1:"$2"'));
|
||
let newData = obj["data"].filter(dataFilter);
|
||
if (settings_check_paid_content === true) {
|
||
let filteredUrls = [];
|
||
for (let i = 0; i < newData.length; i++) {
|
||
if (newData[i]) {
|
||
try {
|
||
let url = newData[i]["common_card"]["feed_content"]["title"]["action"]["intent_url"];
|
||
url = url.replace(/^https:\/\/zhihu\.com\/question\/\d+\/answer\//, "https://www.zhihu.com/appview/v2/answer/");
|
||
filteredUrls.push(url)
|
||
} catch {
|
||
filteredUrls.push("")
|
||
}
|
||
}
|
||
}
|
||
if (filteredUrls.length > 0 && settings_request_content === "cloud") {
|
||
await _checkPaidContentByCloud(filteredUrls, newData)
|
||
} else if (filteredUrls.length > 0 && settings_request_content === "local") {
|
||
await _checkPaidContentByLocal(filteredUrls, newData)
|
||
}
|
||
}
|
||
obj["data"] = newData;
|
||
response = {body: JSON.stringify(obj)}
|
||
} catch (err) {
|
||
$.logger.error(`推荐列表去广告出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeQuestions() {
|
||
let response = null;
|
||
try {
|
||
const userInfo = getUserInfo();
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, "", userInfo.id);
|
||
customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {};
|
||
let obj = JSON.parse($.response.body);
|
||
const settingsBlockedUsers = $.data.read("zhihu_settings_blocked_users", false);
|
||
$.logger.debug(`当前黑名单列表: ${JSON.stringify(customBlockedUsers)}`);
|
||
let blackUserAnswersId = $.data.read(blackAnswersIdKey, []);
|
||
delete obj["ad_info"];
|
||
if (obj["data"]) {
|
||
let newData = [];
|
||
for (let element of obj.data) {
|
||
let blackUserName = "";
|
||
const answerId = element.target.id.toString();
|
||
try {
|
||
if ("target" in element) {
|
||
blackUserName = element["target"]["author"]["name"]
|
||
}
|
||
} catch (ex) {
|
||
$.logger.error(`获取回答列表用户名出现异常:${ex}`)
|
||
}
|
||
const isBlackUser = typeof customBlockedUsers[blackUserName] != "undefined";
|
||
const removeBlackUserAnswer = settingsBlockedUsers && isBlackUser;
|
||
if ("target" in element) {
|
||
element["target"]["visible_only_to_author"] = false;
|
||
element["target"]["is_visible"] = true;
|
||
element["target"]["is_copyable"] = true
|
||
}
|
||
if (!removeBlackUserAnswer) {
|
||
newData.push(element)
|
||
} else if (removeBlackUserAnswer === true && blackUserAnswersId.includes(answerId) === false) {
|
||
blackUserAnswersId.push(answerId);
|
||
$.notification.debug(`记录黑名单用户${blackUserName}的回答Id:${answerId}`)
|
||
}
|
||
}
|
||
obj.data = newData
|
||
}
|
||
$.data.write(blackAnswersIdKey, blackUserAnswersId);
|
||
const body = JSON.stringify(obj);
|
||
$.logger.debug(`修改后的回答列表数据:${body}`);
|
||
response = {body: body}
|
||
} catch (err) {
|
||
$.logger.error(`回答列表去广告出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function modifyAnswer() {
|
||
let response = null;
|
||
try {
|
||
let html = $.response.body;
|
||
let insertText = "";
|
||
if ((html.indexOf("查看完整内容") >= 0 || html.indexOf("查看全部章节") >= 0) && html.indexOf("paid") >= 0) {
|
||
insertText = '<a style="height: 42px;padding: 0 12px;border-radius: 6px;background-color: rgb(247 104 104 / 8%);display: block;text-decoration: none;" href="#"><div style="color: #f36;display: flex;-webkit-box-align: center;align-items: center;height: 100%;"><svg style="width: 1.2em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024"><path d="M821.333333 138.666667c64.8 0 117.333333 52.533333 117.333334 117.333333v149.333333a32 32 0 0 1-32 32 74.666667 74.666667 0 0 0 0 149.333334 32 32 0 0 1 32 32v149.333333c0 64.8-52.533333 117.333333-117.333334 117.333333H202.666667c-64.8 0-117.333333-52.533333-117.333334-117.333333V618.666667a32 32 0 0 1 32-32 74.666667 74.666667 0 0 0 0-149.333334 32 32 0 0 1-32-32V256c0-64.8 52.533333-117.333333 117.333334-117.333333h618.666666zM428.576 329.994667a32 32 0 0 0-43.733333-2.581334l-1.514667 1.344a32 32 0 0 0-2.581333 43.733334L452.565333 458.666667H405.333333l-1.877333 0.053333A32 32 0 0 0 373.333333 490.666667l0.053334 1.877333A32 32 0 0 0 405.333333 522.666667h80v42.666666H405.333333l-1.877333 0.053334A32 32 0 0 0 373.333333 597.333333l0.053334 1.877334A32 32 0 0 0 405.333333 629.333333h80v42.666667l0.053334 1.877333A32 32 0 0 0 517.333333 704l1.877334-0.053333A32 32 0 0 0 549.333333 672v-42.666667H618.666667l1.877333-0.053333A32 32 0 0 0 650.666667 597.333333l-0.053334-1.877333A32 32 0 0 0 618.666667 565.333333h-69.333334v-42.666666H618.666667l1.877333-0.053334A32 32 0 0 0 650.666667 490.666667l-0.053334-1.877334A32 32 0 0 0 618.666667 458.666667h-47.253334l71.84-86.186667 1.248-1.589333a32 32 0 0 0-50.421333-39.381334L512 430.016l-82.08-98.506667z"></path></svg><div style="flex: 1 1;white-space: nowrap;text-overflow: ellipsis;padding-left:4px"><span style="font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Microsoft YaHei,Source Han Sans SC,Noto Sans CJK SC,WenQuanYi Micro Hei,sans-serif;-webkit-tap-highlight-color: rgba(26,26,26,0);font-size: 14px;line-height: 20px;color: #f36;white-space: nowrap;font-weight: 600;">本文为付费内容</span></div><div></div></div></a>'
|
||
} else if (html.indexOf("ad-link-card") >= 0 || html.indexOf("xg.zhihu.com") >= 0 || html.indexOf("营销平台") >= 0) {
|
||
insertText = '<a style="height: 42px;padding: 0 12px;border-radius: 6px;background-color: rgb(8 188 212 / 8%);display: block;text-decoration: none;" href="#"><div style="color: #00bcd4;display: flex;-webkit-box-align: center;align-items: center;height: 100%;"><svg style="width: 1.2em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024"><path d="M128 765.952q0 26.624 18.432 45.056t45.056 18.432l191.488 0 0 128-254.976 0q-26.624 0-49.664-10.24t-40.448-27.648-27.648-40.448-10.24-49.664l0-637.952q0-25.6 10.24-49.152t27.648-40.448 40.448-27.136 49.664-10.24l701.44 0q26.624 0 49.664 10.24t40.448 27.136 27.648 40.448 10.24 49.152l0 251.904-128 1.024 0-61.44q0-26.624-18.432-45.056t-45.056-18.432l-574.464 0q-26.624 0-45.056 18.432t-18.432 45.056l0 382.976zM990.208 705.536q21.504 18.432 22.016 34.304t-20.992 33.28q-21.504 18.432-51.2 41.472t-60.928 48.128-61.952 49.152-55.296 43.52q-26.624 20.48-43.52 15.36t-16.896-31.744q1.024-16.384 0-40.448t-1.024-41.472q0-19.456-10.752-24.064t-31.232-4.608q-21.504 0-39.936-0.512t-35.84-0.512-36.352-0.512-41.472-0.512q-9.216 0-19.968-2.048t-19.456-7.168-14.336-15.36-5.632-27.648l0-80.896q0-31.744 15.36-42.496t48.128-10.752q30.72 1.024 61.44 1.024t71.68 1.024q29.696 0 46.08-5.12t16.384-25.6q-1.024-14.336 0.512-35.328t1.536-37.376q0-26.624 14.336-33.28t36.864 10.752q22.528 18.432 52.736 43.008t61.952 50.688 62.976 51.2 54.784 44.544z"></path></svg><div style="flex: 1 1;white-space: nowrap;text-overflow: ellipsis;padding-left:4px"><span style="font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Microsoft YaHei,Source Han Sans SC,Noto Sans CJK SC,WenQuanYi Micro Hei,sans-serif;-webkit-tap-highlight-color: rgba(26,26,26,0);font-size: 14px;line-height: 20px;color: #00bcd4;white-space: nowrap;font-weight: 600;">本文含有营销推广</span></div><div></div></div></a>'
|
||
} else if (html.indexOf("mcn-link-card") >= 0) {
|
||
insertText = '<a style="height: 42px;padding: 0 12px;border-radius: 6px;background-color: rgb(8 188 212 / 8%);display: block;text-decoration: none;" href="#"><div style="color: #00bcd4;display: flex;-webkit-box-align: center;align-items: center;height: 100%;"><svg style="width: 1.2em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024"><path d="M346.112 806.912q19.456 0 36.864 7.168t30.208 19.968 20.48 30.208 7.68 36.864-7.68 36.864-20.48 30.208-30.208 20.48-36.864 7.68q-20.48 0-37.888-7.68t-30.208-20.48-20.48-30.208-7.68-36.864 7.68-36.864 20.48-30.208 30.208-19.968 37.888-7.168zM772.096 808.96q19.456 0 37.376 7.168t30.72 19.968 20.48 30.208 7.68 36.864-7.68 36.864-20.48 30.208-30.72 20.48-37.376 7.68-36.864-7.68-30.208-20.48-20.48-30.208-7.68-36.864 7.68-36.864 20.48-30.208 30.208-19.968 36.864-7.168zM944.128 227.328q28.672 0 44.544 7.68t22.528 18.944 6.144 24.064-3.584 22.016-13.312 37.888-22.016 62.976-23.552 68.096-18.944 53.248q-13.312 40.96-33.28 56.832t-49.664 15.872l-35.84 0-65.536 0-86.016 0-96.256 0-253.952 0 14.336 92.16 517.12 0q49.152 0 49.152 41.984 0 20.48-9.728 35.328t-38.4 14.848l-49.152 0-94.208 0-118.784 0-119.808 0-99.328 0-55.296 0q-20.48 0-34.304-9.216t-23.04-24.064-14.848-32.256-8.704-32.768q-1.024-6.144-5.632-29.696t-11.264-58.88-14.848-78.848-16.384-87.552q-19.456-103.424-44.032-230.4l-76.8 0q-15.36 0-25.6-7.68t-16.896-18.432-9.216-23.04-2.56-22.528q0-20.48 13.824-33.792t37.376-13.312l21.504 0 21.504 0 25.6 0 34.816 0q20.48 0 32.768 6.144t19.456 15.36 10.24 19.456 5.12 17.408q2.048 8.192 4.096 23.04t4.096 30.208q3.072 18.432 6.144 38.912l700.416 0zM867.328 194.56l-374.784 0 135.168-135.168q23.552-23.552 51.712-24.064t51.712 23.04z"></path></svg><div style="flex: 1 1;white-space: nowrap;text-overflow: ellipsis;padding-left:4px"><span style="font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Microsoft YaHei,Source Han Sans SC,Noto Sans CJK SC,WenQuanYi Micro Hei,sans-serif;-webkit-tap-highlight-color: rgba(26,26,26,0);font-size: 14px;line-height: 20px;color: #00bcd4;white-space: nowrap;font-weight: 600;">本文含有购物推广</span></div><div></div></div></a>'
|
||
} else if (Math.floor(Math.random() * 200) === 7) {
|
||
insertText = '<a style="height: 42px;padding: 0 12px;border-radius: 6px;background-color: rgb(74 162 56 / 8%);display: block;text-decoration: none;" href="#"><div style="color: #619201;display: flex;-webkit-box-align: center;align-items: center;height: 100%;"><svg class="icon" style="width: 1.2em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024"><path d="M512 85.333333c71.477333 0 159.68 57.546667 229.258667 147.018667C817.845333 330.826667 864 455.978667 864 586.666667c0 211.808-148.501333 352-352 352S160 798.474667 160 586.666667c0-130.688 46.144-255.84 122.741333-354.314667C352.32 142.88 440.522667 85.333333 512 85.333333z m0 64c-48.213333 0-120.096 46.912-178.741333 122.314667C265.109333 359.253333 224 470.762667 224 586.666667c0 175.616 119.050667 288 288 288s288-112.384 288-288c0-115.904-41.109333-227.402667-109.258667-315.018667C632.096 196.234667 560.213333 149.333333 512 149.333333z m-74.666667 522.666667a53.333333 53.333333 0 1 1 0 106.666667 53.333333 53.333333 0 0 1 0-106.666667z m-96-128a42.666667 42.666667 0 1 1 0 85.333333 42.666667 42.666667 0 0 1 0-85.333333z"></path></svg><div style="flex: 1 1;white-space: nowrap;text-overflow: ellipsis;padding-left:4px"><span style="font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Microsoft YaHei,Source Han Sans SC,Noto Sans CJK SC,WenQuanYi Micro Hei,sans-serif;-webkit-tap-highlight-color: rgba(26,26,26,0);font-size: 14px;line-height: 20px;color: #619201;white-space: nowrap;font-weight: 600;">本文为免费内容</span></div><div></div></div></a>'
|
||
}
|
||
if (insertText !== "") {
|
||
const matchStr = html.match(/(richText[^<]*>)(.)/)[1];
|
||
const start = html.lastIndexOf(matchStr) + matchStr.length;
|
||
response = {body: html.slice(0, start) + insertText + html.slice(start)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`付费内容提醒出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeComment() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
obj["ad_info"] = {};
|
||
if ($.data.read("zhihu_settings_blocked_users", false) === true) {
|
||
let user_info = getUserInfo();
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, "", user_info.id);
|
||
customBlockedUsers = !!customBlockedUsers ? customBlockedUsers : {};
|
||
let newComments = [];
|
||
let blockCommentIdObj = {};
|
||
if (typeof obj.root != "undefined") {
|
||
const rootUserName = obj.root.author.name;
|
||
const isBlackRootUser = typeof customBlockedUsers[rootUserName] != "undefined";
|
||
if (isBlackRootUser === true) {
|
||
obj.root.is_delete = true;
|
||
obj.root.can_reply = false;
|
||
obj.root.can_like = false;
|
||
obj.root.author.name = "黑名单用户";
|
||
obj.root.author.avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"
|
||
}
|
||
}
|
||
if (typeof obj.data != "undefined") {
|
||
obj.data.forEach(comment => {
|
||
const commentUserName = comment.author.name;
|
||
let replyUserName = "";
|
||
if (comment["reply_to_author"] && comment["reply_to_author"].name) {
|
||
replyUserName = comment["reply_to_author"].name
|
||
}
|
||
const isSubComment = replyUserName !== "";
|
||
const isBlackCommentUser = typeof customBlockedUsers[commentUserName] != "undefined";
|
||
const isBlackReplyUser = typeof customBlockedUsers[replyUserName] != "undefined";
|
||
if (isBlackCommentUser === true || isBlackReplyUser === true) {
|
||
if (isBlackCommentUser && !isSubComment && $.request.url.indexOf("root_comment") > 0) {
|
||
$.notification.debug(`屏蔽黑名单用户“${commentUserName}”的主评论。`)
|
||
} else if (!isBlackCommentUser && isSubComment && !isBlackReplyUser && $.request.url.indexOf("child_comment") > 0) {
|
||
$.notification.debug(`屏蔽黑名单用户“${commentUserName}”的子评论。`)
|
||
} else if (isBlackCommentUser && !isBlackReplyUser && $.request.url.indexOf("child_comment") > 0) {
|
||
$.notification.debug(`屏蔽黑名单用户“${commentUserName}”回复“${replyUserName}”的子评论。`)
|
||
} else if (isBlackCommentUser && isBlackReplyUser && $.request.url.indexOf("child_comment") > 0) {
|
||
$.notification.debug(`屏蔽黑名单用户“${commentUserName}”回复黑名单用户“${replyUserName}”的子评论。`)
|
||
}
|
||
blockCommentIdObj[comment.id] = commentUserName;
|
||
if (isBlackCommentUser) {
|
||
comment.is_delete = true;
|
||
comment.can_reply = false;
|
||
comment.can_like = false;
|
||
comment.author.exposed_medal = {};
|
||
comment.author.name = "[黑名单用户]";
|
||
comment.author.avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"
|
||
}
|
||
if (isBlackReplyUser) {
|
||
comment["reply_to_author"].name = "[黑名单用户]";
|
||
comment["reply_to_author"].exposed_medal = {};
|
||
comment["reply_to_author"].avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"
|
||
}
|
||
}
|
||
if (comment.child_comments) {
|
||
let newChildComments = [];
|
||
comment.child_comments.forEach(childComment => {
|
||
const childCommentUserName = childComment.author.name;
|
||
const childCommentReplyUserName = typeof childComment["reply_to_author"] != "undefined" ? childComment["reply_to_author"].name : "";
|
||
const isChildBlackCommentUser = typeof customBlockedUsers[childCommentUserName] != "undefined";
|
||
const isChildBlackReplyUser = typeof customBlockedUsers[childCommentReplyUserName] != "undefined";
|
||
if (isChildBlackCommentUser || isChildBlackReplyUser) {
|
||
if (isChildBlackCommentUser === true) {
|
||
$.notification.debug(`屏蔽黑名单用户“${childCommentUserName}”的子评论。`);
|
||
blockCommentIdObj[childComment.id] = childCommentUserName;
|
||
childComment.is_delete = true;
|
||
childComment.can_reply = false;
|
||
childComment.can_like = false;
|
||
childComment.author.name = "[黑名单用户]";
|
||
childComment.author.exposed_medal = {};
|
||
childComment.author.avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg"
|
||
}
|
||
if (isChildBlackReplyUser === true) {
|
||
$.logger.debug(`修改前的子评论数据:\n${JSON.stringify(childComment)}`);
|
||
childComment["reply_to_author"].name = "[黑名单用户]";
|
||
childComment["reply_to_author"].exposed_medal = {};
|
||
childComment["reply_to_author"].avatar_url = "https://picx.zhimg.com/v2-abed1a8c04700ba7d72b45195223e0ff_xll.jpg";
|
||
$.notification.debug(`隐藏“${childCommentUserName}”回复黑名单用户“${childCommentReplyUserName}”的名称与头像。`);
|
||
$.logger.debug(`修改后的子评论数据:\n${JSON.stringify(childComment)}`)
|
||
}
|
||
}
|
||
newChildComments.push(childComment)
|
||
});
|
||
comment.child_comments = newChildComments
|
||
}
|
||
newComments.push(comment)
|
||
})
|
||
}
|
||
obj.data = newComments
|
||
}
|
||
const body = JSON.stringify(obj);
|
||
$.logger.debug(`过滤后的评论数据:\n${body}`);
|
||
response = {body: body}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`去除评论广告出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeArticleAd() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
obj["ad_info"] = {};
|
||
const body = JSON.stringify(obj);
|
||
$.logger.debug(`过滤后的文章数据:\n${body}`);
|
||
response = {body: body}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`去除文章广告出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeMarketingMsg() {
|
||
let response = null;
|
||
try {
|
||
let obj = JSON.parse($.response.body);
|
||
let newItems = [];
|
||
for (let item of obj["data"]) {
|
||
if (item["detail_title"] === "官方帐号消息") {
|
||
let unread_count = item["unread_count"];
|
||
if (unread_count > 0) {
|
||
item["content"]["text"] = "未读消息" + unread_count + "条"
|
||
} else {
|
||
item["content"]["text"] = "全部消息已读"
|
||
}
|
||
item["is_read"] = true;
|
||
item["unread_count"] = 0;
|
||
newItems.push(item)
|
||
} else if (item["detail_title"] !== "活动助手") {
|
||
newItems.push(item)
|
||
}
|
||
}
|
||
obj["data"] = newItems;
|
||
response = {body: JSON.stringify(obj)}
|
||
} catch (err) {
|
||
$.logger.error(`屏蔽官方营销消息出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeHotListAds() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
if ("data" in obj) {
|
||
obj["data"] = obj["data"].filter(e => {
|
||
return e["type"] === "hot_list_feed" || e["type"] === "hot_list_feed_video"
|
||
})
|
||
}
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`去除热榜广告出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeKeywordAds() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
$.logger.debug(`预置关键字返回:${$.response.body}`);
|
||
let obj = JSON.parse($.response.body);
|
||
if (obj.hasOwnProperty("preset_words") && obj["preset_words"]["words"]) {
|
||
obj["preset_words"]["words"] = obj["preset_words"]["words"].filter(element => {
|
||
return element["type"] !== "ad"
|
||
});
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`去除预置关键字广告出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function removeNextBlackUserAnswer() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
const blackUserAnswersId = $.data.read(blackAnswersIdKey, []);
|
||
if (blackUserAnswersId.length > 0) {
|
||
let newData = [];
|
||
obj.data.forEach(element => {
|
||
const tag = blackUserAnswersId.includes(element.id.toString());
|
||
if (tag === false) {
|
||
element.ad_info = {data: ""};
|
||
newData.push(element)
|
||
} else {
|
||
$.notification.debug(`屏蔽翻页过程中出现的黑名单用户回答Id:${element.id}`)
|
||
}
|
||
});
|
||
for (let i = 0; i < newData.length; i++) {
|
||
if (newData[i]["extra"] && newData[i]["extra"]["question_index"]) {
|
||
newData[i]["extra"]["question_index"] = i + 1
|
||
}
|
||
if (newData[i]["strategy_info"]) {
|
||
newData[i]["strategy_info"]["global_index"] = i + 1;
|
||
newData[i]["strategy_info"]["strategy_index"] = i + 1
|
||
}
|
||
}
|
||
obj.data = newData
|
||
}
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`屏蔽下翻黑名单用户的回答出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function modifyAnswersNextData() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
let user_info = getUserInfo();
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, {}, user_info.id);
|
||
$.logger.debug(`脚本黑名单用户:\n${JSON.stringify(customBlockedUsers)}`);
|
||
let newData = [];
|
||
obj.data.data.forEach(element => {
|
||
const isAd = element["resource_type"] === "ad" || element["extra"]["recommend_start"] === true || element["next_guide"]["change_type"] === 1 || element["next_guide"]["title"] === "推荐的相关内容";
|
||
if (isAd === true) {
|
||
return
|
||
}
|
||
element["ad_info"] = {data: ""};
|
||
const isBlackUser = typeof customBlockedUsers[element.data.author.name] != "undefined";
|
||
$.logger.debug(`用户${element.data.author.name}是否在黑名单中:${isBlackUser}`);
|
||
if ($.data.read("zhihu_settings_blocked_users", false) === false || isBlackUser === false) {
|
||
newData.push(element)
|
||
}
|
||
});
|
||
obj.data.data = newData;
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`屏蔽回答信息流黑名单用户及广告:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function modifyAnswersNextBff() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
let user_info = getUserInfo();
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, {}, user_info.id);
|
||
$.logger.debug(`脚本黑名单用户:\n${JSON.stringify(customBlockedUsers)}`);
|
||
let newData = [];
|
||
obj.data.forEach(element => {
|
||
const isAd = element["origin_data"]["resource_type"] !== "normal";
|
||
if (isAd === true) {
|
||
return
|
||
}
|
||
element["origin_data"]["ad_info"] = {data: ""};
|
||
const authorName = element["origin_data"]["data"]["author"]["name"];
|
||
const isBlackUser = typeof customBlockedUsers[authorName] != "undefined";
|
||
$.logger.debug(`用户${authorName}是否在黑名单中:${isBlackUser}`);
|
||
if ($.data.read("zhihu_settings_blocked_users", false) === false || isBlackUser === false) {
|
||
newData.push(element)
|
||
}
|
||
});
|
||
obj.data = newData;
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`屏蔽回答信息流黑名单用户及广告:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function modifyAnswersNextRender() {
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
let obj = JSON.parse($.response.body);
|
||
let user_info = getUserInfo();
|
||
let customBlockedUsers = $.data.read(blockedUsersKey, {}, user_info.id);
|
||
$.logger.debug(`脚本黑名单用户:\n${JSON.stringify(customBlockedUsers)}`);
|
||
let newData = [];
|
||
obj.data.forEach(element => {
|
||
const isAd = element["ad_info"] && element["ad_info"]["data"] !== "" || element["biz_type_list"] && element["biz_type_list"].length !== 1 || element["biz_type_list"] && element["biz_type_list"][0] !== "answer" || element["type"] === "ad";
|
||
if (isAd === true) {
|
||
return
|
||
}
|
||
const authorName = element["author"]["fullname"];
|
||
const isBlackUser = typeof customBlockedUsers[authorName] != "undefined";
|
||
$.logger.debug(`用户${authorName}是否在黑名单中:${isBlackUser}`);
|
||
if ($.data.read("zhihu_settings_blocked_users", false) === false || isBlackUser === false) {
|
||
newData.push(element)
|
||
}
|
||
});
|
||
obj.data = newData;
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`屏蔽回答信息流黑名单用户及广告:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
function changeUserCredit() {
|
||
$.notification.debug("开始修改用户盐值");
|
||
let response = null;
|
||
try {
|
||
if (!!$.response.body) {
|
||
const score = parseInt($.data.read(userCreditScoreKey, 780));
|
||
$.logger.debug(`准备修改用户盐值为${score}`);
|
||
let obj = JSON.parse($.response.body);
|
||
if (obj["credit_basis"].total_score < score) {
|
||
obj["credit_basis"].total_score = score;
|
||
$.logger.debug(`已修改用户盐值为:${score}`)
|
||
}
|
||
response = {body: JSON.stringify(obj)}
|
||
}
|
||
} catch (err) {
|
||
$.logger.error(`修改用户盐值出现异常:${err}`)
|
||
}
|
||
return response
|
||
}
|
||
|
||
(async () => {
|
||
let response = null;
|
||
if ($.isResponse) {
|
||
switch (true) {
|
||
case/^https:\/\/api\.zhihu\.com\/people\/self$/.test($.request.url):
|
||
response = processUserInfo();
|
||
break;
|
||
case $.data.read("zhihu_settings_app_conf", false) === true && /^https?:\/\/appcloud2\.zhihu\.com\/v\d+\/config/.test($.request.url):
|
||
response = modifyAppConfig();
|
||
break;
|
||
case $.data.read("zhihu_settings_app_conf", false) === true && /^https?:\/\/m-cloud\.zhihu\.com\/api\/cloud\/config\/all\?/.test($.request.url):
|
||
response = modifyMCloudConfig();
|
||
break;
|
||
case/^https?:\/\/api\.zhihu\.com\/user-credit\/basis/.test($.request.url):
|
||
response = changeUserCredit();
|
||
break;
|
||
case/^https:\/\/api\.zhihu\.com\/topstory\/recommend/.test($.request.url):
|
||
response = await removeRecommend();
|
||
break;
|
||
case/^https?:\/\/api\.zhihu\.com\/(v4\/)?questions\/\d+/.test($.request.url):
|
||
response = removeQuestions();
|
||
break;
|
||
case/^https?:\/\/api\.zhihu\.com\/next-data\?/.test($.request.url):
|
||
response = modifyAnswersNextData();
|
||
break;
|
||
case/^https?:\/\/api\.zhihu\.com\/next-bff\?/.test($.request.url):
|
||
response = modifyAnswersNextBff();
|
||
break;
|
||
case/^https?:\/\/api\.zhihu\.com\/next-render\?/.test($.request.url):
|
||
response = modifyAnswersNextRender();
|
||
break;
|
||
case $.data.read("zhihu_settings_sys_msg", true) !== false && /^https?:\/\/api\.zhihu\.com\/notifications\/v3\/message/.test($.request.url):
|
||
response = removeMarketingMsg();
|
||
break;
|
||
case/^https?:\/\/api\.zhihu\.com\/comment_v5\/(answers|pins|comments?|articles)\/\d+\/(root|child)_comment/.test($.request.url):
|
||
response = removeComment();
|
||
break;
|
||
case/^https?:\/\/www\.zhihu\.com\/api\/v\d\/articles\/\d+\/recommendation\?/.test($.request.url):
|
||
response = removeArticleAd();
|
||
break;
|
||
case/^https?:\/\/www\.zhihu\.com\/api\/v4\/comment_v5\/answers\/\d+\/abstract_comment\?/.test($.request.url):
|
||
response = removeComment();
|
||
break;
|
||
case $.data.read("zhihu_settings_answer_tip", true) === true && /^https?:\/\/www\.zhihu\.com\/appview\/v2\/answer\/.*(entry=(?!(preload-topstory|preload-search|preload-subscription)))?/.test($.request.url):
|
||
response = modifyAnswer();
|
||
break;
|
||
case $.data.read("zhihu_settings_blocked_users", false) !== false && /^https?:\/\/api\.zhihu\.com\/next\?/.test($.request.url):
|
||
response = removeNextBlackUserAnswer();
|
||
break;
|
||
case $.data.read("zhihu_settings_blocked_users", true) === true && /^https?:\/\/api\.zhihu\.com\/people\/((?!self).)*$/.test($.request.url):
|
||
response = autoInsertBlackList();
|
||
break;
|
||
case/^https?:\/\/api\.zhihu\.com\/moments_v3\?/.test($.request.url):
|
||
response = removeMoments();
|
||
break;
|
||
case $.data.read("zhihu_settings_hot_list", true) === true && /^https?:\/\/api\.zhihu\.com\/topstory\/hot-lists(\?|\/)/.test($.request.url):
|
||
response = removeHotListAds();
|
||
break;
|
||
case $.data.read("zhihu_settings_preset_words", true) === true && /^https?:\/\/api\.zhihu\.com\/search\/preset_words\?/.test($.request.url):
|
||
response = removeKeywordAds();
|
||
break;
|
||
case $.data.read("zhihu_settings_blocked_users", false) !== false && /^https?:\/\/api\.zhihu\.com\/settings\/blocked_users/.test($.request.url):
|
||
manageBlackUser();
|
||
break;
|
||
default:
|
||
$.logger.debug("没有匹配到任何请求,请确认配置正确。");
|
||
break
|
||
}
|
||
} else if ($.isRequest) {
|
||
if ($.data.read("zhihu_settings_blocked_keywords", false) !== false && /^https?:\/\/api\.zhihu\.com\/feed-root\/block/.test($.request.url) === true) {
|
||
response = unlockBlockedKeywords(response)
|
||
}
|
||
} else {
|
||
$.data.del(currentUserInfoKey);
|
||
$.data.del(blockedUsersKey);
|
||
$.data.del(keywordBlockKey);
|
||
$.notification.post("哲也同学数据清理完毕")
|
||
}
|
||
if (response) {
|
||
$.done(response)
|
||
} else {
|
||
$.done()
|
||
}
|
||
})();
|
||
|
||
function MagicJS(scriptName = "MagicJS", logLevel = "INFO") {
|
||
const MagicEnvironment = () => {
|
||
const isLoon = typeof $loon !== "undefined";
|
||
const isQuanX = typeof $task !== "undefined";
|
||
const isNode = typeof module !== "undefined";
|
||
const isSurge = typeof $httpClient !== "undefined" && !isLoon;
|
||
const isStorm = typeof $storm !== "undefined";
|
||
const isStash = typeof $environment !== "undefined" && typeof $environment["stash-build"] !== "undefined";
|
||
const isSurgeLike = isSurge || isLoon || isStorm || isStash;
|
||
const isScriptable = typeof importModule !== "undefined";
|
||
return {
|
||
isLoon: isLoon,
|
||
isQuanX: isQuanX,
|
||
isNode: isNode,
|
||
isSurge: isSurge,
|
||
isStorm: isStorm,
|
||
isStash: isStash,
|
||
isSurgeLike: isSurgeLike,
|
||
isScriptable: isScriptable,
|
||
get name() {
|
||
if (isLoon) {
|
||
return "Loon"
|
||
} else if (isQuanX) {
|
||
return "QuantumultX"
|
||
} else if (isNode) {
|
||
return "NodeJS"
|
||
} else if (isSurge) {
|
||
return "Surge"
|
||
} else if (isScriptable) {
|
||
return "Scriptable"
|
||
} else {
|
||
return "unknown"
|
||
}
|
||
},
|
||
get build() {
|
||
if (isSurge) {
|
||
return $environment["surge-build"]
|
||
} else if (isStash) {
|
||
return $environment["stash-build"]
|
||
} else if (isStorm) {
|
||
return $storm.buildVersion
|
||
}
|
||
},
|
||
get language() {
|
||
if (isSurge || isStash) {
|
||
return $environment["language"]
|
||
}
|
||
},
|
||
get version() {
|
||
if (isSurge) {
|
||
return $environment["surge-version"]
|
||
} else if (isStash) {
|
||
return $environment["stash-version"]
|
||
} else if (isStorm) {
|
||
return $storm.appVersion
|
||
} else if (isNode) {
|
||
return process.version
|
||
}
|
||
},
|
||
get system() {
|
||
if (isSurge) {
|
||
return $environment["system"]
|
||
} else if (isNode) {
|
||
return process.platform
|
||
}
|
||
},
|
||
get systemVersion() {
|
||
if (isStorm) {
|
||
return $storm.systemVersion
|
||
}
|
||
},
|
||
get deviceName() {
|
||
if (isStorm) {
|
||
return $storm.deviceName
|
||
}
|
||
}
|
||
}
|
||
};
|
||
const MagicLogger = (scriptName, logLevel = "INFO") => {
|
||
let _level = logLevel;
|
||
const logLevels = {SNIFFER: 6, DEBUG: 5, INFO: 4, NOTIFY: 3, WARNING: 2, ERROR: 1, CRITICAL: 0, NONE: -1};
|
||
const logEmoji = {
|
||
SNIFFER: "",
|
||
DEBUG: "",
|
||
INFO: "",
|
||
NOTIFY: "",
|
||
WARNING: "❗ ",
|
||
ERROR: "❌ ",
|
||
CRITICAL: "❌ ",
|
||
NONE: ""
|
||
};
|
||
const _log = (msg, level = "INFO") => {
|
||
if (!(logLevels[_level] < logLevels[level.toUpperCase()])) console.log(`[${level}] [${scriptName}]\n${logEmoji[level.toUpperCase()]}${msg}\n`)
|
||
};
|
||
const setLevel = logLevel => {
|
||
_level = logLevel
|
||
};
|
||
return {
|
||
getLevel: () => {
|
||
return _level
|
||
}, setLevel: setLevel, sniffer: msg => {
|
||
_log(msg, "SNIFFER")
|
||
}, debug: msg => {
|
||
_log(msg, "DEBUG")
|
||
}, info: msg => {
|
||
_log(msg, "INFO")
|
||
}, notify: msg => {
|
||
_log(msg, "NOTIFY")
|
||
}, warning: msg => {
|
||
_log(msg, "WARNING")
|
||
}, error: msg => {
|
||
_log(msg, "ERROR")
|
||
}, retry: msg => {
|
||
_log(msg, "RETRY")
|
||
}
|
||
}
|
||
};
|
||
return new class {
|
||
constructor(scriptName, logLevel) {
|
||
this._startTime = Date.now();
|
||
this.version = "3.0.0";
|
||
this.scriptName = scriptName;
|
||
this.env = MagicEnvironment();
|
||
this.logger = MagicLogger(scriptName, logLevel);
|
||
this.http = typeof MagicHttp === "function" ? MagicHttp(this.env, this.logger) : undefined;
|
||
this.data = typeof MagicData === "function" ? MagicData(this.env, this.logger) : undefined;
|
||
this.notification = typeof MagicNotification === "function" ? MagicNotification(this.scriptName, this.env, this.logger, this.http) : undefined;
|
||
this.utils = typeof MagicUtils === "function" ? MagicUtils(this.env, this.logger) : undefined;
|
||
this.qinglong = typeof MagicQingLong === "function" ? MagicQingLong(this.env, this.data, this.logger) : undefined;
|
||
if (typeof this.data !== "undefined") {
|
||
let magicLoglevel = this.data.read("magic_loglevel");
|
||
const barkUrl = this.data.read("magic_bark_url");
|
||
if (magicLoglevel) {
|
||
this.logger.setLevel(magicLoglevel.toUpperCase())
|
||
}
|
||
if (barkUrl) {
|
||
this.notification.setBark(barkUrl)
|
||
}
|
||
}
|
||
}
|
||
|
||
get isRequest() {
|
||
return typeof $request !== "undefined" && typeof $response === "undefined"
|
||
}
|
||
|
||
get isResponse() {
|
||
return typeof $response !== "undefined"
|
||
}
|
||
|
||
get isDebug() {
|
||
return this.logger.level === "DEBUG"
|
||
}
|
||
|
||
get request() {
|
||
return typeof $request !== "undefined" ? $request : undefined
|
||
}
|
||
|
||
get response() {
|
||
if (typeof $response !== "undefined") {
|
||
if ($response.hasOwnProperty("status")) $response["statusCode"] = $response["status"];
|
||
if ($response.hasOwnProperty("statusCode")) $response["status"] = $response["statusCode"];
|
||
return $response
|
||
} else {
|
||
return undefined
|
||
}
|
||
}
|
||
|
||
done = (value = {}) => {
|
||
this._endTime = Date.now();
|
||
let span = (this._endTime - this._startTime) / 1e3;
|
||
this.logger.info(`SCRIPT COMPLETED: ${span} S.`);
|
||
if (typeof $done !== "undefined") {
|
||
$done(value)
|
||
}
|
||
}
|
||
}(scriptName, logLevel)
|
||
}
|
||
|
||
function MagicNotification(scriptName, env, logger, http) {
|
||
let _barkUrl = null;
|
||
let _barkKey = null;
|
||
const setBark = url => {
|
||
try {
|
||
let _url = url.replace(/\/+$/g, "");
|
||
_barkUrl = `${/^https?:\/\/([^/]*)/.exec(_url)[0]}/push`;
|
||
_barkKey = /\/([^\/]+)\/?$/.exec(_url)[1]
|
||
} catch (ex) {
|
||
logger.error(`Bark url error: ${ex}.`)
|
||
}
|
||
};
|
||
|
||
function post(title = scriptName, subTitle = "", body = "", opts = "") {
|
||
const _adaptOpts = _opts => {
|
||
try {
|
||
let newOpts = {};
|
||
if (typeof _opts === "string") {
|
||
if (env.isLoon) newOpts = {openUrl: _opts}; else if (env.isQuanX) newOpts = {"open-url": _opts}; else if (env.isSurge) newOpts = {url: _opts}
|
||
} else if (typeof _opts === "object") {
|
||
if (env.isLoon) {
|
||
newOpts["openUrl"] = !!_opts["open-url"] ? _opts["open-url"] : "";
|
||
newOpts["mediaUrl"] = !!_opts["media-url"] ? _opts["media-url"] : ""
|
||
} else if (env.isQuanX) {
|
||
newOpts = !!_opts["open-url"] || !!_opts["media-url"] ? _opts : {}
|
||
} else if (env.isSurge) {
|
||
let openUrl = _opts["open-url"] || _opts["openUrl"];
|
||
newOpts = openUrl ? {url: openUrl} : {}
|
||
}
|
||
}
|
||
return newOpts
|
||
} catch (err) {
|
||
logger.error(`通知选项转换失败${err}`)
|
||
}
|
||
return _opts
|
||
};
|
||
opts = _adaptOpts(opts);
|
||
if (arguments.length === 1) {
|
||
title = scriptName;
|
||
subTitle = "", body = arguments[0]
|
||
}
|
||
logger.notify(`title:${title}\nsubTitle:${subTitle}\nbody:${body}\noptions:${typeof opts === "object" ? JSON.stringify(opts) : opts}`);
|
||
if (env.isSurge) {
|
||
$notification.post(title, subTitle, body, opts)
|
||
} else if (env.isLoon) {
|
||
if (!!opts) $notification.post(title, subTitle, body, opts); else $notification.post(title, subTitle, body)
|
||
} else if (env.isQuanX) {
|
||
$notify(title, subTitle, body, opts)
|
||
}
|
||
if (_barkUrl && _barkKey) {
|
||
bark(title, subTitle, body)
|
||
}
|
||
}
|
||
|
||
function debug(title = scriptName, subTitle = "", body = "", opts = "") {
|
||
if (logger.getLevel() === "DEBUG") {
|
||
if (arguments.length === 1) {
|
||
title = scriptName;
|
||
subTitle = "";
|
||
body = arguments[0]
|
||
}
|
||
this.post(title, subTitle, body, opts)
|
||
}
|
||
}
|
||
|
||
function bark(title = scriptName, subTitle = "", body = "", opts = "") {
|
||
if (typeof http === "undefined" || typeof http.post === "undefined") {
|
||
throw "Bark notification needs to import MagicHttp module."
|
||
}
|
||
let options = {
|
||
url: _barkUrl,
|
||
headers: {"content-type": "application/json; charset=utf-8"},
|
||
body: {title: title, body: subTitle ? `${subTitle}\n${body}` : body, device_key: _barkKey}
|
||
};
|
||
http.post(options).catch(ex => {
|
||
logger.error(`Bark notify error: ${ex}`)
|
||
})
|
||
}
|
||
|
||
return {post: post, debug: debug, bark: bark, setBark: setBark}
|
||
}
|
||
|
||
function MagicData(env, logger) {
|
||
let node = {fs: undefined, data: {}};
|
||
if (env.isNode) {
|
||
node.fs = require("fs");
|
||
try {
|
||
node.fs.accessSync("./magic.json", node.fs.constants.R_OK | node.fs.constants.W_OK)
|
||
} catch (err) {
|
||
node.fs.writeFileSync("./magic.json", "{}", {encoding: "utf8"})
|
||
}
|
||
node.data = require("./magic.json")
|
||
}
|
||
const defaultValueComparator = (oldVal, newVal) => {
|
||
if (typeof newVal === "object") {
|
||
return false
|
||
} else {
|
||
return oldVal === newVal
|
||
}
|
||
};
|
||
const _typeConvertor = val => {
|
||
if (val === "true") {
|
||
return true
|
||
} else if (val === "false") {
|
||
return false
|
||
} else if (typeof val === "undefined") {
|
||
return null
|
||
} else {
|
||
return val
|
||
}
|
||
};
|
||
const _valConvertor = (val, default_, session, read_no_session) => {
|
||
if (session) {
|
||
try {
|
||
if (typeof val === "string") val = JSON.parse(val);
|
||
if (val["magic_session"] === true) {
|
||
val = val[session]
|
||
} else {
|
||
val = null
|
||
}
|
||
} catch {
|
||
val = null
|
||
}
|
||
}
|
||
if (typeof val === "string" && val !== "null") {
|
||
try {
|
||
val = JSON.parse(val)
|
||
} catch {
|
||
}
|
||
}
|
||
if (read_no_session === false && !!val && val["magic_session"] === true) {
|
||
val = null
|
||
}
|
||
if ((val === null || typeof val === "undefined") && default_ !== null && typeof default_ !== "undefined") {
|
||
val = default_
|
||
}
|
||
val = _typeConvertor(val);
|
||
return val
|
||
};
|
||
const convertToObject = obj => {
|
||
if (typeof obj === "string") {
|
||
let data = {};
|
||
try {
|
||
data = JSON.parse(obj);
|
||
const type = typeof data;
|
||
if (type !== "object" || data instanceof Array || type === "bool" || data === null) {
|
||
data = {}
|
||
}
|
||
} catch {
|
||
}
|
||
return data
|
||
} else if (obj instanceof Array || obj === null || typeof obj === "undefined" || obj !== obj || typeof obj === "boolean") {
|
||
return {}
|
||
} else {
|
||
return obj
|
||
}
|
||
};
|
||
const readForNode = (key, default_ = null, session = "", read_no_session = false, externalData = null) => {
|
||
let data = externalData || node.data;
|
||
if (!!data && typeof data[key] !== "undefined" && data[key] !== null) {
|
||
val = data[key]
|
||
} else {
|
||
val = !!session ? {} : null
|
||
}
|
||
val = _valConvertor(val, default_, session, read_no_session);
|
||
return val
|
||
};
|
||
const read = (key, default_ = null, session = "", read_no_session = false, externalData = null) => {
|
||
let val = "";
|
||
if (externalData || env.isNode) {
|
||
val = readForNode(key, default_, session, read_no_session, externalData)
|
||
} else {
|
||
if (env.isSurgeLike) {
|
||
val = $persistentStore.read(key)
|
||
} else if (env.isQuanX) {
|
||
val = $prefs.valueForKey(key)
|
||
}
|
||
val = _valConvertor(val, default_, session, read_no_session)
|
||
}
|
||
logger.debug(`READ DATA [${key}]${!!session ? `[${session}]` : ""} <${typeof val}>\n${JSON.stringify(val)}`);
|
||
return val
|
||
};
|
||
const writeForNode = (key, val, session = "", externalData = null) => {
|
||
let data = externalData || node.data;
|
||
data = convertToObject(data);
|
||
if (!!session) {
|
||
let obj = convertToObject(data[key]);
|
||
obj["magic_session"] = true;
|
||
obj[session] = val;
|
||
data[key] = obj
|
||
} else {
|
||
data[key] = val
|
||
}
|
||
if (externalData !== null) {
|
||
externalData = data
|
||
}
|
||
return data
|
||
};
|
||
const write = (key, val, session = "", externalData = null) => {
|
||
if (typeof val === "undefined" || val !== val) {
|
||
return false
|
||
}
|
||
if (!env.isNode && (typeof val === "boolean" || typeof val === "number")) {
|
||
val = String(val)
|
||
}
|
||
let data = "";
|
||
if (externalData || env.isNode) {
|
||
data = writeForNode(key, val, session, externalData)
|
||
} else {
|
||
if (!session) {
|
||
data = val
|
||
} else {
|
||
if (env.isSurgeLike) {
|
||
data = !!$persistentStore.read(key) ? $persistentStore.read(key) : data
|
||
} else if (env.isQuanX) {
|
||
data = !!$prefs.valueForKey(key) ? $prefs.valueForKey(key) : data
|
||
}
|
||
data = convertToObject(data);
|
||
data["magic_session"] = true;
|
||
data[session] = val
|
||
}
|
||
}
|
||
if (!!data && typeof data === "object") {
|
||
data = JSON.stringify(data, null, 4)
|
||
}
|
||
logger.debug(`WRITE DATA [${key}]${session ? `[${session}]` : ""} <${typeof val}>\n${JSON.stringify(val)}`);
|
||
if (!externalData) {
|
||
if (env.isSurgeLike) {
|
||
return $persistentStore.write(data, key)
|
||
} else if (env.isQuanX) {
|
||
return $prefs.setValueForKey(data, key)
|
||
} else if (env.isNode) {
|
||
try {
|
||
node.fs.writeFileSync("./magic.json", data);
|
||
return true
|
||
} catch (err) {
|
||
logger.error(err);
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
return true
|
||
};
|
||
const update = (key, val, session, comparator = defaultValueComparator, externalData = null) => {
|
||
val = _typeConvertor(val);
|
||
const oldValue = read(key, null, session, false, externalData);
|
||
if (comparator(oldValue, val) === true) {
|
||
return false
|
||
} else {
|
||
const result = write(key, val, session, externalData);
|
||
let newVal = read(key, null, session, false, externalData);
|
||
if (comparator === defaultValueComparator && typeof newVal === "object") {
|
||
return result
|
||
}
|
||
return comparator(val, newVal)
|
||
}
|
||
};
|
||
const delForNode = (key, session, externalData) => {
|
||
let data = externalData || node.data;
|
||
data = convertToObject(data);
|
||
if (!!session) {
|
||
obj = convertToObject(data[key]);
|
||
delete obj[session];
|
||
data[key] = obj
|
||
} else {
|
||
delete data[key]
|
||
}
|
||
if (!!externalData) {
|
||
externalData = data
|
||
}
|
||
return data
|
||
};
|
||
const del = (key, session = "", externalData = null) => {
|
||
let data = {};
|
||
if (externalData || env.isNode) {
|
||
data = delForNode(key, session, externalData);
|
||
if (!externalData) {
|
||
node.fs.writeFileSync("./magic.json", JSON.stringify(data, null, 4))
|
||
} else {
|
||
externalData = data
|
||
}
|
||
} else {
|
||
if (!session) {
|
||
if (env.isStorm) {
|
||
return $persistentStore.remove(key)
|
||
} else if (env.isSurgeLike) {
|
||
return $persistentStore.write(null, key)
|
||
} else if (env.isQuanX) {
|
||
return $prefs.removeValueForKey(key)
|
||
}
|
||
} else {
|
||
if (env.isSurgeLike) {
|
||
data = $persistentStore.read(key)
|
||
} else if (env.isQuanX) {
|
||
data = $prefs.valueForKey(key)
|
||
}
|
||
data = convertToObject(data);
|
||
delete data[session];
|
||
const json = JSON.stringify(data, null, 4);
|
||
write(key, json)
|
||
}
|
||
}
|
||
logger.debug(`DELETE KEY [${key}]${!!session ? `[${session}]` : ""}`)
|
||
};
|
||
const allSessionNames = (key, externalData = null) => {
|
||
let _sessions = [];
|
||
let data = read(key, null, null, true, externalData);
|
||
data = convertToObject(data);
|
||
if (data["magic_session"] !== true) {
|
||
_sessions = []
|
||
} else {
|
||
_sessions = Object.keys(data).filter(key => key !== "magic_session")
|
||
}
|
||
logger.debug(`READ ALL SESSIONS [${key}] <${typeof _sessions}>\n${JSON.stringify(_sessions, null, 4)}`);
|
||
return _sessions
|
||
};
|
||
const allSessions = (key, externalData = null) => {
|
||
let _sessions = {};
|
||
let data = read(key, null, null, true, externalData);
|
||
data = convertToObject(data);
|
||
if (data["magic_session"] === true) {
|
||
_sessions = {...data};
|
||
delete _sessions["magic_session"]
|
||
}
|
||
logger.debug(`READ ALL SESSIONS [${key}] <${typeof _sessions}>\n${JSON.stringify(_sessions, null, 4)}`);
|
||
return _sessions
|
||
};
|
||
return {
|
||
read: read,
|
||
write: write,
|
||
del: del,
|
||
update: update,
|
||
allSessions: allSessions,
|
||
allSessionNames: allSessionNames,
|
||
defaultValueComparator: defaultValueComparator,
|
||
convertToObject: convertToObject
|
||
}
|
||
}
|
||
|
||
function MagicHttp(env, logger) {
|
||
let axiosInstance;
|
||
if (env.isNode) {
|
||
const axios = require("axios");
|
||
axiosInstance = axios.create()
|
||
}
|
||
|
||
class InterceptorManager {
|
||
constructor(isRequest = true) {
|
||
this.handlers = [];
|
||
this.isRequest = isRequest
|
||
}
|
||
|
||
use(fulfilled, rejected, options) {
|
||
if (typeof fulfilled === "function") {
|
||
logger.debug(`Register fulfilled ${fulfilled.name}`)
|
||
}
|
||
if (typeof rejected === "function") {
|
||
logger.debug(`Register rejected ${rejected.name}`)
|
||
}
|
||
this.handlers.push({
|
||
fulfilled: fulfilled,
|
||
rejected: rejected,
|
||
synchronous: options && typeof options.synchronous === "boolean" ? options.synchronous : false,
|
||
runWhen: options ? options.runWhen : null
|
||
});
|
||
return this.handlers.length - 1
|
||
}
|
||
|
||
eject(id) {
|
||
if (this.handlers[id]) {
|
||
this.handlers[id] = null
|
||
}
|
||
}
|
||
|
||
forEach(fn) {
|
||
this.handlers.forEach(element => {
|
||
if (element !== null) {
|
||
fn(element)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
function paramsToQueryString(config) {
|
||
let _config = {...config};
|
||
if (!!_config.params) {
|
||
if (!env.isNode) {
|
||
let qs = Object.keys(_config.params).map(key => {
|
||
const encodeKey = encodeURIComponent(key);
|
||
_config.url = _config.url.replace(new RegExp(`${key}=[^&]*`, "ig"), "");
|
||
_config.url = _config.url.replace(new RegExp(`${encodeKey}=[^&]*`, "ig"), "");
|
||
return `${encodeKey}=${encodeURIComponent(_config.params[key])}`
|
||
}).join("&");
|
||
if (_config.url.indexOf("?") < 0) _config.url += "?";
|
||
if (!/(&|\?)$/g.test(_config.url)) {
|
||
_config.url += "&"
|
||
}
|
||
_config.url += qs;
|
||
delete _config.params;
|
||
logger.debug(`Params to QueryString: ${_config.url}`)
|
||
}
|
||
}
|
||
return _config
|
||
}
|
||
|
||
const mergeConfig = (method, configOrUrl) => {
|
||
let config = typeof configOrUrl === "object" ? {headers: {}, ...configOrUrl} : {url: configOrUrl, headers: {}};
|
||
if (!config.method) {
|
||
config["method"] = method
|
||
}
|
||
config = paramsToQueryString(config);
|
||
if (config["rewrite"] === true) {
|
||
if (env.isSurge) {
|
||
config.headers["X-Surge-Skip-Scripting"] = false;
|
||
delete config["rewrite"]
|
||
} else if (env.isQuanX) {
|
||
config["hints"] = false;
|
||
delete config["rewrite"]
|
||
}
|
||
}
|
||
if (env.isSurgeLike) {
|
||
const contentType = config.headers["content-type"] || config.headers["Content-Type"];
|
||
if (config["method"] !== "GET" && contentType && contentType.indexOf("application/json") >= 0 && config.body instanceof Array) {
|
||
config.body = JSON.stringify(config.body);
|
||
logger.debug(`Convert Array object to String: ${config.body}`)
|
||
}
|
||
} else if (env.isQuanX) {
|
||
if (config.hasOwnProperty("body") && typeof config["body"] !== "string") config["body"] = JSON.stringify(config["body"]);
|
||
config["method"] = method
|
||
} else if (env.isNode) {
|
||
if (method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE") {
|
||
config.data = config.data || config.body
|
||
} else if (method === "GET") {
|
||
config.params = config.params || config.body
|
||
}
|
||
delete config.body
|
||
}
|
||
return config
|
||
};
|
||
const modifyResponse = (resp, config = null) => {
|
||
if (resp) {
|
||
let _resp = {
|
||
...resp,
|
||
config: resp.config || config,
|
||
status: resp.statusCode || resp.status,
|
||
body: resp.body || resp.data,
|
||
headers: resp.headers || resp.header
|
||
};
|
||
if (typeof _resp.body === "string") {
|
||
try {
|
||
_resp.body = JSON.parse(_resp.body)
|
||
} catch {
|
||
}
|
||
}
|
||
delete _resp.data;
|
||
return _resp
|
||
} else {
|
||
return resp
|
||
}
|
||
};
|
||
const convertHeadersToLowerCase = headers => {
|
||
return Object.keys(headers).reduce((acc, key) => {
|
||
acc[key.toLowerCase()] = headers[key];
|
||
return acc
|
||
}, {})
|
||
};
|
||
const convertHeadersToCamelCase = headers => {
|
||
return Object.keys(headers).reduce((acc, key) => {
|
||
const newKey = key.split("-").map(word => word[0].toUpperCase() + word.slice(1)).join("-");
|
||
acc[newKey] = headers[key];
|
||
return acc
|
||
}, {})
|
||
};
|
||
const raiseExceptionByStatusCode = (resp, config = null) => {
|
||
if (!!resp && resp.status >= 400) {
|
||
logger.debug(`Raise exception when status code is ${resp.status}`);
|
||
return {
|
||
name: "RequestException",
|
||
message: `Request failed with status code ${resp.status}`,
|
||
config: config || resp.config,
|
||
response: resp
|
||
}
|
||
}
|
||
};
|
||
const interceptors = {request: new InterceptorManager, response: new InterceptorManager(false)};
|
||
let requestInterceptorChain = [];
|
||
let responseInterceptorChain = [];
|
||
let synchronousRequestInterceptors = true;
|
||
|
||
function interceptConfig(config) {
|
||
config = paramsToQueryString(config);
|
||
logger.debug(`HTTP ${config["method"].toUpperCase()}:\n${JSON.stringify(config)}`);
|
||
return config
|
||
}
|
||
|
||
function interceptResponse(resp) {
|
||
try {
|
||
resp = !!resp ? modifyResponse(resp) : resp;
|
||
logger.sniffer(`HTTP ${resp.config["method"].toUpperCase()}:\n${JSON.stringify(resp.config)}\nSTATUS CODE:\n${resp.status}\nRESPONSE:\n${typeof resp.body === "object" ? JSON.stringify(resp.body) : resp.body}`);
|
||
const err = raiseExceptionByStatusCode(resp);
|
||
if (!!err) {
|
||
return Promise.reject(err)
|
||
}
|
||
return resp
|
||
} catch (err) {
|
||
logger.error(err);
|
||
return resp
|
||
}
|
||
}
|
||
|
||
const registerInterceptors = config => {
|
||
try {
|
||
requestInterceptorChain = [];
|
||
responseInterceptorChain = [];
|
||
interceptors.request.forEach(interceptor => {
|
||
if (typeof interceptor.runWhen === "function" && interceptor.runWhen(config) === false) {
|
||
return
|
||
}
|
||
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
|
||
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected)
|
||
});
|
||
interceptors.response.forEach(interceptor => {
|
||
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected)
|
||
})
|
||
} catch (err) {
|
||
logger.error(`Failed to register interceptors: ${err}.`)
|
||
}
|
||
};
|
||
const request = (method, config) => {
|
||
let dispatchRequest;
|
||
const _method = method.toUpperCase();
|
||
config = mergeConfig(_method, config);
|
||
if (env.isNode) {
|
||
dispatchRequest = axiosInstance
|
||
} else {
|
||
if (env.isSurgeLike) {
|
||
dispatchRequest = config => {
|
||
return new Promise((resolve, reject) => {
|
||
$httpClient[method.toLowerCase()](config, (err, resp, body) => {
|
||
if (err) {
|
||
let newErr = {
|
||
name: err.name || err,
|
||
message: err.message || err,
|
||
stack: err.stack || err,
|
||
config: config,
|
||
response: modifyResponse(resp)
|
||
};
|
||
reject(newErr)
|
||
} else {
|
||
resp.config = config;
|
||
resp.body = body;
|
||
resolve(resp)
|
||
}
|
||
})
|
||
})
|
||
}
|
||
} else {
|
||
dispatchRequest = config => {
|
||
return new Promise((resolve, reject) => {
|
||
$task.fetch(config).then(resp => {
|
||
resp = modifyResponse(resp, config);
|
||
const err = raiseExceptionByStatusCode(resp, config);
|
||
if (err) {
|
||
return Promise.reject(err)
|
||
}
|
||
resolve(resp)
|
||
}).catch(err => {
|
||
let newErr = {
|
||
name: err.message || err.error,
|
||
message: err.message || err.error,
|
||
stack: err.error,
|
||
config: config,
|
||
response: !!err.response ? modifyResponse(err.response) : null
|
||
};
|
||
reject(newErr)
|
||
})
|
||
})
|
||
}
|
||
}
|
||
}
|
||
let promise;
|
||
registerInterceptors(config);
|
||
const defaultRequestInterceptors = [interceptConfig, undefined];
|
||
const defaultResponseInterceptors = [interceptResponse, undefined];
|
||
if (!synchronousRequestInterceptors) {
|
||
logger.debug("Interceptors are executed in asynchronous mode");
|
||
let chain = [dispatchRequest, undefined];
|
||
Array.prototype.unshift.apply(chain, defaultRequestInterceptors);
|
||
Array.prototype.unshift.apply(chain, requestInterceptorChain);
|
||
chain = chain.concat(defaultResponseInterceptors);
|
||
chain = chain.concat(responseInterceptorChain);
|
||
promise = Promise.resolve(config);
|
||
while (chain.length) {
|
||
try {
|
||
let onFulfilled = chain.shift();
|
||
let onRejected = chain.shift();
|
||
if (!env.isNode && config["timeout"] && onFulfilled === dispatchRequest) {
|
||
onFulfilled = requestTimeout
|
||
}
|
||
if (typeof onFulfilled === "function") {
|
||
logger.debug(`Executing request fulfilled ${onFulfilled.name}`)
|
||
}
|
||
if (typeof onRejected === "function") {
|
||
logger.debug(`Executing request rejected ${onRejected.name}`)
|
||
}
|
||
promise = promise.then(onFulfilled, onRejected)
|
||
} catch (err) {
|
||
logger.error(`request exception: ${err}`)
|
||
}
|
||
}
|
||
return promise
|
||
} else {
|
||
logger.debug("Interceptors are executed in synchronous mode");
|
||
Array.prototype.unshift.apply(requestInterceptorChain, defaultRequestInterceptors);
|
||
requestInterceptorChain = requestInterceptorChain.concat([interceptConfig, undefined]);
|
||
while (requestInterceptorChain.length) {
|
||
let onFulfilled = requestInterceptorChain.shift();
|
||
let onRejected = requestInterceptorChain.shift();
|
||
try {
|
||
if (typeof onFulfilled === "function") {
|
||
logger.debug(`Executing request fulfilled ${onFulfilled.name}`)
|
||
}
|
||
config = onFulfilled(config)
|
||
} catch (error) {
|
||
if (typeof onRejected === "function") {
|
||
logger.debug(`Executing request rejected ${onRejected.name}`)
|
||
}
|
||
onRejected(error);
|
||
break
|
||
}
|
||
}
|
||
try {
|
||
if (!env.isNode && config["timeout"]) {
|
||
promise = requestTimeout(config)
|
||
} else {
|
||
promise = dispatchRequest(config)
|
||
}
|
||
} catch (err) {
|
||
return Promise.reject(err)
|
||
}
|
||
Array.prototype.unshift.apply(responseInterceptorChain, defaultResponseInterceptors);
|
||
while (responseInterceptorChain.length) {
|
||
promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift())
|
||
}
|
||
return promise
|
||
}
|
||
|
||
function requestTimeout(config) {
|
||
try {
|
||
const timer = new Promise((_, reject) => {
|
||
setTimeout(() => {
|
||
let err = {message: `timeout of ${config["timeout"]}ms exceeded.`, config: config};
|
||
reject(err)
|
||
}, config["timeout"])
|
||
});
|
||
return Promise.race([dispatchRequest(config), timer])
|
||
} catch (err) {
|
||
logger.error(`Request Timeout exception: ${err}.`)
|
||
}
|
||
}
|
||
};
|
||
return {
|
||
request: request,
|
||
interceptors: interceptors,
|
||
convertHeadersToLowerCase: convertHeadersToLowerCase,
|
||
convertHeadersToCamelCase: convertHeadersToCamelCase,
|
||
modifyResponse: modifyResponse,
|
||
get: configOrUrl => {
|
||
return request("GET", configOrUrl)
|
||
},
|
||
post: configOrUrl => {
|
||
return request("POST", configOrUrl)
|
||
},
|
||
put: configOrUrl => {
|
||
return request("PUT", configOrUrl)
|
||
},
|
||
patch: configOrUrl => {
|
||
return request("PATCH", configOrUrl)
|
||
},
|
||
delete: configOrUrl => {
|
||
return request("DELETE", configOrUrl)
|
||
},
|
||
head: configOrUrl => {
|
||
return request("HEAD", configOrUrl)
|
||
},
|
||
options: configOrUrl => {
|
||
return request("OPTIONS", configOrUrl)
|
||
}
|
||
}
|
||
} |