// ==UserScript==
// @name LeetCode 刷题小助手
// @namespace http://tampermonkey.net/
// @version 0.3
// @description 查询LeetCode考题最近的考察频率、显示codeTop评论
// @author Leochens
// @match https://leetcode.cn/problems/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=leetcode.cn
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// @connect *
// @downloadURL https://update.greasyfork.icu/scripts/444881/LeetCode%20%E5%88%B7%E9%A2%98%E5%B0%8F%E5%8A%A9%E6%89%8B.user.js
// @updateURL https://update.greasyfork.icu/scripts/444881/LeetCode%20%E5%88%B7%E9%A2%98%E5%B0%8F%E5%8A%A9%E6%89%8B.meta.js
// ==/UserScript==
var listeners = []
var doc = document
var MutationObserver = MutationObserver || window.WebKitMutationObserver
var observer
let setTokenMenuId
let problemId
let anchor;
function observerEleReady(selector, fn) {
// 储存选择器和回调函数
listeners.push({
selector: selector,
fn: fn
})
if (!observer) {
// 监听document变化
observer = new MutationObserver(check)
observer.observe(doc.documentElement, {
childList: true,
subtree: true
})
}
// 检查该节点是否已经在DOM中
check()
}
function check() {
// 检查是否匹配已储存的节点
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i]
// 检查指定节点是否有匹配
var elements = document.querySelectorAll(listener.selector)
for (var j = 0; j < elements.length; j++) {
var element = elements[j]
// 确保回调函数只会对该元素调用一次
if (!element.ready) {
element.ready = true
// 对该节点调用回调函数
listener.fn.call(element, element)
}
}
}
}
function fmtDate(utcdate) {
let dateee
if (utcdate) {
dateee = new Date(utcdate).toJSON()
} else {
dateee = new Date().toJSON()
}
return new Date(+new Date(dateee) + 8 * 3600 * 1000).toISOString().replace(/T/g, ' ')
.replace(/\.[\d]{3}Z/, '')
}
function insert() {
return new Promise((resolve,reject)=>{
observerEleReady('h4[data-cypress="QuestionTitle"]', () => {
const title = document.querySelector('h4[data-cypress="QuestionTitle"]')
const titleText = title.innerText
const container = title.parentNode
anchor = container;
const codeTop = document.createElement('span')
codeTop.setAttribute('id', 'codeTop')
codeTop.setAttribute('style', 'display: inline-block;vertical-align: middle;margin-left: 20px;font-size: 12px;font-weight: 500;color: #666')
container.insertBefore(codeTop, container.lastChild)
// 获取codeTop数据
GM_xmlhttpRequest({
method: 'get',
url: `https://codetop.cc/api/questions/?page=1&search=${encodeURI(titleText).replace('.', '')}&ordering=-frequency`,
// data: 'typeName=XXX&content=XXX&options=XXX',
headers: { 'Content-Type': 'application/json' },
onload: function(res) {
// code
if (res.status === 200) {
const obj = JSON.parse(res.response)
console.log(obj)
const value = obj.list[0].value
const time = obj.list[0].time
problemId = obj.list[0].id
let color = 'green'
if (value > 50) color = 'orange'
if (value > 100) color = 'red'
codeTop.innerHTML = '本题考察频率: ' +
` ${value} ` + ' 最近考察时间:' + fmtDate(time)
resolve(1);
} else {
console.log('获取codeTop数据失败!')
reject(0)
}
},
onerror: function(e) {
console.log('获取codeTop数据失败!')
reject(0)
}
})
})
})
}
function setToken() {
const token = prompt('请输入从codeTop获得的用户token')
if(!token) return;
GM_setValue('token', token)
alert('设置token成功!')
if (setTokenMenuId) {
GM_unregisterMenuCommand(setTokenMenuId)
}
setTokenMenuId = GM_registerMenuCommand('已设置token,点击重新设置' + token, setToken)
}
function initMenu() {
const token = GM_getValue('token')
if (token) {
setTokenMenuId = GM_registerMenuCommand('已设置token,点击重新设置' + token, setToken)
} else {
setTokenMenuId = GM_registerMenuCommand('暂无token,点击设置token', setToken)
}
}
async function initFrequency() {
const firstTab = document.querySelector("[data-key='description']>a")
try{
await insert()
}catch(e){}
firstTab.onclick = insert
}
function closeCommentList(){
const commentList = document.getElementById('codetop-comment-list')
if(!commentList) return console.log("未找到元素!");
commentList.style.display = 'none';
}
function openCommentList(){
const commentList = document.getElementById('codetop-comment-list')
if(!commentList) return alert("未找到相关元素 请刷新后再试!");
commentList.style.display = 'block';
}
function renderCommentList(comments){
const ul = document.createElement('ul');
const ulStyle = `
position: absolute;
display:none;
width: 700px;
list-style: none;
height: 400px;
z-index: 1;
background-color: rgb(255, 255, 255);
border: 1px solid rgb(221, 221, 221);
border-radius: 4px;
inset: 0px;
overflow: aoto;
margin: 100px auto;
box-shadow: 4px 3px 5px 1px #efefef;
padding: 20px;`
ul.style = ulStyle;
ul.setAttribute('id','codetop-comment-list');
const closeBtn = document.createElement('button');
closeBtn.innerHTML = "关闭";
closeBtn.onclick = closeCommentList;
closeBtn.style = `
border: 1px solid #ddd;
font-size: 12px;
border-radius: 2px
margin-left: 20px;
background-color: #fff;
position:absolute;
top: 0px;
right: 0px;
cursor: pointer;`
ul.append(closeBtn)
let li;
for(let comment of comments){
li = document.createElement('li');
li.style="font-size:15px;margin-bottom:10px"
li.innerHTML = `
${comment.username}: ${comment.content}
${fmtDate(comment.time)}
`
ul.append(li)
}
const app = document.getElementById('app')
app.prepend(ul);
return ul;
}
function initComments() {
const token = GM_getValue('token')
if (!token) {
return console.log('未配置codeTop的token!不能获取评论')
}
GM_xmlhttpRequest({
method: 'get',
url: `https://codetop.cc/api/comments/?leetcode=${problemId}&page=1&ordering=-like_num,-time`,
// data: 'typeName=XXX&content=XXX&options=XXX',
headers: { 'Content-Type': 'application/json', 'authorization': `Token ${token}` },
onload: function(res) {
// code
if (res.status === 200) {
const obj = JSON.parse(res.response)
console.log(obj)
const comments = obj.results;
renderCommentList(comments);
} else {
console.log('获取codeTopp评论数据失败!',res)
}
},
onerror: function(e) {
console.log(e);
console.log('获取codeTop评论数据失败! 可能是token失效!请重新配置!')
}
})
const openCommentListBtn = document.createElement("button");
openCommentListBtn.innerHTML="查看codeTop评论"
openCommentListBtn.onclick =openCommentList;
openCommentListBtn.style = `
border: 1px solid #ddd;
font-size: 12px;
border-radius: 2px
margin-left: 20px;
background-color: #fff;
cursor: pointer;`
if(!anchor)return console.log("未找到anchor挂载点")
anchor.insertBefore(openCommentListBtn, anchor.lastChild)
}
async function launch(){
// 初始化考频显示
await initFrequency()
// 初始化菜单
initMenu()
// 初始化评论
initComments()
}
window.onload =async function() {
'use strict'
await launch();
}