// ==UserScript==
// @name Lofter网页版查看发现内容
// @license GPLv3
// @namespace http://tampermonkey.net/
// @version 0.3.1
// @description 在 Lofter 网页版查看App端的个性化推荐内容
// @author SrakhiuMeow
// @match https://www.lofter.com/
// @grant GM.xmlHttpRequest
// @connect api.lofter.com
// @downloadURL https://update.greasyfork.icu/scripts/532972/Lofter%E7%BD%91%E9%A1%B5%E7%89%88%E6%9F%A5%E7%9C%8B%E5%8F%91%E7%8E%B0%E5%86%85%E5%AE%B9.user.js
// @updateURL https://update.greasyfork.icu/scripts/532972/Lofter%E7%BD%91%E9%A1%B5%E7%89%88%E6%9F%A5%E7%9C%8B%E5%8F%91%E7%8E%B0%E5%86%85%E5%AE%B9.meta.js
// ==/UserScript==
(function () {
'use strict';
// 喜欢功能暂时不可用
function like(authkey, postId, mode) {
const url = new URL("https://api.lofter.com/v2.0/exploreRecom.api");
const params = {
'product': 'lofter-android-7.6.12',
'op': mode ? 'like' : 'unlike',
'id': postId,
'type': 1,
};
Object.keys(params).forEach(key =>
url.searchParams.append(key, params[key])
);
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url: url.toString(),
headers: {
'lofproduct': 'lofter-android-7.6.12',
'user-agent': 'LOFTER-Android 7.6.12 (V2272A; Android 13; null) WIFI',
'lofter-phone-login-auth': authkey,
'accept-encoding': "br,gzip",
},
onload: function (response) {
try {
// console.log(response);
// const data = JSON.parse(response.responseText);
resolve(response);
} catch (e) {
reject(e);
}
},
onerror: function (error) {
reject(error);
}
});
});
}
function getCookie(name) {
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const [key, value] = cookie.split('=');
if (key === name) {
return decodeURIComponent(value); // 解码 Cookie 值
}
}
return null; // 如果未找到 Cookie,返回 null
}
function getRecommends(authkey, offset = 0) {
const url = new URL("https://api.lofter.com/recommend/exploreRecom.json");
const params = {
'offset': offset,
'product': 'lofter-android-7.6.12'
};
Object.keys(params).forEach(key =>
url.searchParams.append(key, params[key])
);
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url: url.toString(),
headers: {
'lofproduct': 'lofter-android-7.6.12',
'User-Agent': "LOFTER-Android 7.6.12 (V2272A; Android 13; null) WIFI",
'Accept-Encoding': "br,gzip",
'lofter-phone-login-auth': authkey,
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
// console.log(data);
// offset += data.offset;
resolve(data.data);
} catch (e) {
reject(e);
}
},
onerror: function (error) {
reject(error);
}
});
});
}
function notInterest(authkey, blogId, postId) {
const url = new URL("https://api.lofter.com/newapi/recMark.json");
const params = {
'showId': postId,
'blogId': blogId,
'markType': 'notInterestArticle',
'module': 'feed_rec',
'product': 'lofter-android-7.6.12'
};
Object.keys(params).forEach(key =>
url.searchParams.append(key, params[key])
);
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url: url.toString(),
headers: {
'lofproduct': 'lofter-android-7.6.12',
'User-Agent': "LOFTER-Android 7.6.12 (V2272A; Android 13; null) WIFI",
'Accept-Encoding': "br,gzip",
'lofter-phone-login-auth': authkey,
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
// offset += data.offset;
resolve(data.data);
} catch (e) {
reject(e);
}
},
onerror: function (error) {
reject(error);
}
});
});
}
function buildArticleElement(blogUrl, avatarUrl, publisher, imageUrl, digest, tags, postUrl, title, fullContent, postId, hotCount=0, blogId=0) {
const avatar = document.createElement('div');
avatar.className = 'mlistimg';
avatar.innerHTML = `
`;
const notDisplayImage = typeof imageUrl == "undefined" || imageUrl == null || imageUrl == "";
const content = document.createElement('div');
content.className = 'mlistcnt';
content.innerHTML = `
`;
const article = document.createElement('div');
article.className = 'm-mlist';
article.appendChild(avatar);
article.appendChild(content);
// 给喜欢按钮添加点击事件
const likeButton = article.querySelector('.w-icn-0b');
likeButton.addEventListener('click', () => {
// console.log('喜欢按钮被点击');
const authkey = getCookie("LOFTER-PHONE-LOGIN-AUTH");
if (authkey === null) {
console.log('未登录');
return;
}
if (likeButton.classList.contains('w-icn-0b-do')) {
like(authkey, postId, false, blogId)
.then(response => {
// console.log(response);
likeButton.classList.remove('w-icn-0b-do');
likeButton.classList.remove('w-icn-0b-do-anim');
likeButton.querySelector('span').textContent = '喜欢';
})
.catch(error => console.error(error));
} else {
like(authkey, postId, true, blogId)
.then(response => {
// console.log(response);
likeButton.classList.add('w-icn-0b-do-anim');
likeButton.classList.add('w-icn-0b-do');
likeButton.querySelector('span').textContent = '取消喜欢';
})
.catch(error => console.error(error));
}
});
// 给不感兴趣按钮添加点击事件
const notInterestButton = article.querySelector('.noti');
notInterestButton.addEventListener('click', () => {
if (notInterestButton.innerHTML.includes('确认不感兴趣')) {
notInterestButton.addEventListener('click', () => {
// console.log('不感兴趣按钮被点击');
const authkey = getCookie("LOFTER-PHONE-LOGIN-AUTH");
if (authkey === null) {
console.log('未登录');
return;
}
notInterest(authkey, blogId, postId)
.then(response => {
article.remove();
})
.catch(error => console.error(error));
});
} else {
notInterestButton.textContent = '确认不感兴趣';
}
}
);
// 给展开按钮添加点击事件
// const more = article.querySelector('a.w-more');
// more.addEventListener('click', () => {
// const full = article.querySelector('.txt.full');
// const digest = article.querySelector('.txt.digest');
// if (more.classList.contains('w-more-open')) {
// more.classList.remove('w-more-open');
// more.classList.add('w-more-close');
// more.textContent = '收起';
// full.style.display = 'block';
// digest.style.display = 'none';
// }
// else {
// more.classList.remove('w-more-close');
// more.classList.add('w-more-open');
// more.textContent = '展开';
// full.style.display = 'none';
// digest.style.display = 'block';
// digest.scrollIntoView({ behavior: 'smooth' });
// }
// });
// 给查看原图按钮添加点击事件
const viewOriginal = article.querySelector('.w-zoom');
const newViewOriginal = viewOriginal.cloneNode(true);
viewOriginal.parentNode.replaceChild(newViewOriginal, viewOriginal);
newViewOriginal.href = imageUrl;
newViewOriginal.target = '_blank';
return article;
}
function insertArticle(article) {
const main = document.getElementById('main');
main.insertBefore(article, main.children[7]);
}
function insertArticles(articles) {
const main = document.getElementById('main');
// const firstArticle = main.children[7];
// 插入新元素(推荐文章)
articles.forEach(article => {
// console.log(article);
const articleElement = buildArticleElement(
"https://" + article.blogInfo.blogName + ".lofter.com",
article.blogInfo.bigAvaImg,
article.blogInfo.blogNickName,
article.postData.postView.firstImage.orign.split('?')[0],
article.postData.postView.digest,
article.postData.postView.tagList.map(tag => `${tag}`).join(' '),
article.postData.postView.postPageUrl,
article.postData.postView.title,
null,
article.postData.postView.id,
article.postData.postCount.hotCount,
article.postData.postView.blogId
);
// main.insertBefore(articleElement, firstArticle);
main.appendChild(articleElement);
});
}
const offset = {num: 0};
function change2recommends() {
// 变换按钮状态
this.querySelector('span').textContent = '返回主页';
this.removeEventListener('click', change2recommends);
this.addEventListener('click', () => {
location.reload();
});
// 获取authkey
const authkey = getCookie("LOFTER-PHONE-LOGIN-AUTH");
if (authkey === null) {
console.log('未登录');
throw new Error('未登录');
}
// console.log('authkey:', authkey);
// 获取blogDomain
let blogDomain = document.querySelector('span.lg2').textContent;
// console.log('blogDomain:', blogDomain);
// 清除原有元素(关注更新)
const divs = document.querySelectorAll('div.m-mlist');
divs.forEach(div => {
div.remove();
});
const history = getRecommends(authkey, 0)
.then(response => {
// console.log(response);
insertArticles(response.list);
offset.num += response.offset;
})
.catch(error => console.error(error));
// console.log('hi');
// console.log(history);
let isFetching = false; // 防止重复请求
window.addEventListener('scroll', e => {
e.stopImmediatePropagation();
const threshold = 100;
const isNearBottom = (window.innerHeight + window.scrollY) >=
document.body.scrollHeight - threshold;
if (isNearBottom && !isFetching) { // 只有在未加载时才触发
isFetching = true; // 锁定
// console.log("触发自定义加载...");
getRecommends(authkey, offset.num)
.then(response => {
// console.log(response);
offset.num += response.offset;
insertArticles(response.list);
})
.catch(error => console.error(error))
.finally(() => {
isFetching = false; // 解锁
});
}
}, true);
}
function initializeRecommendsFeature() {
const slideBar = document.getElementById('slide-bar')?.children[0]?.children[1];
if (!slideBar) {
console.error('无法找到侧边栏元素');
return;
}
// 添加分割线
const dividingLine = document.createElement('div');
dividingLine.className = document.querySelector('[class*="horizontalDividingLine"]').className;
slideBar.insertBefore(dividingLine, slideBar.children[0]);
// 添加发现按钮
// 不知道为什么直接用会有报错(
const recommends = document.createElement('div');
recommends.id = 'getRecommends';
recommends.innerHTML = `
`;
recommends.style.cursor = 'pointer';
recommends.addEventListener('click', change2recommends);
slideBar.insertBefore(recommends, slideBar.children[0]);
}
// 监听 DOM 变化,等待 slidebar 加载完成
function waitForElement(selector, callback) {
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
clearTimeout(timeoutId); // 清除超时定时器
obs.disconnect(); // 停止观察
callback(element);
}
});
const timeoutId = setTimeout(() => {
observer.disconnect(); // 停止观察
}, 2000); // 2秒超时
// 开始观察整个文档的变化
observer.observe(document, {
childList: true,
subtree: true
});
}
// 避免脚本过早执行
waitForElement('#slide-bar', (element) => {
setTimeout(() => {
// console.log('Slide bar loaded');
initializeRecommendsFeature();
}, 50); // 等待50ms后执行
});
})();