GreasyFork Βοηθός ειδοποιήσεων

Όταν υπάρχει μια νέα απάντηση στο σενάριό σας ή σε μια συζήτηση στην οποία συμμετέχετε,Το σενάριο θα εμφανίσει το πιο πρόσφατο περιεχόμενο συζήτησης σε ένα παράθυρο τρόπου στην ιστοσελίδα。

// ==UserScript==
// @name              GreasyFork Discussion Watcher
// @name:zh-CN        GreasyFork 通知助手
// @description:zh-CN 当你的脚本或你参与的讨论有新回复时,脚本会在网页上以模态窗口显示最新的讨论内容。
// @name:ar           GreasyFork مساعد الإخطار
// @description:ar    عندما يكون هناك رد جديد على البرنامج النصي الخاص بك أو على مناقشة تشارك فيها,سيعرض البرنامج النصي أحدث محتوى للمناقشة في نافذة مشروطة على صفحة الويب。
// @name:bg           GreasyFork Асистент за уведомяване
// @description:bg    Когато има нов отговор на вашия скрипт или на дискусия, в която участвате,Скриптът ще покаже най-новото съдържание на дискусия в модален прозорец на уеб страницата。
// @name:cs           GreasyFork Asistent upozornění
// @description:cs    Když přijde nová odpověď na váš scénář nebo na diskuzi, které se účastníte,Skript zobrazí nejnovější obsah diskuse v modálním okně na webové stránce。
// @name:da           GreasyFork Underretningsassistent
// @description:da    Når der er et nyt svar på dit manuskript eller til en diskussion, du deltager i,Scriptet vil vise det seneste diskussionsindhold i et modalt vindue på websiden。
// @name:de           GreasyFork Benachrichtigungsassistent
// @description:de    Wenn es eine neue Antwort auf Ihr Skript oder auf eine Diskussion gibt, an der Sie teilnehmen,Das Skript zeigt den neuesten Diskussionsinhalt in einem modalen Fenster auf der Webseite an。
// @name:el           GreasyFork Βοηθός ειδοποιήσεων
// @description:el    Όταν υπάρχει μια νέα απάντηση στο σενάριό σας ή σε μια συζήτηση στην οποία συμμετέχετε,Το σενάριο θα εμφανίσει το πιο πρόσφατο περιεχόμενο συζήτησης σε ένα παράθυρο τρόπου στην ιστοσελίδα。
// @name:en           GreasyFork Notification Assistant
// @description:en    When there’s a new reply to your script or to a discussion you’re participating in,The script will display the latest discussion content in a modal window on the web page。
// @name:eo           GreasyFork Asistanto pri sciigo
// @description:eo    Kiam estas nova respondo al via skripto aŭ al diskuto, kiun vi partoprenas,La skripto montros la lastan diskutenhavon en modala fenestro sur la retpaĝo。
// @name:es           GreasyFork Asistente de notificaciones
// @description:es    Cuando hay una nueva respuesta a tu guión o a una discusión en la que estás participando,El script mostrará el contenido de la discusión más reciente en una ventana modal en la página web.。
// @name:fi           GreasyFork Ilmoitusassistentti
// @description:fi    Kun käsikirjoitukseen tai keskusteluun, johon osallistut, tulee uusi vastaus,Käsikirjoitus näyttää uusimman keskustelusisällön web-sivun modaaliikkunassa。
// @name:fr           GreasyFork Assistant de notifications
// @description:fr    Lorsqu’il y a une nouvelle réponse à votre script ou à une discussion à laquelle vous participez,Le script affichera le dernier contenu de la discussion dans une fenêtre modale sur la page Web。
// @name:he           GreasyFork עוזר הודעות
// @description:he    כאשר יש תשובה חדשה לתסריט שלך או לדיון שאתה משתתף בו,הסקריפט יציג את תוכן הדיון העדכני ביותר בחלון מודאלי בדף האינטרנט。
// @name:hr           GreasyFork Pomoćnik za obavijesti
// @description:hr    Kada postoji novi odgovor na vašu skriptu ili na raspravu u kojoj sudjelujete,Skripta će prikazati najnoviji sadržaj rasprave u modalnom prozoru na web stranici。
// @name:hu           GreasyFork Értesítési asszisztens
// @description:hu    Amikor új válasz érkezik a forgatókönyvére vagy egy beszélgetésre, amelyben részt vesz,A szkript megjeleníti a legfrissebb vitatartalmat egy modális ablakban a weboldalon。
// @name:id           GreasyFork Asisten Pemberitahuan
// @description:id    Ketika ada balasan baru pada naskah Anda atau pada diskusi yang Anda ikuti,Script akan menampilkan konten diskusi terbaru di jendela modal di halaman web。
// @name:it           GreasyFork Assistente alle notifiche
// @description:it    Quando c’è una nuova risposta al tuo script o a una discussione a cui stai partecipando,Lo script visualizzerà il contenuto della discussione più recente in una finestra modale sulla pagina web。
// @name:ja           GreasyFork 通知アシスタント
// @description:ja    スクリプトまたは参加しているディスカッションに対して新しい返信があったとき,スクリプトは、Web ページ上のモーダル ウィンドウに最新のディスカッション コンテンツを表示します。。
// @name:ka           GreasyFork შეტყობინებების ასისტენტი
// @description:ka    როდესაც არის ახალი პასუხი თქვენს სკრიპტზე ან დისკუსიაზე, რომელშიც თქვენ მონაწილეობთ,სკრიპტი აჩვენებს უახლესი დისკუსიის შინაარსს მოდალურ ფანჯარაში ვებ გვერდზე。
// @name:ko           GreasyFork 알림 도우미
// @description:ko    귀하의 스크립트나 귀하가 참여하고 있는 토론에 대한 새로운 답변이 있는 경우,스크립트는 웹 페이지의 모달 창에 최신 토론 내용을 표시합니다.。
// @name:nl           GreasyFork Meldingsassistent
// @description:nl    Wanneer er een nieuw antwoord is op uw script of op een discussie waaraan u deelneemt,Het script geeft de nieuwste discussie-inhoud weer in een modaal venster op de webpagina。
// @name:nb           GreasyFork Varslingsassistent
// @description:nb    Når det kommer et nytt svar på manuset ditt eller på en diskusjon du deltar i,Skriptet vil vise det siste diskusjonsinnholdet i et modalt vindu på nettsiden。
// @name:pl           GreasyFork Asystent powiadomień
// @description:pl    Kiedy pojawi się nowa odpowiedź na Twój skrypt lub dyskusję, w której bierzesz udział,Skrypt wyświetli najnowszą treść dyskusji w oknie modalnym na stronie internetowej。
// @name:pt-BR        GreasyFork Assistente de Notificação
// @description:pt-BR Quando há uma nova resposta ao seu script ou a uma discussão da qual você está participando,O script exibirá o conteúdo da discussão mais recente em uma janela modal na página da web。
// @name:ro           GreasyFork Asistent de notificare
// @description:ro    Când există un nou răspuns la scenariul dvs. sau la o discuție la care participați,Scriptul va afișa cel mai recent conținut de discuție într-o fereastră modală pe pagina web。
// @name:ru           GreasyFork Помощник по уведомлениям
// @description:ru    Когда появляется новый ответ на ваш сценарий или на обсуждение, в котором вы участвуете.,Скрипт будет отображать последнее содержимое обсуждения в модальном окне на веб-странице.。
// @name:sk           GreasyFork Asistent upozornení
// @description:sk    Keď príde nová odpoveď na váš scenár alebo na diskusiu, ktorej sa zúčastňujete,Skript zobrazí najnovší obsah diskusie v modálnom okne na webovej stránke。
// @name:sr           GreasyFork Помоћник за обавештења
// @description:sr    Када постоји нови одговор на ваш сценарио или на дискусију у којој учествујете,Скрипта ће приказати најновији садржај дискусије у модалном прозору на веб страници。
// @name:sv           GreasyFork Aviseringsassistent
// @description:sv    När det kommer ett nytt svar på ditt manus eller på en diskussion du deltar i,Skriptet kommer att visa det senaste diskussionsinnehållet i ett modalt fönster på webbsidan。
// @name:th           GreasyFork ผู้ช่วยการแจ้งเตือน
// @description:th    เมื่อมีการตอบกลับสคริปต์ของคุณหรือการสนทนาที่คุณเข้าร่วมใหม่,สคริปต์จะแสดงเนื้อหาการสนทนาล่าสุดในหน้าต่างโมดอลบนหน้าเว็บ。
// @name:tr           GreasyFork Bildirim Asistanı
// @description:tr    Senaryonuza veya katıldığınız bir tartışmaya yeni bir yanıt geldiğinde,Komut dosyası, en son tartışma içeriğini web sayfasındaki kalıcı bir pencerede görüntüleyecektir.。
// @name:ug           GreasyFork ئۇقتۇرۇش ياردەمچىسى
// @description:ug    قوليازمىڭىزغا ياكى سىز قاتناشقان مۇنازىرىگە يېڭى جاۋاب كەلگەندە,بۇ قوليازما ئەڭ يېڭى مۇنازىرە مەزمۇنىنى تور بەتتىكى مودېل كۆزنەكتە كۆرسىتىدۇ。
// @name:uk           GreasyFork Помічник сповіщень
// @description:uk    Коли є нова відповідь на ваш сценарій або на обговорення, в якому ви берете участь,Сценарій відображатиме останній вміст обговорення в модальному вікні на веб-сторінці。
// @name:vi           GreasyFork Trợ lý thông báo
// @description:vi    Khi có câu trả lời mới cho tập lệnh của bạn hoặc cho cuộc thảo luận mà bạn đang tham gia,Tập lệnh sẽ hiển thị nội dung thảo luận mới nhất trong một cửa sổ phương thức trên trang web。
// @name:zh-TW        GreasyFork 通知助手
// @description:zh-TW 當你的腳本或你參與的討論有新回應時,腳本會在網頁上以模態視窗顯示最新的討論內容。
// @name:zh-HK        GreasyFork 通知助手
// @description:zh-HK 當你的腳本或你參與的討論有新回應時,腳本會在網頁上以模態視窗顯示最新的討論內容。
// @name:fr-CA        GreasyFork Assistant de notifications
// @description:fr-CA Lorsqu’il y a une nouvelle réponse à votre script ou à une discussion à laquelle vous participez,Le script affichera le dernier contenu de la discussion dans une fenêtre modale sur la page Web。
// @description       On GreasyFork, when there are new replies to your scripts or discussions you're involved in, the latest discussion content will be displayed on the webpage.
// @namespace         https://github.com/ChinaGodMan/UserScripts
// @version           1.5.0.4
// @icon              data:image/png;base64,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
// @iconbak           https://greasyfork.org/vite/assets/blacklogo96-CxYTSM_T.png
// @author            人民的勤务员 <china.qinwuyuan@gmail.com>
// @license           MIT
// @match             https://greasyfork.org/*
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_registerMenuCommand
// @require           https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.js
// @supportURL        https://github.com/ChinaGodMan/UserScripts/issues
// @homepageURL       https://github.com/ChinaGodMan/UserScripts
// @modified          2024-09-11 07:52:25
// ==/UserScript==
(function () {
    'use strict'
    var DEBUG = false
    const config = {
        reversal: GM_getValue('reversal', true),
        isInstalled: GM_getValue('Installed', false),//第一次不加载.
        lastUpdated: GM_getValue('lastUpdated', 0),//上次更新时间
        delay: GM_getValue('delay', '30m'), // 格式如下: 1h1m1s, 1h1s, 1m, 1s, 1m1s
        userId: null,//当前登录id
        userName: null,
        maxItem: GM_getValue('maxItem', 50),//访问时显示最大的信息数量
        saveDiscussions: GM_getValue('saveDiscussions', true)//可以不保存到本地,如保存到本地,未读每次都会弹出提示,反之保存在本地,未读只会弹出一次,除非有新回复,
    }
    const translate = (function () {
        const userLang = location.pathname.split('/')[1]
        const strings = {
            'en': {
                reversal: 'Reverse Notifications:',
                loadingquantity: 'Discussions loading quantity:',
                popup: 'Pop-up time:',
                Save: 'Save Discussions:',
                Setoptions: 'Set  Options',
                ScriptsDiscussions: 'ScriptsDiscussions',
                Discussions: 'Discussions',
                title: 'GreasyFork Discussion Watcher Settings',
                saveBtn: 'Save',
                cancelBtn: 'Cancel'
            }, 'zh-CN': {
                reversal: '反转通知信息:',
                loadingquantity: '讨论加载数量:',
                popup: '弹出时间:',
                Save: '保存讨论:',
                Setoptions: '设置选项',
                ScriptsDiscussions: '脚本讨论',
                Discussions: '讨论',
                title: 'GreasyFork 通知助手设置',
                saveBtn: '保存',
                cancelBtn: '取消'
            }, 'zh-TW': {
                reversal: '反轉通知信息:',
                loadingquantity: '討論加載數量:',
                popup: '彈出時間:',
                Save: '保存討論:',
                Setoptions: '設置選項',
                ScriptsDiscussions: '腳本討論',
                Discussions: '討論',
                title: 'GreasyFork 通知助手設置',
                saveBtn: '保存',
                cancelBtn: '取消'
            }, 'vi': {
                reversal: 'Lật ngược thông báo:',
                loadingquantity: 'Số lượng thảo luận đang tải:',
                popup: 'Thời gian pop-up:',
                Save: 'Lưu thảo luận:',
                Setoptions: 'Cài đặt tùy chọn',
                ScriptsDiscussions: 'Thảo luận kịch bản',
                Discussions: 'Thảo luận',
                title: 'Cài đặt Trình giám sát Thảo luận GreasyFork',
                saveBtn: 'Lưu',
                cancelBtn: 'Hủy'
            }, 'ko': {
                reversal: '알림 정보를 반전:',
                loadingquantity: '토론 로딩 수량:',
                popup: '팝업 시간:',
                Save: '토론 저장:',
                Setoptions: '옵션 설정',
                ScriptsDiscussions: '스크립트 토론',
                Discussions: '토론',
                title: 'GreasyFork 토론 감시기 설정',
                saveBtn: '저장',
                cancelBtn: '취소'
            }, 'ja': {
                reversal: '通知情報を反転:',
                loadingquantity: 'ディスカッションの読み込み数量:',
                popup: 'ポップアップ時間:',
                Save: 'ディスカッションを保存:',
                Setoptions: 'オプションを設定',
                ScriptsDiscussions: 'スクリプトディスカッション',
                Discussions: 'ディスカッション',
                title: 'GreasyForkディスカッションウォッチャーの設定',
                saveBtn: '保存',
                cancelBtn: 'キャンセル'
            }
        }
        return (id, lang = '') => {
            const selectedLang = lang || userLang
            return (strings[selectedLang] || strings.en)[id] || strings.en[id]
        }
        //  return id => (strings[userLang] || strings.en)[id] || strings.en[id];
    }())
    function setSettings() {
        const currentDelay = config.delay
        const currentSaveDiscussions = GM_getValue('saveDiscussions', true)
        const currentMax = config.maxItem
        const content = `
        <div style="text-align: left;">
            <label>${translate('popup')}</label>
            <input id="delay-input" type="text" value="${currentDelay}" placeholder="1h30m1s, 1s0m30s, 1h, 1m, 1s" style="width: 100%; margin-bottom: 10px;">
            <br>
            <label>${translate('loadingquantity')}</label>
            <input id="max-input" type="number" value="${currentMax}" placeholder="Enter max count" style="width: 100%; margin-bottom: 10px;">
                        <br>
            <label>${translate('Save')}</label>
            <input id="save-discussions" type="checkbox" ${currentSaveDiscussions ? 'checked' : ''}><br>
             <label>${translate('reversal')}</label>
            <input id="reversal-notifications" type="checkbox" ${config.reversal ? 'checked' : ''}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="https://greasyfork.org/users/${config.userId}/notifications" target="_blank">${config.userName}</a>
        </div>
    `
        Swal.fire({
            imageUrl: 'https://greasyfork.org/vite/assets/blacklogo96-CxYTSM_T.png',
            imageWidth: 96,
            imageHeight: 96,
            footer: '<div style="text-align: center;font-size: 1em;">Powered by <a href="https://www.github.com/ChinaGodMan">人民的勤务员</a></div>',
            title: translate('title'),
            html: content,
            focusConfirm: false,
            showCancelButton: true,
            confirmButtonText: translate('saveBtn'),
            cancelButtonText: translate('cancelBtn')
        }).then((result) => {
            if (result.isConfirmed) {
                const newDelay = document.getElementById('delay-input').value
                const newSaveDiscussions = document.getElementById('save-discussions').checked
                const reversalNotifications = document.getElementById('reversal-notifications').checked
                const newMaxItem = document.getElementById('max-input').value
                if (/^\d+(h|m|s)?(\d+(h|m|s)?)*$/.test(newDelay)) {
                    GM_setValue('delay', newDelay)
                    config.delay = newDelay
                } else {
                    Swal.fire('Error', 'The refresh time format is incorrect!', 'error')
                    return
                }
                GM_setValue('saveDiscussions', newSaveDiscussions)
                config.saveDiscussions = newSaveDiscussions
                GM_setValue('reversal', reversalNotifications)
                config.reversal = reversalNotifications
                if (newMaxItem && !isNaN(newMaxItem)) {
                    GM_setValue('maxItem', newMaxItem)
                    config.maxItem = newMaxItem
                } else {
                    Swal.fire('Error', 'Please enter a valid number for max item count!', 'error')
                    return
                }
            }
        })
    }
    GM_registerMenuCommand(translate('Setoptions'), setSettings)
    function timeToSeconds(timeStr) {
        let hours = 0, minutes = 0, seconds = 0
        const hoursMatch = timeStr.match(/(\d+)h/)
        const minutesMatch = timeStr.match(/(\d+)m/)
        const secondsMatch = timeStr.match(/(\d+)s/)
        if (hoursMatch) {
            hours = parseInt(hoursMatch[1], 10)
        }
        if (minutesMatch) {
            minutes = parseInt(minutesMatch[1], 10)
        }
        if (secondsMatch) {
            seconds = parseInt(secondsMatch[1], 10)
        }
        let totalSeconds = (hours * 3600) + (minutes * 60) + seconds
        return totalSeconds
    }
    function isUpdate() {
        const now = Math.floor(new Date().getTime() / 1000)
        const lastUpdated = config.lastUpdated
        const secondsDifference = now - lastUpdated
        if (secondsDifference > timeToSeconds(config.delay)) {
            GM_setValue('lastUpdated', now)
            console.log(`时间超过${config.delay} 进行更新`)
            return true
        }
        return DEBUG
    }
    function isMobileDevice() {
        return /Mobi|Android|iPhone|iPad|iPod|Windows Phone/i.test(navigator.userAgent)
    }
    function fetchAndDisplayDiscussions(urls) {
        if (DEBUG) GM_setValue('discussions', [])
        if (!isUpdate()) return
        let discussions = GM_getValue('discussions', [])
        let fetchPromises = []
        let itemCount = 0
        var modalHTML = `
<div id="discussion-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; visibility: hidden; align-items: center; justify-content: center; z-index: 1000;">
    <button id="close-button" style="align-self: flex-end; background-color: #ff5e5e; border: none; color: #fff; padding: 25px 25px; cursor: pointer; border-radius: 5px; font-family: Arial, sans-serif; font-size: 30px;">&times;</button>
<div id="modal-content" style="background: #fff; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); max-height: 80vh; overflow-y: auto; width: 100%; max-width: 100%; padding: 20px; font-family: Arial, sans-serif; display: flex; flex-direction: column;">
        <div class="discussion-list" style="margin-top: 10px;">
            <ul id="discussion-list-a" style="list-style-type: none; padding: 0; margin: 0;"></ul>
        </div>
    </div>
</div>
        `
        document.body.insertAdjacentHTML('beforeend', modalHTML)
        const discussionList = document.getElementById('discussion-list-a')
        const modalContent = document.getElementById('modal-content')
        const closeButton = document.getElementById('close-button')
        if (isMobileDevice()) {
            modalContent.appendChild(closeButton)
            closeButton.style.alignSelf = 'flex-end'
            closeButton.style.padding = '5px 5px'
        }
        var isFirst = false
        urls.forEach(([url, description]) => {
            fetchPromises.push(
                fetch(url)
                    .then(response => response.text())
                    .then(data => {
                        var parser = new DOMParser()
                        var doc = parser.parseFromString(data, 'text/html')
                        var elements = doc.querySelectorAll('.discussion-list > div > div')
                        var discussionsHTML = ''
                        var currentCount = 0
                        elements.forEach(function (element) {
                            var discussionTitle = element.querySelector('.discussion-title')
                            var relativeTimes = element.querySelectorAll('relative-time')
                            if (!discussionTitle || relativeTimes.length === 0) return
                            var discussionTitleHref = discussionTitle.getAttribute('href')
                            var latestRelativeTime = relativeTimes[relativeTimes.length - 1].getAttribute('datetime')
                            var existingDiscussion = discussions.find(function (disc) {
                                return disc.discussionTitleHref === discussionTitleHref
                            })
                            const userLinks = element.querySelectorAll('a.user-link')
                            if (existingDiscussion && existingDiscussion.relativeTime === latestRelativeTime) return
                            var discussionInfo = {
                                discussionTitleHref: discussionTitleHref,
                                relativeTime: latestRelativeTime,
                                lasteduserName: userLinks.length > 0 ? userLinks[userLinks.length - 1].textContent.trim() : null,//最后的发言人
                                lastedID: userLinks.length > 0 ? userLinks[userLinks.length - 1].href.match(/\/users\/(\d+)-/)[1] : null//ANCHOR - 获取最后的发言人ID
                            }
                            if (existingDiscussion) {
                                existingDiscussion.relativeTime = latestRelativeTime
                                existingDiscussion.lasteduserName = discussionInfo.lasteduserName
                            } else {
                                discussions.push(discussionInfo)
                            }
                            if (discussionInfo.lastedID === config.userId || discussionInfo.lasteduserName === config.userName) {
                                // 最新发言ID是自己,或用户名是自己,跳过
                                console.log('skip ')
                                return
                            }
                            var listItemHTML = '<div class="discussion-list-item">' + element.innerHTML + '</div>'
                            currentCount++
                            discussionsHTML += listItemHTML
                            itemCount++
                        })
                        if (currentCount > 0) {
                            discussionsHTML = `<center><h5>${isFirst ? `<div class="discussion-list-item"><a href="${url}" target="_blank">${description} (${currentCount})</a></div>` : `<a href="${url}" target="_blank">${description} (${currentCount})`}</a></h5></center>` + discussionsHTML
                            isFirst = true // 后续描述都以嵌套结构添加
                        }
                        discussionList.innerHTML += discussionsHTML
                    })
            )
        })
        Promise.all(fetchPromises).then(() => {
            if (itemCount === 0) return
            // 将讨论信息保存到 GM_setValue
            if (config.saveDiscussions) GM_setValue('discussions', discussions)
            if (!config.isInstalled) {
                console.log('首次安装时,不弹出:')
                GM_setValue('Installed', true)
                return
            }
            // 计算关闭按钮的位置,并动态设置
            closeButton.style.position = 'absolute'
            var closeButtonRect = closeButton.getBoundingClientRect()
            var modalContentRect = modalContent.getBoundingClientRect()
            if (isMobileDevice()) {
                closeButton.style.top = `${discussionList.offsetTop - closeButtonRect.height}px`
                closeButton.style.left = `${discussionList.right}px`
            } else {
                closeButton.style.top = `${modalContentRect.top - closeButtonRect.height}px`
                closeButton.style.left = `${modalContentRect.width / 2}px`
            }
            document.getElementById('discussion-modal').style.visibility = 'visible'
            // 设置所有链接在新窗口中打开
            var links = discussionList.querySelectorAll('a')
            links.forEach(function (link) {
                link.setAttribute('target', '_blank')
            })
            closeButton.addEventListener('click', function () {
                document.body.removeChild(document.getElementById('discussion-modal'))
            })
        }).catch(error => {
            console.error('无法获取讨论列表:', error)
        })
    }
    function getUserId() {
        const profileLinkElement = document.querySelector('#nav-user-info > span.user-profile-link > a')
        if (profileLinkElement) {
            const href = profileLinkElement.getAttribute('href')
            const match = href.match(/\/users\/(\d+)-/)
            if (match) {
                const userId = match[1]
                config.userId = userId
                const nameMatch = href.match(/\/users\/\d+-([^\/]+)$/)
                config.userName = nameMatch ? decodeURIComponent(nameMatch[1]) : ''
                return true
            } else {
                console.log('放弃操作,无法找到id')
                return false
            }
        } else {
            return false
        }
    }
    if (getUserId()) {
        fetchAndDisplayDiscussions([
            [`https://greasyfork.org/discussions?user=${config.userId}&read=unread&per_page=${config.maxItem}`, translate('Discussions')],
            [`https://greasyfork.org/discussions?me=script&read=unread&per_page=${config.maxItem}`, translate('ScriptsDiscussions')]//添加read=unread参数,只提取未读信息,此版本为最后版本,Greasyfork已经逐步支持站内通知.
        ])
    } else {
        console.log('没有登录,放弃操作')
    }

    const notificationList = document.querySelector('.notification-list')
    if (notificationList && config.reversal) {
        const items = Array.from(notificationList.children)
        items.reverse()
        notificationList.innerHTML = ''
        items.forEach(item => notificationList.appendChild(item))

    }
})()