yyawf

Under construction

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name              yyawf
// @description       Under construction
// @namespace         https://github.com/tiansh
// @version           0.0.8
// @match             *://*.weibo.com/*
// @noframes
// @run-at            document-start
// @nocompat
// @connect           miaopai.com
// @connect           sina.cn
// @connect           sina.com.cn
// @connect           sinaimg.cn
// @connect           sinajs.cn
// @connect           t.cn
// @connect           weibo.com
// @author            田生 http://weibo.com/tsh90
// @license           MPL-2.0
// @grant             unsafeWindow
// @grant             GM_getValue
// @grant             GM_setValue
// @grant             GM_addValueChangeListener
// @grant             GM_removeValueChangeListener
// ==/UserScript==

/*!
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/* eslint-env browser, greasemonkey */

const configKey = 'CONFIG', messageKey = 'yawf-' + Array(64).fill(0).map(() => (Math.random() * 16).toString(16)[0]).join('');
const payload = (Array(35).fill('\n').join('') + 'void(' + function (config, messageKey) {

  const $CONFIG = {}, yawfConfig = {};
  if (typeof window.$CONFIG !== 'undefined') {
    alert('脚本需要在页面打开前加载才能正常工作。当前注入加载时间过晚,请检查你是用的猴子版本是否受到支持。');
  }

  //#region utils
  const log = function (...args) {
    if (typeof args[0] === 'string') console.log('[yyawf] ' + args[0], ...args.slice(1));
    else console.log('[yyawf]', ...args);
  };
  const invokeContentScript = function (method, data) {
    const event = new CustomEvent(messageKey, { detail: { method, data } });
    document.dispatchEvent(event);
  };
  const kebabCase = function (word) {
    if (typeof word !== 'string') return word;
    return word.replace(/./g, (char, index) => {
      const lower = char.toLowerCase();
      if (char === lower || index === 0) return lower;
      else return '-' + lower;
    });
  };
  const wrapFunction = function (original, wrapped) {
    original.__raw__ = original;
    return new Proxy(original, {
      apply(target, thisArg, argumentsList) {
        return wrapped.apply(thisArg, argumentsList);
      },
    });
  };
  let $style = null;
  const addStyle = function (css) {
    if (!$style) {
      $style = document.createElement('style');
      document.head.appendChild($style);
    }
    $style.textContent += css + '\n';
  };
  //#endregion

  //#region 配置
  const getConfigBoolean = key => yawfConfig[key] === true;
  const getConfigStrings = key => Array.isArray(yawfConfig[key]) ? yawfConfig[key].filter(x => typeof x === 'string') : [];
  //#endregion

  //#region 初始化
  /** @type {Promise<App>} */
  const appReady = new Promise(resolve => {
    const prevDesc = Object.getOwnPropertyDescriptor(Object.prototype, '$cookies');
    const isVueApp = obj => obj?._uid === 0;
    Object.defineProperty(Object.prototype, '$cookies', {
      set(value) {
        if (isVueApp(this)) {
          resolve(this);
          delete Object.prototype.$cookies;
          if (prevDesc && typeof prevDesc.set === 'function') {
            Object.defineProperty(Object.prototype, '$cookies', prevDesc);
          }
        } else if (prevDesc && typeof prevDesc.set === 'function') {
          prevDesc.set.call(this, value);
        } else {
          Object.defineProperty(this, '$cookies', { value, writable: true, configurable: true, enumerable: true });
        }
      },
      configurable: true,
      enumerable: false,
    });
  });
  //#endregion

  //#region 过滤逻辑
  const shouldFilterFeedList = instance => {
    // 不过滤编辑历史弹框
    const getAncestor = ins => ins.parent ? [ins.parent, ...getAncestor(ins.parent)] : [];
    const ancestor = getAncestor(instance);
    if (ancestor.some(ins => kebabCase(ins.type?.name) === 'edit-history')) return false;
    return true;
  };
  const shouldFilterRCList = instance => {
    // 不过滤“@我的”页面
    const $route = instance.appContext.config.globalProperties.$route;
    if ($route.name === 'atWeibo') return false;
    return true;
  };

  const filterConfig = () => {
    return {
      keywords: getConfigStrings('filter::keywords'),
      removeAd: getConfigBoolean('cleanup::ad'),
    };
  };
  const feedFilter = function (feed) {
    const { keywords, removeAd } = filterConfig();
    if (removeAd && feed.content_auth === 5) return { action: 'hide', reason: '广告' };
    if (removeAd && feed.retweeted_status?.content_auth === 5) return { action: 'hide', reason: '广告' };
    const text = feed.text_raw || feed.text;
    const keywordMatch = keywords.find(keyword => text.includes(keyword));
    if (keywordMatch) return { action: 'hide', reason: `关键词“${keywordMatch}”` };
    const re_text = feed.retweeted_status?.text_raw || feed.retweeted_status?.text;
    const reKeywordMatch = re_text && keywords.find(keyword => re_text?.includes(keyword));
    if (reKeywordMatch) return { action: 'hide', reason: `关键词“${reKeywordMatch}”` };
    return { action: 'show' };
  };
  const commentFilter = function (comment) {
    const { keywords, removeAd } = filterConfig();
    const text = comment.text_raw || comment.text;
    const keywordMatch = keywords.find(keyword => text.includes(keyword));
    if (keywordMatch) return { action: 'hide', reason: `关键词“${keywordMatch}”` };
    return { action: 'show' };
  };
  const hotSearchFilter = function (hotSearch) {
    const { keywords, removeAd } = filterConfig();
    if (removeAd && hotSearch.rank == null) return { action: 'hide', reason: '广告' };
    if (removeAd && hotSearch.is_ad) return { action: 'hide', reason: '广告' };
    const text = hotSearch.word;
    const keywordMatch = keywords.find(keyword => text.includes(keyword));
    if (keywordMatch) return { action: 'hide', reason: `关键词“${keywordMatch}”` };
    return { action: 'show' };
  };
  //#endregion

  //#region 监听组件生命周期
  /** @type {Record<LifecycleName, Record<string, Function[]>>} */
  const lifecycleListeners = {};
  const addLifecycleListener = (lifecycles, componentName, listener) => {
    const item = { listener };
    lifecycles.match(/\S+/g).forEach(lifecycle => {
      lifecycleListeners[lifecycle] ??= {};
      lifecycleListeners[lifecycle][componentName] ??= [];
      lifecycleListeners[lifecycle][componentName].push(item);
    });
    return () => {
      lifecycles.match(/\S+/g).forEach(lifecycle => {
        const index = lifecycleListeners[lifecycle][componentName].indexOf(item);
        if (index !== -1) lifecycleListeners[lifecycle][componentName].splice(index, 1);
      });
    };
  };
  const runLifecycleListeners = (lifecycle, instance) => {
    if (!instance?.render) return;
    const name = kebabCase(instance.type.name || instance.type.__name || instance.type.__refName);
    const listenersForAny = lifecycleListeners[lifecycle]?.['*'] ?? [];
    const listenersForComponent = lifecycleListeners[lifecycle]?.[name] ?? [];
    const listeners = [...listenersForAny, ...listenersForComponent];
    listeners.forEach(item => {
      try { item.listener(instance); } catch (E) { console.error(E); }
    });
  };
  const wrapRenderContext = wrapper => {
    const wrapping = new WeakMap(), wrapped = new WeakSet();
    return instance => {
      if (wrapped.has(instance.render)) {
        return wrapping.get(instance.render);
      } else if (wrapping.has(instance.render)) {
        return (instance.render = wrapping.get(instance.render));
      }
      const wrappedRender = wrapper(instance);
      wrapping.set(instance.render, (instance.render = wrapFunction(instance.render, wrappedRender)));
      return wrappedRender;
    };
  };

  appReady.then(app => {
    Object.assign($CONFIG, window.$CONFIG);
    Object.assign(yawfConfig, config[$CONFIG.uid]);
  });

  appReady.then(app => {
    log('Vue 加载完成');
    app.mixin({
      beforeCreate() {
        runLifecycleListeners('beforeCreate', this?._);
      },
      created() {
        runLifecycleListeners('created', this?._);
      },
      mounted() {
        runLifecycleListeners('mounted', this?._);
      },
      beforeUpdate() {
        runLifecycleListeners('beforeUpdate', this?._);
      },
      updated() {
        runLifecycleListeners('updated', this?._);
      }
    });
  });
  //#endregion

  //#region DEBUG
  addLifecycleListener('mounted updated', '*', instance => {
    const el = instance.vnode.el;
    el.__vue__ = new WeakRef(instance);
  });
  //#endregion

  //#region 渲染增加组件名称
  const styleMapping = new Map();
  const updateStyleMapping = type => {
    const $style = type?.__cssModules?.$style;
    if (!$style) return;
    const prefix = kebabCase(type.name || type.__name || type.__refName);
    Object.keys($style).forEach(key => {
      styleMapping.set($style[key], '__yawf_' + prefix + '_' + key);
    });
  };
  const renderWithExtraInfo = wrapRenderContext(function (instance) {
    const properName = result => {
      if (!result) return 'unnamed-component';
      const name = kebabCase(result.type?.name || result.type?.__name || result.type?.__refName);
      if (name) return name + '--child';
      return properName(result.parent);
    };
    const render = instance.render;
    const childrenSlots = new WeakMap();
    const tagRenderResult = vnode => {
      if (typeof vnode !== 'object' || !vnode) return vnode;
      if (Array.isArray(vnode)) return vnode.map(child => tagRenderResult(child));
      // 更新 ClassName
      if (typeof vnode.props?.class === 'string') {
        const classList = vnode.props.class.split(' ').flatMap(klass => {
          if (!klass || klass.includes('__yawf_')) return [];
          const mapped = styleMapping.get(klass);
          if (mapped) return [klass, mapped];
          return [klass];
        }).filter(x => x);
        vnode.props.class = classList.join(' ');
      }
      // 标记事件处理
      if (vnode.props && typeof vnode.props === 'object') {
        Object.keys(vnode.props).forEach(eventName => {
          if (!eventName.startsWith('on') || eventName.startsWith('onUpdate')) return;
          const handler = vnode.props[eventName];
          const name = (typeof handler === 'function' ? [handler.name ?? 'function'] :
            Array.isArray(handler) ? handler.flatMap(item => typeof item === 'function' ? [(item.name ?? 'function')] : []) : []
          ).map(item => item.replace(/.*\s/, '')).join(' ');
          const key = kebabCase(eventName);
          vnode.props ??= {};
          vnode.props['__yawf_event_' + key.replace(/-/g, '_') + '__'] = name;
        });
      }
      // 递归内部元素
      if (vnode.children && Array.isArray(vnode.children)) {
        vnode.children.forEach(child => {
          tagRenderResult(child);
        });
      } else if (vnode.children && typeof vnode.children === 'object') {
        Object.keys(vnode.children).forEach(key => {
          const value = vnode.children[key];
          if (typeof value !== 'function') return;
          if (childrenSlots.has(value)) {
            vnode.children[key] = childrenSlots.get(value);
          } else {
            const wrapped = wrapFunction(value, function (...args) {
              const result = value.apply(this, args);
              tagRenderResult(result);
              return result;
            });
            vnode.children[key] = wrapped;
            childrenSlots.set(value, wrapped);
          }
        });
      }
      return vnode;
    };
    return function (...args) {
      const result = render.apply(this, args);
      tagRenderResult(result);
      // 增加 Component Name
      if (typeof result.type === 'string') instance.__renderTag = result.type;
      else instance.__renderTag = null;
      result.props ??= {};
      for (let ins = instance, lv = 0; ins && (!lv || typeof ins.__renderTag !== 'string'); ins = ins.parent, ++lv) {
        const name = kebabCase(ins.type.name || ins.type.__name || ins.type.__refName || properName(ins));
        result.props['__yawf_component_' + name + '__'] = ins.uid;
        const key = ins.vnode.key;
        if (typeof key === 'string' || typeof key === 'number' || typeof key === 'symbol') {
          result.props['__yawf_key_' + (lv || '') + '__'] = String(key);
        }
        if (ins !== ins.parent?.children?.[0]) break;
      }
      if (result.key && (typeof result.key === 'string' || typeof result.key === 'number' || typeof result.key === 'symbol')) {
        result.props['__yawf_key__'] = String(result.key);
      }
      return result;
    };
  });
  addLifecycleListener('beforeCreate', '*', instance => {
    if (instance.type.components && typeof instance.type.components === 'object') {
      Object.keys(instance.type.components).forEach(key => {
        const value = instance.type.components[key];
        value.__refName = key;
      });
    }
    updateStyleMapping(instance.type);
    renderWithExtraInfo(instance);
  });
  // 微博的操作按钮
  addLifecycleListener('mounted updated', 'feed-toolbar', instance => {
    const el = instance.vnode.el;
    [...el?.querySelectorAll?.('.__yawf_feed-toolbar__item')].forEach(item => {
      if (item.querySelector('[__yawf_component_woo-like__]')) item.setAttribute('__yawf_feed_toolbar__', 'like');
      else {
        const i = item.querySelector('i[class*="woo-font--"]');
        if (i) item.setAttribute('__yawf_feed_toolbar__', i.className.split(' ').find(i => i.includes('woo-font--')).split('--')[1]);
      }
    });
    const container = el.querySelector('.woo-box-flex').children;
    if (container[1]) container[1].setAttribute('__yawf_feed_toobar__extra__', '');
  });
  addLifecycleListener('mounted updated', 'icon-list', instance => {
    // 名字旁边的 Vip 等图标
    if (instance.type?.props?.icons) {
      const icons = instance.props.icons;
      const el = instance.vnode.el;
      if (Array.isArray(icons) && icons.length) {
        const children = el.children;
        icons.forEach((icon, index) => {
          children?.[index]?.setAttribute('__yawf_icon_list_item__', icon.type);
        })
      }
    }
    // 评论的操作按钮
    if (instance.type?.props?.iconsName) {
      const icons = instance.props.iconsName;
      const el = instance.vnode.el;
      if (Array.isArray(icons) && icons.length) {
        const children = el.children;
        icons.forEach((icon, index) => {
          children?.[index]?.setAttribute('__yawf_comment_toolbar_item__', icon.name);
        });
      }
    }
  });
  //#endregion

  //#region 设置入口
  addLifecycleListener('created', 'weibo-top-nav', instance => {
    instance.setupState?.configs.splice(-1, 0, {
      divider: true,
      href: '',
      name: '药方设置',
      type: 'yawf-config',
    });
    instance.ctx.configHandle = (function (_configHandle) {
      return function (...args) {
        const [index] = args;
        const type = instance.setupState.configs[index].type;
        if (type === 'yawf-config') {
          invokeContentScript('config', { profileId: window.$CONFIG.uid });
        } else _configHandle.apply(this, args);
      };
    }(instance.ctx.configHandle)).bind(null);
  });
  //#endregion

  //#region 广告
  addLifecycleListener('created', 'card-hot-search', instance => {
    instance.proxy.$watch(() => instance.data.bandList, function (bandList) {
      const status = bandList.map(item => hotSearchFilter(item));
      if (status.some(item => item.action === 'hide')) {
        status.forEach((item, index) => { if (item.action === 'hide') log(`热搜过滤(${item.reason})`, bandList[index]); });
        bandList.splice(0, bandList.length, ...bandList.filter((item, index) => status[index].action !== 'hide'));
      }
    }, { deep: true });
  });
  //#endregion

  //#region 消息流
  const filterExecutedItems = new Set();
  const filterFeedList = feedList => {
    if (!Array.isArray(feedList) || !feedList.length) return null;
    const filtered = [];
    let dirty = false;
    feedList.forEach(item => {
      if (filterExecutedItems.has(item.idstr)) {
        filtered.push(item);
        return;
      }
      filterExecutedItems.add(item.idstr);
      const status = feedFilter(item);
      if (status.action !== 'hide') filtered.push(item);
      else {
        dirty = true;
        log(`微博过滤:${status.reason}`, item);
      }
    });
    return dirty ? filtered : null;
  };
  const filterRepostCommentList = rcList => {
    if (!Array.isArray(rcList) || !rcList.length) return;
    const filtered = [];
    let dirty = false;
    rcList.forEach(item => {
      if (filterExecutedItems.has(item.idstr)) {
        filtered.push(item);
        return;
      }
      filterExecutedItems.add(item.idstr);
      const isRepost = item.retweeted_status;
      if (isRepost) {

        const status = feedFilter(item);
        if (status.action !== 'hide') filtered.push(item);
        else {
          dirty = true;
          log(`转发过滤:${status.reason}`, item);
        }
      } else {
        const status = commentFilter(item);
        if (status.action !== 'hide') {
          const subComments = item.comments;
          const filteredSubComments = [];
          if (Array.isArray(subComments)) subComments.forEach(subComment => {
            const status = commentFilter(subComment);
            if (status.action !== 'hide') filteredSubComments.push(subComment);
            else log(`二级评论过滤:${status.reason}`, subComment);
          });
          if (filteredSubComments.length === subComments.length) filtered.push(item);
          else filtered.push({ ...item, comments: filteredSubComments });
        } else {
          dirty = true;
          log(`评论过滤:${status.reason}`, item);
        }
      }
    });
    return dirty ? filtered : null;
  };
  addLifecycleListener('created', 'feed-scroll', instance => {
    if (!shouldFilterFeedList(instance)) return;
    instance.proxy.$watch(() => instance.props.data, feedList => {
      const filtered = filterFeedList(feedList);
      if (filtered) instance.emit('update:data', filtered);
    }, { deep: true });
  });
  addLifecycleListener('created', 'feed', instance => {
    if (!shouldFilterRCList(instance)) return;
    instance.proxy.$watch(() => instance.props.data.rcList, rcList => {
      const filtered = filterRepostCommentList(rcList);
      if (filtered) instance.emit('update:data', { ...instance.props.data, rcList: filtered });
    }, { deep: true });
  });
  addLifecycleListener('created', 'repost-coment-list', instance => {
    if (!shouldFilterRCList(instance)) return;
    instance.proxy.$watch(() => instance.data.list, list => {
      const filtered = filterRepostCommentList(list);
      if (filtered) instance.data.list.splice(0, instance.data.list.length, ...filtered);
    });
  });
  //#endregion

  //#region 热搜固顶
  addLifecycleListener('created', 'card-hot-search', instance => {
    if (!getConfigBoolean('cleanup::searchTop')) return;
    instance.proxy.$watch(() => instance.data.TopWords, function (TopWords) {
      if (TopWords?.length) {
        log('清理:热搜:置顶热搜', TopWords);
        TopWords.splice(0);
      }
    });
  }, { deep: true });
  //#endregion

  //#region 元素清理
  const cleanupStyles = {
    'cleanup::navHome': `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href="/"] { display: none; }`,
    'cleanup::navHot': `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href="/hot"] { display: none; }`,
    'cleanup::navTv': `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href="/tv"] { display: none; }`,
    'cleanup::navMessage': `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href="/at/weibo"] { display: none; }`,
    'cleanup::navProfile': `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href^="/u/"] { display: none; }`,
    'cleanup::navAvatar': [
      `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href^="/u/"] { text-decoration: none; }`,
      `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href^="/u/"] .__yawf_ctrls_avatarItem::before { font-family: woo; content: "\\e087"; color: var(--weibo-top-nav-icon-color); font-size: 30px; }`,
      `[__yawf_component_weibo-top-nav-base__] [__yawf_component_ctrls__] a[href^="/u/"] .__yawf_ctrls_avatarItem > *{ display: none; }`,
    ].join('\n'),
    'cleanup::navGame': `[__yawf_component_weibo-top-nav-base__] a[href*="game.weibo.com"] { display: none; }`,
    'cleanup::navDarkMode': `[__yawf_component_weibo-top-nav-base__] [__yawf_component_dark__] { display: none; }`,
    'cleanup::navAria': `[__yawf_component_aria__] { display: none; }`,
    'cleanup::hotSearch': `[__yawf_component_card-hot-search__] { display: none; }`,
    'cleanup::interested': `[__yawf_component_card-interested__] { display: none; }`,
    'cleanup::creatorCenter': `[__yawf_component_card-service__] { display: none; }`,
    'cleanup::sideFooter': `.wbpro-side-copy[__yawf_component_index__] { display: none; }`,
    'cleanup::service': `[__yawf_component_service-module__] { display: none; }`,
    'cleanup::followRecom': `[__yawf_component_recom-module__] { display: none; }`,
    'cleanup::feedEmptyTip': `.__yawf_home_emptyPic { display: none; }`,
    'cleanup::feedSource': `.__yawf_head-info_source { display: none; }`,
    'cleanup::feedFollow': `[__yawf_component_feed__] [__yawf_component_head__] [__yawf_component_follow-btn__] { display: none; }`,
    'cleanup::feedQr': `[__yawf_feed_toobar__extra__] { display: none; }`,
    'cleanup::feedRetweet': `[__yawf_feed_toolbar__="retweet"], [__yawf_comment_toolbar_item__="retweet"] { display: none; }`,
    'cleanup::feedLike': `[__yawf_feed_toolbar__="like"], [__yawf_comment_toolbar_item__="like"] { display: none; }`,
    'cleanup::translate': `.__yawf_translate_opt { display: none; }`,
    'cleanup::iconVerify': `.woo-icon-skinSpe, .woo-icon-skinSpe + .woo-icon-frames, .woo-icon--vred, .woo-icon--vorange, .woo-icon--vyellow, .woo-icon--vblue { display: none; }`,
    'cleanup::iconVip': `[__yawf_icon_list_item__="vip"], .__yawf_woo-icon_vipimg { display: none; }`,
    'cleanup::iconFans': `.__yawf_icon-fans_fans { display: none; }`,
    'cleanup::iconOther': `.__yawf_icon-list_custom { display: none; }`,
    'cleanup::profileHeader': [
      `[__yawf_component_profile-header__] .wbpro-pos { display: none; }`,
      `[__yawf_component_profile-header__] .__yawf_profile-header_box1 { padding-top: 40px; }`,
      `[__yawf_component_profile-header__] .__yawf_profile-header_avatar2 { margin-top: 0; }`,
      `[__yawf_component_profile-header__] .__yawf_profile-header_content { padding-left: 100px; margin-top: -50px; width: 0; }`,
      `[__yawf_component_profile-header__] .__yawf_profile-header_box3 { margin-left: 126px; }`,
    ],
  };
  appReady.then(() => {
    Object.keys(cleanupStyles).forEach(key => {
      if (getConfigBoolean(key)) addStyle(cleanupStyles[key]);
    });
  });
  //#endregion

} + '(' + [(function () {
  try {
    const config = GM_getValue(configKey);
    if (typeof config === 'object') return config || {};
    return {};
  } catch { return {}; }
}()), messageKey].map(x => JSON.stringify(x)) + '))');

try {
  if (typeof GM_getValue !== 'function') throw Error();
  if (typeof GM_setValue !== 'function') throw Error();
  if (typeof GM_addValueChangeListener !== 'function') throw Error();
  if (typeof GM_removeValueChangeListener !== 'function') throw Error();
} catch (e) {
  alert('脚本需要在页面加载前读取配置,当前猴子环境可能不支持相关功能。请检查你是用的猴子版本是否受到支持。');
}

const style = document.createElement('style');
document.addEventListener('DOMContentLoaded', () => {
  document.body.appendChild(style);
});

const handlers = {};
document.addEventListener(messageKey, event => {
  const { method, data } = event.detail;
  handlers[method]?.(data);
});

handlers.config = ({ profileId }) => {
  configDialog(profileId);
};
//#region 基本 UI 组件
const dialogStack = [];
/**
 * 显示一个对话框
 * @param {{
 *   id: string;
 *   title: string;
 *   render: Function;
 *   button?: { [type: string]: Function? }?,
 *   onShow?: (() => void);
 *   onHide?: (() => void);
 * }}
 */
const uiDialog = function ({ id, title, render, button, onShow, onHide }) {
  // 初始化 DOM
  const template = document.createElement('template');
  template.innerHTML = `
<div class="woo-box-flex woo-box-alignCenter woo-box-justifyCenter woo-modal-wrap woo-modal-an--pop-enter">
  <div class="woo-modal-main yawf-dialog">
    <i class="woo-font woo-font--cross yawf-dialog-close"></i>
    <div class="woo-box-flex woo-box-column woo-box-alignCenter woo-dialog-main" aria-modal="true" tabindex="0" role="alertdialog">
      <div class="woo-dialog-title yawf-dialog-title"></div>
      <div class="woo-dialog-body yawf-dialog-content">
      </div>
      <div class="woo-dialog-ctrl yawf-dialog-buttons">
        <button class="woo-button-main woo-button-line woo-button-default woo-button-m woo-button-round woo-dialog-btn yawf-dialog-button-cancel"><span class="woo-button-wrap"><span class="woo-button-content"></span></span></button>
        <button class="woo-button-main woo-button-flat woo-button-primary woo-button-m woo-button-round woo-dialog-btn yawf-dialog-button-ok"><span class="woo-button-wrap"><span class="woo-button-content"></span></span></button>
      </div>
    </div>
  </div>
  <div class="woo-modal-mask yawf-dialog-mask"></div>
</div>
`;
  const container = document.importNode(template.content.firstElementChild, true);
  const dialog = container.querySelector('.yawf-dialog') || container;
  dialog.id = id;
  const titleNode = dialog.querySelector('.yawf-dialog-title');
  const buttonCollectionNode = dialog.querySelector('.yawf-dialog-buttons');
  const okButton = dialog.querySelector('.yawf-dialog-button-ok');
  const cancelButton = dialog.querySelector('.yawf-dialog-button-cancel');
  const closeButton = dialog.querySelector('.yawf-dialog-close');
  const mask = container.querySelector('.yawf-dialog-mask');
  const contentNode = dialog.querySelector('.yawf-dialog-content');
  // 填入内容
  titleNode.textContent = title;
  titleNode.classList.add('woo-dialog-bar');
  okButton.textContent = '确定';
  cancelButton.textContent = '取消';
  closeButton.title = '关闭';
  render(contentNode, Object.assign({}, ...[
    { close: closeButton },
    button?.ok ? { ok: okButton } : {},
    button?.cancel ? { cancel: cancelButton } : {},
  ]));
  // 定位对话框的位置
  const lastPos = { x: 0, y: 0 };
  const setPos = function ({ x, y }) {
    const left = Math.min(Math.max(0, x), document.body.clientWidth - dialog.clientWidth - 2);
    const top = Math.min(Math.max(0, y), document.body.clientHeight - dialog.clientHeight - 2);
    if (left + 'px' !== dialog.style.left) dialog.style.left = left + 'px';
    if (top + 'px' !== dialog.style.top) dialog.style.top = top + 'px';
    return Object.assign(lastPos, { x: left, y: top });
  };
  // 网页滚动时维持在页面内
  const resetPos = () => { setPos(lastPos); };
  const dragMoveStart = (function mouseDrag() {
    const mouseStart = {};
    // 拖拽移动
    const dragMove = event => {
      setPos({
        x: event.screenX - mouseStart.x,
        y: event.screenY - mouseStart.y,
      });
    };
    // 拖拽结束
    const dragMoveDone = function () {
      document.removeEventListener('mousemove', dragMove);
      document.removeEventListener('mouseup', dragMoveDone);
      dialog.classList.remove('yawf-drag');
      if (dialog.releaseCapture) { dialog.releaseCapture(); }
    };
    // 开始拖拽
    const dragMoveStart = function (e) {
      Object.assign(mouseStart, {
        x: e.screenX - lastPos.x,
        y: e.screenY - lastPos.y,
      });
      document.addEventListener('mousemove', dragMove);
      document.addEventListener('mouseup', dragMoveDone);
      dialog.classList.add('yawf-drag');
    };
    return dragMoveStart;
  }());
  // 标题栏可以拖拽
  if (titleNode) {
    titleNode.addEventListener('mousedown', dragMoveStart);
  }
  // 背景遮罩
  // 响应鼠标
  if (!button?.ok && !button?.cancel) {
    buttonCollectionNode.parentNode.removeChild(buttonCollectionNode);
  } else {
    if (button.ok) okButton.addEventListener('click', event => {
      if (!event.isTrusted) return;
      button.ok();
    });
    else buttonCollectionNode.removeChild(okButton);
    if (button.cancel) cancelButton.addEventListener('click', event => {
      if (!event.isTrusted) return;
      button.cancel();
    });
    else buttonCollectionNode.removeChild(cancelButton);
  }
  closeButton.addEventListener('click', event => {
    if (!event.isTrusted) return;
    (button?.close ?? hide)();
  });
  mask.addEventListener('click', event => {
    if (!event.isTrusted) return;
    (button?.close ?? hide)();
  });
  // 响应按键
  /** @type {KeyboardEvent} */
  const keys = event => {
    if (!event.isTrusted) return;
    if (dialogStack[dialogStack.length - 1] !== dialog) return;
    const code = event.keyCode;
    if (code === 13 && button && button.ok) button.ok(event);
    else if (code === 27) {
      (button?.cancel ?? button?.close ?? hide)(event);
    } else return;
    event.stopPropagation();
    event.preventDefault();
  };
  const stopKeys = event => {
    event.stopPropagation();
  };
  // 关闭对话框
  const hide = function () {
    onHide?.();
    container.classList.add('woo-modal-an--pop-leave-to');
    document.removeEventListener('keydown', keys);
    container.removeEventListener('keypress', stopKeys);
    document.removeEventListener('scroll', resetPos);
    window.removeEventListener('resize', resetPos);
    setTimeout(function () { container.remove(); }, 200);
    dialogStack.splice(dialogStack.indexOf(dialog), 1);
  };
  const resetPosition = function ({ x, y } = {}) {
    if (x == null) x = (window.innerWidth - dialog.clientWidth) / 2;
    if (y == null) y = (window.innerHeight - dialog.clientHeight) / 2;
    setPos({ x, y });
  };
  // 显示对话框
  const show = function ({ x, y } = {}) {
    document.body.appendChild(container);
    resetPosition({ x, y });
    document.addEventListener('keydown', keys);
    container.addEventListener('keypress', stopKeys);
    document.addEventListener('scroll', resetPos);
    window.addEventListener('resize', resetPos);
    document.activeElement.blur();
    setTimeout(function () {
      container.classList.remove('woo-modal-an--pop-enter');
    }, 200);
    dialogStack.push(dialog);
    onShow?.();
  };
  return { hide, show, resetPosition, dom: dialog };
};
//#endregion

//#region 设置界面
const configDialog = function (profileId) {
  let contentDestroy = null;
  try {
    uiDialog({
      id: 'yawf-config',
      title: '药方 (YAWF) 设置',
      render: inner => {
        contentDestroy = renderConfig(inner, profileId, CONFIG_TEMPLATE);
      },
      onHide() {
        contentDestroy();
      }
    }).show();
  } catch (e) { console.log('Error while showing rule dialog %o', e); }
};
const renderConfig = (container, profileId, template) => {
  const domParser = new DOMParser();
  const dom = domParser.parseFromString('<yawf-config>' + template + '</yawf-config>', 'text/html');
  /** @param {HTMLElement} o * @param {HTMLElement} n */
  const moveSubTree = (n, o) => { while (o.firstChild) n.appendChild(o.removeChild(o.firstChild)); return n; };
  /** @type {
    (<TagName extends keyof HTMLElementTagNameMap>(tagName: TagName, className?: string, attributes?: Record<string, string>, children?: ArrayLike<HTMLElement>) => HTMLElementTagNameMap[TagName]) |
    (<TagName extends keyof HTMLElementTagNameMap>(tagName: TagName, attributes?: Record<string, string>, children?: ArrayLike<HTMLElement>) => HTMLElementTagNameMap[TagName]) |
    (<TagName extends keyof HTMLElementTagNameMap>(tagName: TagName, children?: ArrayLike<HTMLElement>) => HTMLElementTagNameMap[TagName])
  } */
  const r = (t, ...args) => {
    const d = document.createElement(t);
    if (typeof args[0] === 'string') d.className = args.shift();
    if (args[0] && !Array.isArray(args[0])) ((a => Object.keys(a).forEach(k => d.setAttribute(k, a[k])))(args.shift()));
    if (Array.isArray(args[0])) ((a => a.forEach(t => d.appendChild(t)))(args.shift()));
    return d;
  };
  const t = t => new Text(t);
  /** @type {HTMLElement} */
  const main = dom.querySelector('yawf-config').cloneNode(true);
  // 标签页
  [...main.querySelectorAll('yawf-tabs')].forEach(tabs => {
    const tabItems = [...tabs.children].filter(item => item.matches('yawf-tab'));
    const list = r('div', 'yawf-tab-list'), content = r('div', 'yawf-tab-content');
    const container = r('div', 'yawf-tabs', [list, content]);
    const highlight = index => {
      const updateHighlight = (item, i) => item.classList[i === index ? 'add' : 'remove']('yawf-current');
      [...list.children].forEach(updateHighlight);
      [...content.children].forEach(updateHighlight);
    };
    tabItems.forEach((tab, index) => {
      const button = list.appendChild(r('button', 'yawf-tab-item', [t(tab.getAttribute('name'))]));
      moveSubTree(content.appendChild(r('div', 'yawf-tab')), tab);
      button.addEventListener('click', () => highlight(index));
    }); tabs.parentNode.replaceChild(container, tabs);
    highlight(0);
  });
  // 设置读写
  const readConfig = () => {
    const config = GM_getValue(configKey);
    console.log('YAWF CONFIG READ', config);
    if (config && typeof config === 'object') {
      config[profileId] ??= {};
      return config;
    }
    return writeConfig({});
  };
  const writeConfig = (config) => {
    console.log('YAWF CONFIG WRITE', config);
    try { GM_setValue(configKey, config); return config; } catch (e) { }
    try { GM_setValue(configKey, {}); } catch (e) { }
    return {};
  };
  let config = readConfig();
  const getConfig = key => config[profileId]?.[key];
  const setConfig = (key, newValue) => {
    if (JSON.stringify(config[key]) === JSON.stringify(newValue)) return;
    writeConfig({
      ...config, [profileId]: {
        ...(config[profileId] ?? {}),
        [key]: newValue
      },
    });
  };
  const listenerId = GM_addValueChangeListener(configKey, (name, oldValue, newValue, remote) => {
    const oldConfig = config;
    const newConfig = (config = readConfig());
    const o = oldConfig[profileId], n = newConfig[profileId];
    const keys = new Set(Object.keys(o ?? {}).concat(Object.keys(n ?? {})));
    keys.forEach(key => {
      const ov = o?.[key] ?? null, nv = n?.[key] ?? null;
      if (JSON.stringify(ov) !== JSON.stringify(nv)) {
        configs.get(key)?.forEach(c => c.onChange(nv));
      }
    });
  });
  /** @type {Map<string, ConfigItem[]>} */
  const configs = new Map();
  class ConfigItem {
    /** @param {string} key @param {HTMLElement} dom @param {any} defaultValue */
    constructor(key, dom, defaultValue) {
      this.key = key;
      this.dom = dom;
      this.defaultValue = defaultValue;
      this.initRender(getConfig(this.key));
      if (!configs.has(key)) configs.set(key, []);
      configs.get(key).push(this);
    }
    get value() { return getConfig(this.key); }
    set value(newValue) { setConfig(this.key, newValue); return true; }
    initRender(value) { this.renderValue(value); }
    renderValue(value) { }
    onChange(value) { this.renderValue(value); }
  }
  class CheckboxConfigItem extends ConfigItem {
    renderValue(value) { this.dom.checked = value; }
  }
  class SelectConfigItem extends ConfigItem {
    renderValue(value) { this.dom.value = value; }
  }
  class StringsConfigItem extends ConfigItem {
    renderValue(value) {
      const items = [...this.dom.children].filter(item => item.matches('.yawf-collection-item'));
      const newItems = Array.isArray(value) ? value.map(val => {
        const exist = items.find(i => i.dataset.value === val);
        if (exist) return exist;
        return r('li', 'yawf-collection-item', { 'data-value': val }, [
          r('button', 'yawf-collection-item-remove', { type: 'button', title: '删除', 'data-key': this.key, 'data-value': val }, [r('i', 'woo-font woo-font--cross')]),
          r('div', 'yawf-collection-item-content', [this.renderItem(val)]),
        ]);
      }) : [];
      this.dom.innerHTML = '';
      newItems.forEach(item => this.dom.appendChild(item));
    }
    renderItem(val) { return t(val); }
  }
  // 勾选框
  [...main.querySelectorAll('yawf-checkbox')].forEach(checkbox => {
    const key = checkbox.getAttribute('key');
    const defaultValue = checkbox.getAttribute('default') === 'true';
    const label = r('label', 'yawf-checkbox-label', [
      r('span', 'yawf-checkbox-wrap', [
        r('input', 'yawf-checkbox', { 'data-key': key, type: 'checkbox' }),
        r('span', 'yawf-checkbox-icon'),
      ]),
    ]);
    const $input = label.querySelector('input'), $icon = label.querySelector('.yawf-checkbox-icon');
    $icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M0 0v16h16V0H0zm14.398 2.9a.667.667 0 0 1 .523 1.129l-8.686 8.604c-.26.258-.677.258-.937 0L1.408 8.78a.667.667 0 1 1 .939-.947l3.42 3.39 8.215-8.14a.667.667 0 0 1 .416-.182z"></path></svg>';
    moveSubTree(label, checkbox);
    checkbox.parentNode.replaceChild(label, checkbox);
    new CheckboxConfigItem(key, $input, defaultValue);
  });
  // 下拉框
  [...main.querySelectorAll('yawf-select')].forEach(select => {
    const key = select.getAttribute('key');
    const options = [...select.children].filter(o => o.matches('yawf-option'));
    const $select = r('select', 'yawf-select', { 'data-key': select.getAttribute('key') });
    const defaultValue = options.find(o => o.hasAttribute('default'))?.getAttribute('value');
    options.forEach(opt => {
      $select.appendChild(r('option', 'yawf-option', { value: opt.getAttribute('value') }), [t(opt.textContent)]);
    });
    select.parentNode.replaceChild($select, select);
    new SelectConfigItem(key, $select, defaultValue);
  });
  // 字符串列表
  [...main.querySelectorAll('yawf-strings')].forEach(strings => {
    const key = strings.getAttribute('key');
    const $ul = r('ul', 'yawf-collection-list', { 'data-key': key });
    strings.parentNode.replaceChild($ul, strings);
    new StringsConfigItem(key, $ul, []);
  });
  [...main.querySelectorAll('yawf-strings-input')].forEach(stringsInput => {
    const key = stringsInput.getAttribute('key');
    const $form = r('form', 'yawf-collection-form', { 'data-key': key }, [
      r('div', 'woo-input-wrap', [r('input', 'yawf-collection-input woo-input-main', { type: 'text' })]),
      r('button', 'yawf-collection-submit woo-button-main woo-button-line woo-button-primary woo-button-s woo-button-round', { type: 'submit' }, [t('添加')]),
    ]);
    stringsInput.parentNode.replaceChild($form, stringsInput);
  });
  // 界面
  [...main.querySelectorAll('yawf-rule')].forEach(rule => {
    const $rule = rule.parentNode.replaceChild(moveSubTree(r('div', 'yawf-rule'), rule), rule);
    if (rule.className) $rule.className += ' '+ rule.className;
  });
  [...main.querySelectorAll('yawf-group')].forEach(group => {
    const name = group.getAttribute('name');
    const $group = r('div', 'yawf-group', [
      r('div', 'yawf-group-title', [t(name)]),
      moveSubTree(r('div', 'yawf-group-content'), group),
    ]);
    if (group.className) $group.className += ' '+ group.className;
    group.parentNode.replaceChild($group, group);
  });
  // 事件处理
  const root = moveSubTree(container.appendChild(r('div', 'yawf-config')), main);
  root.addEventListener('change', e => {
    const el = e.target;
    const key = el?.dataset?.key;
    if (!key) return;
    if (el.matches('input.yawf-checkbox')) {
      setConfig(key, el.checked);
    } else if (el.matches('select.yawf-select')) {
      setConfig(key, el.value);
    }
  });
  root.addEventListener('click', e => {
    const el = e.target.closest('button');
    const key = el?.dataset?.key;
    if (!key) return;
    if (el?.matches('button.yawf-collection-item-remove')) {
      const value = el.dataset.value;
      setConfig(key, getConfig(key)?.filter(i => i !== value) || []);
    }
  })
  root.addEventListener('submit', e => {
    const el = e.target;
    const key = el?.dataset?.key;
    if (!key) return;
    if (el.matches('form.yawf-collection-form')) {
      const input = el.querySelector('input.yawf-collection-input');
      const value = input.value.trim();
      if (value) setConfig(key, (getConfig(key)?.filter(i => i !== value) || []).concat([value]));
      input.value = '';
      input.focus();
      e.preventDefault();
    }
  });
  return () => {
    GM_removeValueChangeListener(listenerId);
    configs.clear();
  };
};
const addCss = css => { style.textContent += '\n' + css + '\n'; };
addCss(`
.yawf-config { width: 800px; font-size: 14px; color: var(--w-main); background: var(--frame-background); }

.yawf-tabs { display: flex; width: 100%; height: 480px; overflow: hidden; }
.yawf-tab-list { width: 160px; padding: 20px 0; box-sizing: border-box; border-right: 1px solid var(--w-dividing-line); }
.yawf-tab-item { display: block; font: inherit; width: 100%; height: 40px; line-height: 40px; margin: 0; padding: 0 20px; background: none; border: none; cursor: pointer; text-align: left; color: var(--w-main); }
.yawf-tab-item:hover { background: var(--w-hover); }
.yawf-tab-item.yawf-current { font-weight: bold; color: var(--w-main); box-shadow: -2px 0 0 var(--w-brand) inset; background: var(--w-card-background); }
.yawf-tab-content { flex: 1; padding: 10px 20px 20px; max-height: 480px; overflow: auto; box-sizing: border-box; background: var(--w-card-background); }
.yawf-tab { display: none; min-height: 400px; color: var(--w-main); }
.yawf-tab.yawf-current { display: block; }

.yawf-group-title { display: block; font-weight: bold; margin: 15px 10px 5px; }
.yawf-rule { display: block; margin: 5px 20px; }
.yawf-compact-group .yawf-group-content { display: grid; grid-template-columns: 1fr 1fr 1fr; }

.yawf-checkbox-wrap { display: inline-block; position: relative; width: var(--w-checkbox-size); height: var(--w-checkbox-size); overflow: hidden; margin-right: 4px; vertical-align: baseline; }
.yawf-checkbox { position: absolute; left: -100px; }
.yawf-checkbox-icon { position: absolute; top: 0; left: 0; right: 0; bottom: 0; border: 1px solid var(--w-checkbox-border); color: var(--w-checkbox-check-color); }
.yawf-checkbox-icon svg { position: absolute; top: -1px; left: -1px; right: -1px; bottom: -1px; }
.yawf-checkbox:not(:checked) ~ .yawf-checkbox-icon svg { display: none; }
label:hover .yawf-checkbox-wrap .yawf-checkbox-icon,
.yawf-checkbox-wrap:hover .yawf-checkbox-icon { border-color: var(--w-checkbox-check-color); }

.yawf-collection-list { display: block; margin: 5px; padding: 0; list-style: none; }
.yawf-collection-item { padding: 0 5px 0 20px; min-width: 0; height: 20px; overflow: hidden; text-overflow: ellipsis; cursor: default; display: inline-block; position: relative; margin-left: 8px; border: 1px solid var(--w-b-line-default); border-radius: 2px; background: var(--w-card-background); line-height: 20px; vertical-align: middle; }
.yawf-collection-item-remove { display: block; position: absolute; top: 2px; left: 0; width: 20px; height: 20px; line-height: 20px; text-align: center; cursor: pointer; background: none; border: none; }
.yawf-collection-item-remove i { color: var(--w-fonticon); }
.yawf-collection-item-content { max-width: 500px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display: inline-block; padding-left: 2px; }
.yawf-collection-form { display: contents; align-items: center; margin: 5px 0; }
.yawf-collection-form .woo-input-wrap { flex: 1; margin-right: 4px; }
.yawf-collection-input { width: 100%; height: 20px; box-sizing: border-box; padding-left: var(--w-input-indent, 4px); padding-right: var(--w-input-indent, 4px); }
.yawf-collection-submit { padding: 4px 16px; margin: 0 4px; vertical-align: bottom; background: var(--w-b-flat-default-bg); border: 1px solid var(--w-b-line-default-border); color: var(--w-main); border-radius: 4px; cursor: pointer; }
.yawf-collection-submit:hover { background: var(--w-b-line-default-bg-hover); }
`);
//#endregion

//#region 配置界面
const CONFIG_TEMPLATE = `
<yawf-tabs>
  <yawf-tab name="微博过滤">
    <yawf-group name="过滤规则">
      <yawf-rule id="filter::keywords">
        <div>关键字 <yawf-strings-input key="filter::keywords" /></div>
        <div><yawf-strings key="filter::keywords" /></div>
      </yawf-rule>
      <p>此处的规则会被应用于微博和评论,关键字会匹配全文,包括话题和@的用户。</p>
    </yawf-group>
  </yawf-tab>
  <yawf-tab name="界面清理">
    <yawf-group name="顶栏" class="yawf-compact-group">
      <yawf-rule id="cleanup::navHome"><yawf-checkbox key="cleanup::navHome">首页</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navHot"><yawf-checkbox key="cleanup::navHot">推荐</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navTv"><yawf-checkbox key="cleanup::navTv">视频</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navMessage"><yawf-checkbox key="cleanup::navMessage">消息</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navProfile"><yawf-checkbox key="cleanup::navProfile">个人主页</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navProfile"><yawf-checkbox key="cleanup::navAvatar">头像</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navGame"><yawf-checkbox key="cleanup::navGame">游戏</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navDarkMode"><yawf-checkbox key="cleanup::navDarkMode">日/夜模式</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::navAria"><yawf-checkbox key="cleanup::navAria">无障碍</yawf-checkbox></yawf-rule>
    </yawf-group>
    <yawf-group name="侧栏" class="yawf-compact-group">
      <yawf-rule id="cleanup::searchTop"><yawf-checkbox key="cleanup::hotSearch">热搜</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::searchTop"><yawf-checkbox key="cleanup::searchTop">热搜置顶</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::interested"><yawf-checkbox key="cleanup::interested">可能感兴趣的人</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::creatorCenter"><yawf-checkbox key="cleanup::creatorCenter">创作者中心</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::sideFooter"><yawf-checkbox key="cleanup::sideFooter">底部链接</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::service"><yawf-checkbox key="cleanup::service">常用功能</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::followRecom"><yawf-checkbox key="cleanup::followRecom">关注推荐</yawf-checkbox></yawf-rule>
    </yawf-group>
    <yawf-group name="微博内容" class="yawf-compact-group">
      <yawf-rule id="cleanup::feedEmptyTip"><yawf-checkbox key="cleanup::feedEmptyTip">已刷完提示</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::feedSource"><yawf-checkbox key="cleanup::feedSource">来源</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::feedFollow"><yawf-checkbox key="cleanup::feedFollow">关注按钮</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::feedQr"><yawf-checkbox key="cleanup::feedQr">分享二维码</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::feedRetweet"><yawf-checkbox key="cleanup::feedRetweet">转发</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::feedLike"><yawf-checkbox key="cleanup::feedLike">点赞</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::translate"><yawf-checkbox key="cleanup::translate">翻译</yawf-checkbox></yawf-rule>
    </yawf-group>
    <yawf-group name="图标" class="yawf-compact-group">
      <yawf-rule id="cleanup::iconVerify"><yawf-checkbox key="cleanup::iconVerify">红橙黄蓝V</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::iconVip"><yawf-checkbox key="cleanup::iconVip">VIP</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::iconFans"><yawf-checkbox key="cleanup::iconFans">铁粉钻粉</yawf-checkbox></yawf-rule>
      <yawf-rule id="cleanup::iconOther"><yawf-checkbox key="cleanup::iconOther">其他</yawf-checkbox></yawf-rule>
    </yawf-group>
    <yawf-group name="个人主页" class="yawf-compact-group">
      <yawf-rule id="cleanup::profileHeader"><yawf-checkbox key="cleanup::profileHeader">顶图</yawf-checkbox></yawf-rule>
    </yawf-group>
    <yawf-group name="其他" class="yawf-compact-group">
      <yawf-rule id="cleanup::ad"><yawf-checkbox key="cleanup::ad">广告</yawf-checkbox></yawf-rule>
    </yawf-group>
  </yawf-tab>
  <yawf-tab name="关于">
    <p>yyawf 目前正在开发中,欢迎贡献代码</p>
    <p>Licensed under MPL-2.0</p>
  </yawf-tab>
</yawf-tabs>
`;
//#endregion

unsafeWindow.eval(payload);