Greasy Fork is available in English.

Luogu Favorites

Enable a unlimited favourite list

// ==UserScript==
// @name         Luogu Favorites
// @namespace    http://tampermonkey.net/
// @version      v2.6.0
// @description  Enable a unlimited favourite list
// @author       limesarine
// @match        https://www.luogu.com.cn/*
// @license      © 2024 Limesarine. All rights reserved.
// @icon         https://www.google.com/s2/favicons?sz=64&domain=luogu.com.cn
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    let Pflag=false,Fflag=false;
    if(localStorage.limesarine_favourite){}
    else
    {
        localStorage.limesarine_favourite="{}";
    }
    let tmp=JSON.parse(localStorage.limesarine_favourite);
    delete tmp.list;
    localStorage.limesarine_favourite=JSON.stringify(tmp);
    const observer=new MutationObserver(function(mutationsList,observer){
        setTimeout(function() {
            const url=document.URL;
            let Pmatches=url.match(/problem\/([A-Z][A-Za-z0-9_]+)$/);
            if(Pmatches && !Pflag)
            {
                Pflag=true;
                let title="";
                let spanElements=document.getElementsByTagName("span");
                for(let i=0;i<spanElements.length;i++)
                {
                    let spanElement=spanElements[i];
                    let titleValue=spanElement.getAttribute("title");
                    if(titleValue && titleValue!=Pmatches[1])
                    {
                        title=titleValue;
                    }
                }
                if(title.startsWith(Pmatches[1]))
                {
                    title=title.substr(Pmatches[1].length+1);
                }
                let now=JSON.parse(localStorage.limesarine_favourite)[Pmatches[1]];
                console.log(Pmatches[1],title);
                let newScript=document.createElement("script");
                newScript.innerHTML=`
function limesarine_add_to_favourite(pid,title)
{
    let dict=JSON.parse(localStorage.limesarine_favourite);
    dict[pid]=decodeURI(title);
    localStorage.limesarine_favourite=JSON.stringify(dict);
    alert('加入成功!');
    location.reload();
}
function limesarine_delete_from_favourite(pid)
{
    let dict=JSON.parse(localStorage.limesarine_favourite);
    delete dict[pid];
    localStorage.limesarine_favourite=JSON.stringify(dict);
    alert('删除成功!');
    location.reload();
}
`;
                document.head.appendChild(newScript);
                let newButton=document.createElement("button");
                newButton.setAttribute("type","button");
                newButton.setAttribute("data-v-7ade990c", "");
                newButton.setAttribute("data-v-1fbfa3c2", "");
                newButton.setAttribute("data-v-2dfcfd35", "");
                newButton.setAttribute("class","lfe-form-sz-middle");
                if(now)
                {
                    newButton.setAttribute("style","border-color: rgba(255, 255, 255, 0.5); color: rgb(255, 255, 255); background-color: rgba(0, 0, 0, 0.5);");
                    newButton.setAttribute("onclick","limesarine_delete_from_favourite('"+Pmatches[1]+"');");
                    newButton.innerHTML="移出计划";
                }
                else
                {
                    newButton.setAttribute("style","border-color: rgb(52, 152, 219); background-color: rgb(52, 152, 219);");
                    newButton.setAttribute("onclick","limesarine_add_to_favourite(\""+Pmatches[1]+"\",\""+encodeURI(title)+"\");");
                    newButton.innerHTML="加入计划";
                }
                let element=document.getElementsByClassName('lfe-form-sz-middle')[0];
                element.parentNode.insertBefore(newButton,element.nextSibling);
            }
            else if((url=="https://www.luogu.com.cn" || url=="https://www.luogu.com.cn/") && !Fflag)
            {
                Fflag=true;
                let newArticle=document.createElement("div");
                newArticle.setAttribute("class","lg-article");
                newArticle.innerHTML="<h2>做题计划</h2>";
                let newScript=document.createElement("script");
                newScript.innerHTML=`
async function limesarine_clear()
{
    localStorage.limesarine_favourite="{}";
    alert('清空成功!')
    location.reload();
}
async function limesarine_import_from_paste()
{
    let pasteid=document.getElementById('limesarine_paste_no').value;
    let reply=await fetch('/paste/'+pasteid+'?_contentOnly');
    let content=await reply.text();
    let json=await JSON.parse(content);
    localStorage.limesarine_favourite=json.currentData.paste.data;
    alert('导入成功!')
    location.reload();
}
async function limesarine_export_to_paste()
{
    let pasteid=document.getElementById('limesarine_paste_no').value;
    let response=await fetch("/paste/edit/"+pasteid, {
        headers: [
            ["content-type", "application/json"],
            ["referer", "https://www.luogu.com.cn/"],
            ["x-csrf-token", document.querySelector("meta[name=csrf-token]").content],
        ],
        body: JSON.stringify({
            data: localStorage.limesarine_favourite,
            id: pasteid,
            public: true,
            time: Math.floor(Date.now()/1000),
        }),
        method: "POST",
    });
    response=await response.text();
    alert('导出成功!');
}
`;
                document.head.appendChild(newScript);
                let savedFavourites=JSON.parse(localStorage.limesarine_favourite);
                for(let i in savedFavourites)
                {
                    newArticle.innerHTML+='<div class="tasklist-item" data-pid="'+i+'"><a class="colored" style="padding-left: 3px" href="/problem/'+i+'" target="_blank"><b>'+i+'</b> '+savedFavourites[i]+'</a></div>';
                }
                newArticle.innerHTML+=`
<p><input type="text" class="am-form-field" placeholder="云剪贴板编号" id="limesarine_paste_no"></input></p>
<p>
    <button class="am-btn am-btn-danger am-btn-sm" onclick="limesarine_import_from_paste();">导入</button>
    <button class="am-btn am-btn-primary am-btn-sm" onclick="limesarine_export_to_paste();">导出</button>
    <button class="am-btn am-btn-sm am-btn-success" onclick="limesarine_clear();">清空</button>
</p>
<div><small>我们提供从云剪贴板导入和导出到云剪贴板的功能。注意,导入操作将覆盖当前做题计划,请谨慎使用!</small></div>
`;
                let element=document.getElementsByClassName('lg-article')[3];
                element.parentNode.insertBefore(newArticle,element);
                console.log(newArticle);
            }
        }, 750);
    });
    observer.observe(document,{childList:true,subtree:true});
})();