Instagram - 為使用者新增備註(別名/標籤)

為使用者新增備註(別名/標籤)功能,以幫助識別和搜尋

目前為 2023-02-27 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name                Instagram - Add notes to the user
// @name:zh-CN          Instagram - 为用户添加备注(别名/标签)
// @name:zh-TW          Instagram - 為使用者新增備註(別名/標籤)
// @namespace           https://greasyfork.org/zh-CN/users/193133-pana
// @homepage            https://greasyfork.org/zh-CN/users/193133-pana
// @icon                data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBhcmlhLWxhYmVsbGVkYnk9Im5ld0ljb25UaXRsZSIgc3Ryb2tlPSJyZ2JhKDI5LDE2MSwyNDIsMS4wMCkiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgZmlsbD0ibm9uZSIgY29sb3I9InJnYmEoMjksMTYxLDI0MiwxLjAwKSI+IDx0aXRsZSBpZD0ibmV3SWNvblRpdGxlIj5OZXc8L3RpdGxlPiA8cGF0aCBkPSJNMTkgMTRWMjJIMi45OTk5N1Y0SDEzIi8+IDxwYXRoIGQ9Ik0xNy40NjA4IDQuMDM5MjFDMTguMjQxOCAzLjI1ODE3IDE5LjUwODIgMy4yNTgxNiAyMC4yODkyIDQuMDM5MjFMMjAuOTYwOCA0LjcxMDc5QzIxLjc0MTggNS40OTE4NCAyMS43NDE4IDYuNzU4MTcgMjAuOTYwOCA3LjUzOTIxTDExLjU4NTggMTYuOTE0MkMxMS4yMTA3IDE3LjI4OTMgMTAuNzAyIDE3LjUgMTAuMTcxNiAxNy41TDcuNSAxNy41TDcuNSAxNC44Mjg0QzcuNSAxNC4yOTggNy43MTA3MSAxMy43ODkzIDguMDg1NzkgMTMuNDE0MkwxNy40NjA4IDQuMDM5MjFaIi8+IDxwYXRoIGQ9Ik0xNi4yNSA1LjI1TDE5Ljc1IDguNzUiLz4gPC9zdmc+
// @version             6.0.5
// @description         Add notes (aliases/tags) for users to help identify and search
// @description:zh-CN   为用户添加备注(别名/标签)功能,以帮助识别和搜索
// @description:zh-TW   為使用者新增備註(別名/標籤)功能,以幫助識別和搜尋
// @license             GNU General Public License v3.0 or later
// @compatible          chrome
// @compatible          firefox
// @author              pana
// @match               *://*.instagram.com/*
// @require             https://gcore.jsdelivr.net/npm/[email protected]/minified/arrive.min.js
// @require             https://gcore.jsdelivr.net/gh/LightAPIs/greasy-fork-library@1b596ab3b97d13e3aa41dcdad1870b65944fda4d/Note_Obj.js
// @noframes
// @grant               GM_info
// @grant               GM_getValue
// @grant               GM_setValue
// @grant               GM_deleteValue
// @grant               GM_listValues
// @grant               GM_openInTab
// @grant               GM_addStyle
// @grant               GM_registerMenuCommand
// @grant               GM_unregisterMenuCommand
// @grant               GM_addValueChangeListener
// @grant               GM_removeValueChangeListener
// ==/UserScript==

(function () {
    'use strict';
    const UPDATED = '2023-02-27';
    const INS_ICON = {
        NOTE_BLACK: 'url(data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBhcmlhLWxhYmVsbGVkYnk9Im5ld0ljb25UaXRsZSIgc3Ryb2tlPSJyZ2IoMzgsIDM4LCAzOCkiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgZmlsbD0ibm9uZSIgY29sb3I9InJnYigzOCwgMzgsIDM4KSI+IDx0aXRsZSBpZD0ibmV3SWNvblRpdGxlIj5OZXc8L3RpdGxlPiA8cGF0aCBkPSJNMTkgMTRWMjJIMi45OTk5N1Y0SDEzIi8+IDxwYXRoIGQ9Ik0xNy40NjA4IDQuMDM5MjFDMTguMjQxOCAzLjI1ODE3IDE5LjUwODIgMy4yNTgxNiAyMC4yODkyIDQuMDM5MjFMMjAuOTYwOCA0LjcxMDc5QzIxLjc0MTggNS40OTE4NCAyMS43NDE4IDYuNzU4MTcgMjAuOTYwOCA3LjUzOTIxTDExLjU4NTggMTYuOTE0MkMxMS4yMTA3IDE3LjI4OTMgMTAuNzAyIDE3LjUgMTAuMTcxNiAxNy41TDcuNSAxNy41TDcuNSAxNC44Mjg0QzcuNSAxNC4yOTggNy43MTA3MSAxMy43ODkzIDguMDg1NzkgMTMuNDE0MkwxNy40NjA4IDQuMDM5MjFaIi8+IDxwYXRoIGQ9Ik0xNi4yNSA1LjI1TDE5Ljc1IDguNzUiLz4gPC9zdmc+)',
    };
    const INS_STYLE = `
    .note-obj-ins-background-box {
        display: inline-block;
        align-items: center;
        white-space: nowrap;
        border-radius: 50px;
        padding: 0px 10px;
        background-color: #336699;
        color: #fff;
    }
    .note-obj-ins-add-btn {
        background-image: ${INS_ICON.NOTE_BLACK};
        background-size: 24px;
        background-repeat: no-repeat;
        background-position: center;
        margin-left: 5px;
        cursor: pointer;
        width: 24px;
        height: 24px;
    }
    .note-obj-ins-homepage-btn {
        margin: 6px !important;
    }
    .note-obj-ins-homepage-btn:hover {
      opacity: 0.5;
    }
    .note-obj-ins-userpage-btn {
        margin-top: 2px;
    }
    .note-obj-ins-userpage-tag {
        display: block;
        font-size: 20px;
        margin-bottom: 20px;
        white-space: nowrap;
    }
    .note-obj-ins-font-bold {
        font-weight: bold;
    }
    .note-obj-veryins-blue-tag {
        background-color: #3c81df;
        color: #fff;
        display: inline-flex;
        align-items: center;
        padding: 0px 10px;
        white-space: nowrap;
        line-height: 100%;
        border-radius: 50px;
        padding: 2px 10px;
    }
    .note-obj-veryins-userpage-btn {
        display: inline-block;
        vertical-align: middle;
    }
    .note-obj-settings-frame-card label {
      color: #2f2f2f;
    }
    .note-obj-interface-dark .note-obj-settings-frame-card label {
      color: #fff;
    }
    .note-obj-group-frame-tbody input {
      color: #000;
    }`;
    const selector = {
        homepage: {
            article: '[role="main"] article',
            id: '._aaqt a',
            icon: 'span._aamz',
            commentId: '._ab8x .xt0psk2 a.notranslate',
            commentAt: '._ab8x ._aacl a.notranslate',
        },
        homepageStories: {
            id: '._aad6',
            idShell: 'li [role="menuitem"]',
        },
        homepageRecommend: {
            id: '._aak3 ._ab8x .xt0psk2 a',
        },
        userPage: {
            frame: '._aa_y',
            id: 'h2',
            bar: '.x8j4wrb',
            box: 'ul',
            common: 'span._aaai',
            suggest: '._acj1 a.notranslate',
            infoAt: '.notranslate',
            userName: '._aa_c > span',
        },
        watchList: {
            initialItem: '[role="dialog"] [aria-labelledby]',
            laterItem: '._aaei',
            id: '._ab8w a.notranslate',
        },
        stories: {
            id: 'a.notranslate',
            idShell: '._ac0o',
            cardId: '._afgd ._aacw',
        },
        dialog: {
            frame: '[role="dialog"] article',
            commentId: '._a9zc ._ab8w a, ._aacx._aacu a',
            commentAt: '._a9zs .notranslate',
        },
        request: {
            follow: '._aajc ._ab8x .xt0psk2 a',
        },
        suggest: {
            user: '._aa0- ._ab8x .xt0psk2 a',
        },
    };
    const noteObj = new Note_Obj({
        id: 'myInstagramNote',
        script: {
            author: {
                name: 'pana',
                homepage: 'https://greasyfork.org/zh-CN/users/193133-pana',
            },
            url: 'https://greasyfork.org/scripts/387871',
            updated: UPDATED,
            library: [
                {
                    name: 'arrive.js',
                    version: '2.4.1',
                    url: 'https://github.com/uzairfarooq/arrive',
                },
            ],
        },
        style: INS_STYLE,
        primaryColor: '#336699',
        settings: {
            replaceHomepageID: {
                type: 'checkbox',
                lang: {
                    en: 'Allow replacing user IDs on the home page',
                    zhHans: '允许替换首页上的用户 ID',
                    zhHant: '允許替換首頁上的使用者 ID',
                },
                default: true,
                event: instagramHomepageEvent,
            },
        },
        changeEvent: instagramChangeEvent,
    });
    function homepageNote(ele, changeId) {
        const user = Note_Obj.fn.queryAnchor(ele, selector.homepage.id);
        if (user) {
            const replaceHomepageID = noteObj.getOtherConfig().replaceHomepageID === true;
            const eleId = Note_Obj.fn.getIdFromUrl(user.href);
            if (!changeId || changeId === eleId) {
                noteObj.handler(eleId, user, undefined, {
                    add: replaceHomepageID ? undefined : 'sapn',
                    className: replaceHomepageID ? undefined : ['note-obj-ins-background-box'],
                });
            }
        }
    }
    function homepageCommentNote(ele, changeId) {
        for (const comment of Note_Obj.fn.queryAllAnchor(ele, selector.homepage.commentId, 'info')) {
            const commentId = Note_Obj.fn.getIdFromUrl(comment.href);
            if (!changeId || changeId === commentId) {
                noteObj.handler(commentId, comment);
            }
        }
    }
    function homepageCommentAtNote(ele, changeId) {
        const commentAtId = Note_Obj.fn.getIdFromUrl(ele.href);
        if (!changeId || changeId === commentAtId) {
            noteObj.handler(commentAtId, ele, undefined, {
                prefix: '@',
                title: true,
            });
        }
    }
    function dialogCommentNote(ele, chagneId) {
        const picCommentId = Note_Obj.fn.getIdFromUrl(ele.href);
        if (!chagneId || chagneId === picCommentId) {
            noteObj.handler(picCommentId, ele);
        }
    }
    function dialogCommentAtNote(ele, changeId) {
        if (!ele.classList.contains(selector.homepage.commentId.replace(/^\.|\s+.*$/g, ''))) {
            const picCommentAtId = Note_Obj.fn.getIdFromUrl(ele.href);
            if (!changeId || changeId === picCommentAtId) {
                noteObj.handler(picCommentAtId, ele, undefined, {
                    prefix: '@',
                    title: true,
                });
            }
        }
    }
    function homepageStoriesNote(ele, changeId) {
        const homepageStoriesId = Note_Obj.fn.getText(ele, selector.homepageStories.id);
        if (!changeId || changeId === homepageStoriesId) {
            ele.title = noteObj.getUserTag(homepageStoriesId);
        }
    }
    function anchorElementNote(ele, changeId) {
        const itemId = Note_Obj.fn.getIdFromUrl(ele.href);
        if (!changeId || changeId === itemId) {
            noteObj.handler(itemId, ele);
        }
    }
    function userPageNote(ele, changeId) {
        const userPageId = Note_Obj.fn.getText(ele, selector.userPage.id);
        const userPageBox = Note_Obj.fn.query(ele, selector.userPage.box);
        if (userPageId && userPageBox) {
            if (changeId) {
                if (changeId === userPageId) {
                    noteObj.handler(userPageId, ele, undefined, {
                        add: 'div',
                        after: userPageBox,
                        maskSecondaryColor: true,
                        offsetWidth: -20,
                        className: ['note-obj-ins-userpage-tag', 'note-obj-ins-font-bold'],
                    });
                }
            }
            else {
                const userNameText = Note_Obj.fn.getText(ele, selector.userPage.userName, 'warn');
                noteObj.handler(userPageId, ele, undefined, {
                    add: 'div',
                    after: userPageBox,
                    maskSecondaryColor: true,
                    offsetHeight: -20,
                    className: ['note-obj-ins-userpage-tag', 'note-obj-ins-font-bold'],
                }, userNameText);
            }
        }
    }
    function userPageCommonNote(ele, changeId) {
        for (const commonUser of Note_Obj.fn.queryAll(ele, selector.userPage.common, 'info')) {
            const commonUserId = commonUser.textContent?.trim();
            if (commonUserId) {
                if (!changeId || changeId === commonUserId)
                    noteObj.handler(commonUserId, commonUser, undefined, {
                        title: true,
                        notModify: true,
                    });
            }
        }
    }
    function userPageInfoAtNote(ele, changeId) {
        for (const infoAtUser of Note_Obj.fn.queryAllAnchor(ele, selector.userPage.infoAt, 'info')) {
            const infoAtUserId = Note_Obj.fn.getIdFromUrl(infoAtUser.href);
            if (!changeId || changeId === infoAtUserId) {
                noteObj.handler(infoAtUserId, infoAtUser, undefined, {
                    prefix: '@',
                    title: true,
                });
            }
        }
    }
    function storiesNote(ele, changeId) {
        itemNote(ele, selector.stories.id, changeId);
        Note_Obj.fn.docQueryAll(selector.stories.cardId).forEach(item => {
            const itemId = item.textContent?.trim() || '';
            if (!changeId || changeId === itemId) {
                noteObj.handler(itemId, item, undefined, {
                    notModify: true,
                    title: true,
                });
            }
        });
    }
    function watchListItemNote(ele, changeId) {
        itemNote(ele, selector.watchList.id, changeId);
    }
    function itemNote(ele, idSelector, changeId) {
        const item = Note_Obj.fn.queryAnchor(ele, idSelector);
        if (item) {
            const itemId = Note_Obj.fn.getIdFromUrl(item.href);
            if (!changeId || changeId === itemId) {
                noteObj.handler(itemId, item);
            }
        }
    }
    function instagramChangeEvent(changeId) {
        for (const article of Note_Obj.fn.docQueryAll(selector.homepage.article, 'none')) {
            homepageNote(article, changeId);
            homepageCommentNote(article, changeId);
            for (const commentAt of Note_Obj.fn.docQueryAllAnchor(selector.homepage.commentAt, 'none')) {
                homepageCommentAtNote(commentAt, changeId);
            }
            for (const picCommentUser of Note_Obj.fn.docQueryAllAnchor(selector.dialog.commentId, 'none')) {
                dialogCommentNote(picCommentUser, changeId);
            }
            for (const picCommentAt of Note_Obj.fn.docQueryAllAnchor(selector.dialog.commentAt, 'none')) {
                dialogCommentAtNote(picCommentAt, changeId);
            }
        }
        for (const homepageStories of Note_Obj.fn.docQueryAll(selector.homepageStories.idShell, 'none')) {
            homepageStoriesNote(homepageStories, changeId);
        }
        for (const homepageRecommend of Note_Obj.fn.docQueryAllAnchor(selector.homepageRecommend.id, 'none')) {
            anchorElementNote(homepageRecommend, changeId);
        }
        for (const userPage of Note_Obj.fn.docQueryAll(selector.userPage.frame, 'none')) {
            userPageNote(userPage, changeId);
            userPageCommonNote(userPage, changeId);
            userPageInfoAtNote(userPage, changeId);
        }
        for (const storiesShell of Note_Obj.fn.docQueryAll(selector.stories.idShell, 'none')) {
            storiesNote(storiesShell, changeId);
        }
        for (const initial of Note_Obj.fn.docQueryAll(selector.watchList.initialItem, 'none')) {
            watchListItemNote(initial, changeId);
        }
        for (const later of Note_Obj.fn.docQueryAll(selector.watchList.laterItem, 'none')) {
            watchListItemNote(later, changeId);
        }
        for (const dialog of Note_Obj.fn.docQueryAll(selector.dialog.frame, 'none')) {
            homepageNote(dialog, changeId);
            homepageCommentNote(dialog, changeId);
            for (const commentUser of Note_Obj.fn.docQueryAllAnchor(selector.dialog.commentId, 'none')) {
                dialogCommentNote(commentUser, changeId);
            }
            for (const commentAt of Note_Obj.fn.docQueryAllAnchor(selector.dialog.commentAt, 'none')) {
                dialogCommentAtNote(commentAt, changeId);
            }
        }
        for (const follow of Note_Obj.fn.docQueryAllAnchor(selector.request.follow, 'none')) {
            anchorElementNote(follow, changeId);
        }
        for (const suggestUser of Note_Obj.fn.docQueryAllAnchor(selector.suggest.user, 'none')) {
            anchorElementNote(suggestUser, changeId);
        }
        for (const suggest of Note_Obj.fn.docQueryAllAnchor(selector.userPage.suggest, 'none')) {
            anchorElementNote(suggest, changeId);
        }
    }
    function instagramHomepageEvent(newValue, oldValue) {
        for (const article of Note_Obj.fn.docQueryAll(selector.homepage.article)) {
            const articleUser = Note_Obj.fn.queryAnchor(article, selector.homepage.id);
            if (articleUser) {
                const articleUserId = Note_Obj.fn.getIdFromUrl(articleUser.href);
                noteObj.handler(articleUserId, articleUser, undefined, {
                    add: oldValue ? undefined : 'span',
                    className: oldValue ? undefined : ['note-obj-ins-background-box'],
                    title: oldValue,
                    restore: true,
                });
                noteObj.handler(articleUserId, articleUser, undefined, {
                    add: newValue ? undefined : 'span',
                    className: newValue ? undefined : ['note-obj-ins-background-box'],
                    title: newValue,
                });
            }
        }
    }
    function initInstagram() {
        const arriveOption = {
            fireOnAttributesModification: true,
            existing: true,
        };
        document.body.arrive(selector.homepage.article, arriveOption, article => {
            const homepageIcon = Note_Obj.fn.query(article, selector.homepage.icon);
            const articleUserId = Note_Obj.fn.getUrlId(article, selector.homepage.id);
            if (homepageIcon && articleUserId) {
                homepageIcon.insertAdjacentElement('beforebegin', noteObj.createNoteBtn(articleUserId, undefined, ['note-obj-ins-add-btn', 'note-obj-ins-homepage-btn'], 'span'));
            }
            homepageNote(article);
            homepageCommentNote(article);
            article.arrive(selector.homepage.commentAt, arriveOption, commentAt => {
                homepageCommentAtNote(commentAt);
            });
            article.arrive(selector.dialog.commentId, arriveOption, picCommentUser => {
                dialogCommentNote(picCommentUser);
            });
            article.arrive(selector.dialog.commentAt, arriveOption, picCommentAt => {
                dialogCommentAtNote(picCommentAt);
            });
        });
        document.body.arrive(selector.homepageStories.idShell, arriveOption, homepageStories => {
            homepageStoriesNote(homepageStories);
        });
        document.body.arrive(selector.homepageRecommend.id, arriveOption, homepageRecommend => {
            anchorElementNote(homepageRecommend);
        });
        document.body.arrive(selector.userPage.frame, arriveOption, userPage => {
            const userPageBar = Note_Obj.fn.query(userPage, selector.userPage.bar);
            const userPageId = Note_Obj.fn.getText(userPage, selector.userPage.id);
            if (userPageBar && userPageId) {
                const userNameText = Note_Obj.fn.getText(userPage, selector.userPage.userName, 'info');
                userPageBar.after(noteObj.createNoteBtn(userPageId, userNameText, ['note-obj-ins-add-btn', 'note-obj-ins-userpage-btn']));
            }
            userPageNote(userPage);
            userPageCommonNote(userPage);
            userPageInfoAtNote(userPage);
        });
        document.body.arrive(selector.stories.idShell, arriveOption, storiesShell => {
            storiesNote(storiesShell);
            const stories = Note_Obj.fn.queryAnchor(storiesShell, selector.stories.id);
            if (stories) {
                const userIdChange = new MutationObserver(() => {
                    const newUserId = Note_Obj.fn.getIdFromUrl(stories.href);
                    if (noteObj.judgeUsers(newUserId)) {
                        noteObj.handler(newUserId, stories);
                    }
                    else {
                        noteObj.handler(newUserId, stories, undefined, {
                            restore: true,
                        });
                    }
                });
                userIdChange.observe(stories, {
                    attributeFilter: ['href'],
                });
            }
        });
        document.body.arrive(selector.watchList.initialItem, arriveOption, initial => {
            watchListItemNote(initial);
        });
        document.body.arrive(selector.watchList.laterItem, arriveOption, later => {
            watchListItemNote(later);
        });
        document.body.arrive(selector.dialog.frame, arriveOption, dialog => {
            const homepageIcon = Note_Obj.fn.query(dialog, selector.homepage.icon);
            const dialogUserId = Note_Obj.fn.getUrlId(dialog, selector.homepage.id);
            if (homepageIcon && dialogUserId) {
                homepageIcon.insertAdjacentElement('beforebegin', noteObj.createNoteBtn(dialogUserId, undefined, ['note-obj-ins-add-btn', 'note-obj-ins-homepage-btn'], 'span'));
            }
            homepageNote(dialog);
            homepageCommentNote(dialog);
            dialog.arrive(selector.dialog.commentId, arriveOption, commentUser => {
                dialogCommentNote(commentUser);
            });
            dialog.arrive(selector.dialog.commentAt, arriveOption, commentAt => {
                dialogCommentAtNote(commentAt);
            });
        });
        document.body.arrive(selector.request.follow, arriveOption, follow => {
            anchorElementNote(follow);
        });
        document.body.arrive(selector.suggest.user, arriveOption, suggestUser => {
            anchorElementNote(suggestUser);
        });
        document.body.arrive(selector.userPage.suggest, arriveOption, suggest => {
            anchorElementNote(suggest);
        });
    }
    initInstagram();
})();