Skip to content

油猴介绍

油猴(Tampermonkey)是一款非常流行的浏览器扩展程序,它允许用户在网页上运行自定义的 JavaScript 程序,这些程序被称为“用户脚本”或“油猴脚本”。以下是对油猴的详细介绍:

一、主要功能

  1. 脚本管理:油猴插件是一个脚本管理工具,用户可以通过它搜索、安装、管理各种用户脚本。这些脚本能够改变网站的行为和外观,增强或添加新的功能。
  2. 广告拦截:油猴脚本可以轻松过滤网络广告,让浏览环境更清爽。
  3. 网页增强:通过安装相应的脚本,用户可以添加实用功能,例如下载器、自动填写表单和自定义主题等。
  4. 社交媒体优化:油猴脚本能够提升社交媒体体验,例如群组管理、自动点赞和内容过滤等。
  5. 隐私保护:一些油猴脚本还可以保护用户的在线隐私,例如跟踪器拦截和 cookie 管理。
  6. 数据转换:用户可以将网页数据转换为不同格式,例如 csv、json 和 xml 等。

二、特色功能(以手机版为例)

  1. 日期倒数:可以将重要节日添加到产品中,清楚地看到距离该日期还有多长时间。
  2. 打卡签到:可以有效地提高用户的自律性。
  3. 护眼功能:浏览网页时开启该功能,可以改变网页背景色为豆沙绿,有效减少白色背景对用户眼睛的刺激。
  4. 界面简洁:产品没有弹窗广告、信息流等,基本不会打扰用户,回归浏览本身。
  5. UI 设计:符合大众审美,且会持续优化更新。

三、兼容性

油猴支持多种浏览器,包括 Chrome、Firefox、Edge、Safari 等。用户可以根据自己的需求选择适合自己浏览器的版本进行安装和使用。

四、使用教程

  1. 安装插件:打开Tampermonkey 官网,安装对应版本的插件。安装好后自动启用,扩展区域会出现相应扩展图标。
  2. 安装脚本:打开Greasyfork,搜索需要的脚本,输入大致名称或用途即可。进入安装页面,点击“安装此脚本”->“安装”,完成脚本安装。脚本安装完成后,只会在有效的网站上自动启用。
  3. 管理脚本:在浏览器扩展栏可以快捷查看和管理脚本。进入“管理面板”可以管理已安装的脚本,包括开关、更新、编辑、删除等操作。也可以添加自己编写的脚本。

自己写一个

百度一下改为百度两三下

js
// ==UserScript==
// @name         百度两下
// @namespace    http://tampermonkey.net/
// @version      2024-12-01
// @description  百度两三下
// @author       happyfe呀
// @match        https://www.baidu.com/*
// @match        https://www.hao123.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=baidu.com
// @grant        none
// ==/UserScript==

(function () {
  'use strict';
  var baidu = document.getElementById('su');
  var baidu2 = document.getElementsByClassName('button-hook s_btn')[0];
  if (!!baidu2) {
    baidu2.value = '百度三下';
  }
  if (!!baidu) {
    baidu.value = '百度两下';
    baidu.style.background = 'pink';
    // baidu.style.display="none"
  }
})();

油猴脚本(Tampermonkey Scripts)中的元注释(Meta Block)是一种特殊的注释,它位于脚本的开头部分,用于定义脚本的一些元数据和设置。这些元数据包括脚本的名称、版本、描述、作者、许可证、匹配网址等信息。元注释对于油猴插件来说非常重要,因为它决定了脚本的行为和适用范围。

  • @name:脚本的名称。
  • @namespace:脚本的命名空间,通常使用油猴的官方命名空间。
  • @version:脚本的版本号。
  • @description:脚本的描述,简要说明脚本的功能和用途。
  • @author:脚本的作者。
  • @match:指定脚本适用的网址模式,使用通配符(*)来匹配多个网址。
  • @grant:指定脚本需要的油猴 API 权限,none表示不需要任何特殊权限。
  • @license:脚本的许可证类型,常见的许可证类型包括 MIT、GPL 等。

元注释中的这些指令对于油猴插件来说非常重要,它们决定了脚本如何被识别、加载和执行。例如,@match指令告诉油猴插件哪些网页应该加载和执行这个脚本,而@grant指令则告诉油猴插件这个脚本需要哪些 API 权限。

请注意,元注释必须位于脚本的最开头部分,并且紧跟在// ==UserScript==// ==/UserScript==之间。如果元注释的格式不正确或位置不正确,油猴插件可能无法正确识别和执行脚本。

CSDN 免登录复制

js
// ==UserScript==
// @name         CSDN免登录代码复制
// @namespace    http://tampermonkey.net/
// @version      2024-11-30
// @description  CSDN免登录代码复制,啦啦啦
// @author       happyfe呀
// @match        https://blog.csdn.net/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function () {
  'use strict';
  function addCss(code, id) {
    const style = document.createElement('style');
    const css = document.createTextNode(code);
    style.setAttribute('data-id', id || 'codebox-css');
    style.appendChild(css);
    document.head.appendChild(style);
  }

  function copyCodeCssFunc() {
    const css = `
    #content_views pre,
    #content_views pre code {
      -webkit-touch-callout: auto !important;
      -webkit-user-select: auto !important;
      -khtml-user-select: auto !important;
      -moz-user-select: auto !important;
      -ms-user-select: auto !important;
      user-select: auto !important;
    }`;
    addCss(css);
  }

  function copyCodeFunc() {
    copyCodeCssFunc();
    // 内容区开启复制
    var content_views = document.querySelector('#content_views');
    content_views.replaceWith(content_views.cloneNode(true));

    // 功能一: 修改复制按钮,支持一键复制
    const buttons = document.querySelectorAll('.hljs-button');

    buttons.forEach((btn) => {
      // 更改标题
      btn.dataset.title = '复制';

      // 移除点击事件
      btn.setAttribute('onclick', '');

      // 克隆按钮
      var elClone = btn.cloneNode(true);

      // 替回按钮
      btn.parentNode.replaceChild(elClone, btn);

      // 重新添加点击事件
      elClone.addEventListener('click', (e) => {
        // 实现复制
        const target = e.target;
        const parentPreBlock = target.closest('pre');
        const codeBlock = parentPreBlock.querySelector('code');

        navigator.clipboard.writeText(codeBlock.innerText);
        console.log(codeBlock.innerText);

        target.dataset.title = '复制成功';
        setTimeout(() => {
          target.dataset.title = '复制';
        }, 1000);
        e.stopPropagation();
        e.preventDefault();
      });
    });
  }

  copyCodeFunc();
})();

京东自动申请价保

js
// ==UserScript==
// @name         京东
// @namespace    http://tampermonkey.net/
// @version      2024-12-07
// @description  try to take over the world!
// @author       happyfe呀
// @match        https://pcsitepp-fm.jd.com/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jd.com
// @grant        none
//@require       https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js
// ==/UserScript==

(function () {
  'use strict';

  $(function () {
    console.log('jquery 加载完成');
    setInterval(function () {
      $('.co-th').each(function () {
        var aBtn = $(this).find('.btn').find('a');
        var text = aBtn.text().trim();
        if (text == '申请价保') {
          aBtn.click();
        }
      });
      window.location.reload();
    }, 10 * 60 * 1000);
  });
})();

fetch 发送网络请求

js
// ==UserScript==
// @name         获取B站关注数
// @namespace    http://tampermonkey.net/
// @version      2024-12-07
// @description  try to take over the world!
// @author       You
// @match        https://space.bilibili.com/371312151
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  async function getFollowingCount(vmid) {
    const url = `https://api.bilibili.com/x/relation/stat?vmid=${vmid}`;
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }
      const data = await response.json();
      if (data.code === 0) {
        return data.data;
      } else {
        throw new Error(`API error! Message: ${data.message}`);
      }
    } catch (error) {
      console.error('Error fetching following count:', error);
      return null;
    }
  }

  const vmid = 371312151; // 替换为实际的用户UID
  getFollowingCount(vmid).then((data) => {
    if (data !== null) {
      console.log(`用户 ${vmid} 的粉丝数为: ${data.follower}`);
      console.log(`用户 ${vmid} 的关注数为: ${data.following}`);
    }
  });
})();

jquery ajax 发送网络请求

js
// ==UserScript==
// @name         获取B站关注数
// @namespace    http://tampermonkey.net/
// @version      2024-12-07
// @description  try to take over the world!
// @author       You
// @match        https://space.bilibili.com/371312151
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
//  @require     https://code.jquery.com/jquery-3.7.1.min.js
// ==/UserScript==

(function () {
  'use strict';

  function getFollowingCount(vmid) {
    const url = `https://api.bilibili.com/x/relation/stat?vmid=${vmid}`;
    $.ajax({
      url: url,
      method: 'GET',
      dataType: 'json',
      success: function (data) {
        if (data.code === 0) {
          console.log(`用户 ${vmid} 的粉丝数为啦啦啦: ${data.data.follower}`);
          console.log(`用户 ${vmid} 的关注数为啦啦啦: ${data.data.following}`);
        } else {
          console.error(`API error! Message: ${data.message}`);
        }
      },
      error: function (xhr, status, error) {
        console.error('Error fetching following count:', error);
      },
    });
  }

  const vmid = 371312151; // 替换为实际的用户UID
  getFollowingCount(vmid);
})();

GM_xmlhttpRequest 解决跨域

js
// ==UserScript==
// @name         获取B站关注数
// @namespace    http://tampermonkey.net/
// @version      2024-12-07
// @description  try to take over the world!
// @author       You
// @match        https://www.happyfe.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
  'use strict';

  function getFollowingCount(vmid) {
    const url = `https://api.bilibili.com/x/relation/stat?vmid=${vmid}`;
    GM_xmlhttpRequest({
      method: 'GET',
      url: url,
      responseType: 'json',
      onload: function (response) {
        if (response.status >= 200 && response.status < 300) {
          const data = response.response;
          if (data.code === 0) {
            console.log(
              `用户 ${vmid} 的粉丝数happyfe为: ${data.data.follower}`
            );
            var span = document.createElement('span');
            span.innerText = `B站粉丝数: ${data.data.follower}`;
            document.querySelector('.clip').appendChild(span);
            console.log(
              `用户 ${vmid} 的关注数happyfe为: ${data.data.following}`
            );
          } else {
            console.error(`API error! Message: ${data.message}`);
          }
        } else {
          console.error(`HTTP error! Status: ${response.status}`);
        }
      },
      onerror: function (error) {
        console.error('Error fetching following count:', error);
      },
    });
  }

  const vmid = 371312151; // 替换为实际的用户UID
  getFollowingCount(vmid);
})();

fetch 网络请求拦截

html
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    我运行了
    <div id="json"></div>
  </body>
  <script src="test.js"></script>
</html>
js
//test.js
fetch('http://localhost:3002/api/query')
  .then((response) => response.json())
  .then((res) => {
    console.log(res);
    const dom = document.getElementById('json');
    dom.innerText = res.data;
  });
js
// server.js
const Koa = require('koa');
const Router = require('koa-router');
const cors = require('koa-cors');

const app = new Koa();
const router = new Router();

// 跨域
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
  ctx.set(
    'Access-Control-Allow-Headers',
    'Content-Type, Content-Length, Authorization, Accept, X-Requested-With'
  );
  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  if (ctx.method === 'OPTIONS') {
    ctx.body = 200;
  } else {
    await next();
  }
});

router.get('/api/query', async (ctx) => {
  ctx.body = {
    data: [1, 2, 3, 4],
    code: 0,
    msg: '成功',
  };
});

// 使用路由中间件
app.use(router.routes());

// 启动服务器
app.listen(3002, () => {
  console.log('Server is running on http://localhost:3002');
});
js
// ==UserScript==
// @name         fetch 拦截
// @namespace    http://tampermonkey.net/
// @version      2024-12-08
// @description  try to take over the world!
// @author       You
// @match        http://127.0.0.1:5500/index.html
// @icon         https://www.google.com/s2/favicons?sz=64&domain=0.1
// @grant unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function () {
  console.log(window.unsafeWindow);
  const originFetch = fetch;
  console.log(originFetch);
  window.unsafeWindow.fetch = (url, options) => {
    return originFetch(url, options).then(async (response) => {
      console.log(url);
      if (url === 'http://localhost:3002/api/query') {
        const responseClone = response.clone();
        let res = await responseClone.json();
        res.data.push('油猴脚本修改数据happyfe');
        const responseNew = new Response(JSON.stringify(res), response);
        return responseNew;
      } else {
        return response;
      }
    });
  };
})();

xhr 网络请求拦截

js
//test.js
setTimeout(() => {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'http://localhost:3002/api/query');
  xhr.send();
  xhr.onload = function () {
    const res = JSON.parse(this.responseText);
    console.log(res, 777);
    const dom = document.getElementById('json');
    dom.innerText = res.data;
  };
}, 0);
js
// ==UserScript==
// @name        xhr 拦截
// @namespace    http://tampermonkey.net/
// @version      2024-12-08
// @description  try to take over the world!
// @author       You
// @match        http://127.0.0.1:5500/index.html
// @icon         https://www.google.com/s2/favicons?sz=64&domain=0.1
// @grant        none
// @run-at document-start
// ==/UserScript==

(function () {
  'use strict';

  const originOpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function (_, url) {
    if (url === 'http://localhost:3002/api/query') {
      const xhr = this;
      const getter = Object.getOwnPropertyDescriptor(
        XMLHttpRequest.prototype,
        'response'
      ).get;
      Object.defineProperty(xhr, 'responseText', {
        get: () => {
          let result = getter.call(xhr);
          try {
            const res = JSON.parse(result);
            res.data.push('油猴脚本修改数据888888');
            return JSON.stringify(res);
          } catch (e) {
            return result;
          }
        },
      });
    }
    originOpen.apply(this, arguments);
  };

  //写法2
  // const originOpen = XMLHttpRequest.prototype.open;
  // XMLHttpRequest.prototype.open = function (_, url) {
  //   if (url === "http://localhost:3002/api/query") {
  //     this.addEventListener("readystatechange", function () {
  //       if (this.readyState === 4) {
  //         const res = JSON.parse(this.responseText);
  //         // 当前 xhr 对象上定义 responseText
  //         Object.defineProperty(this, "responseText", {
  //           writable: true,
  //         });
  //         res.data.push("油猴脚本修改数据");
  //         this.responseText = JSON.stringify(res);
  //       }
  //     });
  //   }

  //   originOpen.apply(this, arguments);
  // };
})();

统计 B 站合集时长

js
// ==UserScript==
// @name         统计B站合集总时长
// @namespace    http://tampermonkey.net/
// @version      2024-12-13
// @description  try to take over the world!
// @author       happyfe呀
// @match        https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
// @require     https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js
// ==/UserScript==

(function () {
  'use strict';
  function timeStringToSeconds(timeString) {
    const parts = timeString.split(':').map(Number);
    if (parts.length === 3) {
      // 格式为 HH:MM:SS
      return parts[0] * 3600 + parts[1] * 60 + parts[2];
    } else if (parts.length === 2) {
      // 格式为 MM:SS
      return parts[0] * 60 + parts[1];
    } else {
      throw new Error('Invalid time format');
    }
  }

  function secondsToTime(seconds) {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const secs = seconds % 60;

    return `${hours.toString().padStart(2, '0')}:${minutes
      .toString()
      .padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  }
  var totalSeconds = 0;
  setTimeout(() => {
    $('.stat-item.duration').each(function () {
      var duration = $(this).text().trim();
      // console.log(duration);
      totalSeconds += timeStringToSeconds(duration);
      console.log(secondsToTime(totalSeconds));
    });

    $('.video-pod__header').append(
      `<span style="color:red;font-size:20px;">合集时长:${secondsToTime(
        totalSeconds
      )}</span>`
    );
  }, 2000);
})();

调整视频播放速度

js
// ==UserScript==
// @name         b站4倍8倍速播放
// @namespace    http://tampermonkey.net/
// @version      2024-12-14
// @description  try to take over the world!
// @author       You
// @match        https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  var li8 = document.createElement('li');
  li8.setAttribute('class', 'bpx-player-ctrl-playbackrate-menu-item');
  li8.setAttribute('data-value', '8');
  li8.innerHTML = '8.0x';

  var li4 = document.createElement('li');
  li4.setAttribute('class', 'bpx-player-ctrl-playbackrate-menu-item');
  li4.setAttribute('data-value', '4');
  li4.innerHTML = '4.0x';
  var ul = null;
  var timer = setInterval(function () {
    ul = document.querySelector('.bpx-player-ctrl-playbackrate-menu');
    if (ul) {
      ul.insertBefore(li4, ul.firstChild);
      ul.insertBefore(li8, ul.firstChild);
      ul.addEventListener('click', function (event) {
        var target = event.target;
        if (target.tagName === 'LI') {
          var value = target.getAttribute('data-value');
          if (value) {
            var player = document.querySelector('.bpx-player-video-wrap video');
            player.playbackRate = parseFloat(value);
          }
        }
      });
      clearInterval(timer);
    }
  }, 500);
})();

删除网页中的广告

js
// ==UserScript==
// @name         删除页面广告
// @namespace    http://tampermonkey.net/
// @version      2024-12-14
// @description  try to take over the world!
// @author       You
// @match        https://www.bilibili.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  setTimeout(function () {
    var ad = document.getElementById('slide_ad');
    if (ad) {
      ad.remove();
    }

    var ad2 = document.querySelector('.ad-floor-cover.b-img');
    if (ad2) {
      ad2.remove();
    }
  }, 1000);
})();

B 站下载 MP3

js
// ==UserScript==
// @name         下载B站mp3
// @namespace    http://tampermonkey.net/
// @version      2024-12-09
// @description  try to take over the world!
// @author       You
// @match        https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  // 动态创建CSS
  const createStyles = () => {
    const style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = `
            .custom-button {
                background-color: #00a1d6;
                color: white;
                padding: 10px 20px;
                border: none;
                border-radius: 5px;
                cursor: pointer;
                font-size: 16px;
            }
         
        `;
    document.head.appendChild(style);
  };

  createStyles();

  let audioState = 'pending';
  let my_xhr = null;
  const resetAudioAbout = (btn) => {
    btn.textContent = '下载MP3';
    audioState = 'pending';
  };
  var btn = document.createElement('button');
  btn.className = 'custom-button';
  btn.textContent = '下载MP3';
  btn.addEventListener('click', (e) => aduioEvent(btn, e));
  let count = 10;
  let timer = setInterval(() => {
    var list = document.querySelector('.video-info-detail-list');
    if (list) {
      list.appendChild(btn);
    }
    if (count > 0) {
      count--;
    } else {
      clearInterval(timer);
    }
  }, 1000);

  const aduioEvent = (btn, e) => {
    e.preventDefault();
    if (audioState === 'pending') {
      audioState = 'active';
      const url = `https://api.bilibili.com/x/player/playurl?avid=${
        __INITIAL_STATE__.aid
      }&bvid=${__INITIAL_STATE__.bvid}&cid=${
        __INITIAL_STATE__.cidMap[__INITIAL_STATE__.bvid].cids[
          __INITIAL_STATE__.p
        ]
      }&fnval=4048`;
      fetch(url)
        .then((resp) => resp.json())
        .then((i) => {
          my_xhr = new XMLHttpRequest();
          my_xhr.responseType = 'blob';
          my_xhr.open('GET', i.data.dash.audio[0].base_url, true);
          my_xhr.onprogress = (event) =>
            (btn.textContent = `下载中 ${parseInt(
              (event.loaded / event.total) * 100
            )}%`);
          my_xhr.onload = () => {
            if (my_xhr.status !== 200) resetAudioAbout(btn);
            const reader = new FileReader();
            reader.readAsDataURL(my_xhr.response);
            reader.onload = (e) => {
              const a = document.createElement('a');
              a.download =
                document.querySelector('.video-title').textContent + '.mp3';
              a.href = e.target.result;
              document.documentElement.appendChild(a);
              a.click();
              a.remove();
              my_xhr = null;
              resetAudioAbout(btn);
            };
          };
          my_xhr.onerror = () => resetAudioAbout(btn);
          my_xhr.onabort = () => resetAudioAbout(btn);
          my_xhr.ontimeout = () => resetAudioAbout(btn);
          my_xhr.send();
        });
    } else {
      my_xhr.abort();
      btn.textContent = '已取消下载';
      setTimeout(() => resetAudioAbout(btn), 1000);
    }
  };
})();

下载图片

js
// ==UserScript==
// @name         Bilidown联动脚本[音频mp3🎵,视频mp4📹,弹幕xml,ass🌴,封面🌾]
// @namespace    http://tampermonkey.net/
// @version      0.3.4
// @description  脚本负责获取音频,工具负责视频封面弹幕等等,相互联动,互相增强,好耶ヽ(✿゚▽゚)ノ
// @author       王子周棋洛
// @match        https://www.bilibili.com/video/*
// @icon         data:image/webp;base64,UklGRkoEAABXRUJQVlA4WAoAAAAwAAAAPwAAPwAASUNDUMgBAAAAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADZBTFBIoAAAAAFfoDaSDTr9kEn/UrmORERgqJdeT4zgpta2N3kpvwT2vncJ2etDlRAJkRAdkdAAefX7doaI/k+AJK1zTKOR3ieYT97EcH+aVhBe4GkOkmI6u5IgUT8dr2hK51EyjHm6EuLG5xYyr0N+96kins78P8p7nyrKGp/bIfYKMT6EKZ1HwVDUHlekmM6uIEgCO5CkGHqbCsKLUsxXer/ObaKRJAFWUDggtAEAAFAMAJ0BKkAAQAA/gbrNXrYxKSaoGAyqwDAJQBqv5i4QYRFzT/ewe1mJ22KucUYMTsK3jwwkf8stqz7QBvQGk6vMjaTmq2ViEiSLAR1tyBqGQgiVl9OQAsS9pi/vVcuzN4zM37Byu/vzBSbAANtKZ5uayFBaFvRGroBkEeijKjuBHPFsLGenK/cmNDrTzifuCSIJ/7i/iN3mTpvr+VDTxfR+AndcIgd4ITIALC+kAtGhEY+84lcBrJGGTL8sctYdjSwyxEymZswKkLHIQau61+hOR8OfHc9zjTsE1th9tHAMR4HsE2DBQYVAXK74u6HvEXONvCPNf+Ah+DAPYogluF+in3FCd0ceF2p704DGdxYqBDrVldeuslKulMg8w50VpbgiNKl4kAQ18A4yjKYOMrTZuNCp11UNCtSvjM9tCe1GXxDQY3fuAy24FtQaL12glbmxTCFblu/ZpuLBTgSCu1LCzsOLWdEZQAgrnNmoJkd4DzQjKGVrf4d1lQJiEfYQUxaBrK6syg07hhCi2hajnkjUg/XGWBJiQX5OvF7FLrdmlLZeCOW3tese1rensTisK4VgAAA=
// @grant        GM_download
// @run-at       document-start
// @license      MIT
// @downloadURL https://update.greasyfork.org/scripts/459377/Bilidown%E8%81%94%E5%8A%A8%E8%84%9A%E6%9C%AC%5B%E9%9F%B3%E9%A2%91mp3%F0%9F%8E%B5%EF%BC%8C%E8%A7%86%E9%A2%91mp4%F0%9F%93%B9%EF%BC%8C%E5%BC%B9%E5%B9%95xml%EF%BC%8Cass%F0%9F%8C%B4%EF%BC%8C%E5%B0%81%E9%9D%A2%F0%9F%8C%BE%5D.user.js
// @updateURL https://update.greasyfork.org/scripts/459377/Bilidown%E8%81%94%E5%8A%A8%E8%84%9A%E6%9C%AC%5B%E9%9F%B3%E9%A2%91mp3%F0%9F%8E%B5%EF%BC%8C%E8%A7%86%E9%A2%91mp4%F0%9F%93%B9%EF%BC%8C%E5%BC%B9%E5%B9%95xml%EF%BC%8Cass%F0%9F%8C%B4%EF%BC%8C%E5%B0%81%E9%9D%A2%F0%9F%8C%BE%5D.meta.js
// ==/UserScript==

(function () {
  let printLog = true;
  const log = window.console.log;
  window.console.log = (...args) => printLog && log.apply(window.console, args);
  let audioState = 'pending';
  let retryCounter = 0;
  let my_xhr = null;
  const currentVersion = GM_info.script.version;
  let remoteVersion = null;
  const updateUrl =
    'https://update.greasyfork.org/scripts/459377/Bilidown%E8%81%94%E5%8A%A8%E8%84%9A%E6%9C%AC%5B%E9%9F%B3%E9%A2%91mp3%F0%9F%8E%B5%EF%BC%8C%E8%A7%86%E9%A2%91mp4%F0%9F%93%B9%EF%BC%8C%E5%BC%B9%E5%B9%95xml%EF%BC%8Cass%F0%9F%8C%B4%EF%BC%8C%E5%B0%81%E9%9D%A2%F0%9F%8C%BE%5D.meta.js?t=' +
    new Date().getTime();
  const localStorageMap = {
    封面: 'bilidown_script_cover',
    音频: 'bilidown_script_audio',
    视频: 'bilidown_script_video',
    bilidown客户端: 'bilidown_script_launch',
  };

  const $ = (el) => document.querySelector(el);
  const checkElExist = (c) => $(`.${c}`);
  const chooseEl = () =>
    document.querySelectorAll('.video-info-detail-list') ||
    document.querySelectorAll('.video-data');
  const renderBtn = () =>
    Object.keys(localStorageMap).forEach((k) =>
      renderBtnLogic(`bilidown_script_btn bilidown_script_${k}_btn`, k)
    );
  const mount = (el, containers) =>
    containers.forEach((item) => item.appendChild(el));
  const resetAudioAbout = (btn) => {
    btn.textContent = '音频';
    audioState = 'pending';
  };
  const removeAllBtn = () =>
    Object.keys(localStorageMap).forEach(
      (k) =>
        $(`.bilidown_script_${k}_btn`) &&
        $(`.bilidown_script_${k}_btn`).remove()
    );
  const injectStyle = (className, css) => {
    if (checkElExist(className)) return;
    let style = document.createElement('style');
    style.className = className;
    style.innerText = css;
    document.head.appendChild(style);
  };
  // inject edit native style
  injectStyle(
    'bilidown_script_eidt_native_2024',
    '.video-info-container{margin-bottom:6px}.video-info-detail-list{flex-wrap:wrap!important;height:44px!important}'
  );
  // const coverEvent = () => window.open(__INITIAL_STATE__.videoData.pic, '_blank')
  const coverEvent = () =>
    GM_download(
      __INITIAL_STATE__.videoData.pic,
      document.querySelector('.video-title').innerText +
        __INITIAL_STATE__.videoData.pic.substring(
          __INITIAL_STATE__.videoData.pic.lastIndexOf('.')
        )
    );
  const videoEvent = () =>
    location.href &&
    window.open(`http://zhouql.vip/bilibili/?${location.href}`, '_blank');
  const launchEvent = () =>
    location.href && window.open(`bilidown://parser?link=${location.href}`);
  const aduioEvent = (btn, e) => {
    e.preventDefault();
    if (audioState === 'pending') {
      audioState = 'active';
      const url = `https://api.bilibili.com/x/player/playurl?avid=${
        __INITIAL_STATE__.aid
      }&bvid=${__INITIAL_STATE__.bvid}&cid=${
        __INITIAL_STATE__.cidMap[__INITIAL_STATE__.bvid].cids[
          __INITIAL_STATE__.p
        ]
      }&fnval=4048`;
      fetch(url)
        .then((resp) => resp.json())
        .then((i) => {
          my_xhr = new XMLHttpRequest();
          my_xhr.responseType = 'blob';
          my_xhr.open('GET', i.data.dash.audio[0].base_url, true);
          my_xhr.onprogress = (event) =>
            (btn.textContent = `下载中 ${parseInt(
              (event.loaded / event.total) * 100
            )}%`);
          my_xhr.onload = () => {
            if (my_xhr.status !== 200) resetAudioAbout(btn);
            const reader = new FileReader();
            reader.readAsDataURL(my_xhr.response);
            reader.onload = (e) => {
              const a = document.createElement('a');
              a.download = `${
                $('.video-title').textContent || 'Hello World'
              }.mp3`;
              a.href = e.target.result;
              document.documentElement.appendChild(a);
              a.click();
              a.remove();
              my_xhr = null;
              resetAudioAbout(btn);
            };
          };
          my_xhr.onerror = () => resetAudioAbout(btn);
          my_xhr.onabort = () => resetAudioAbout(btn);
          my_xhr.ontimeout = () => resetAudioAbout(btn);
          my_xhr.send();
        });
    } else {
      my_xhr.abort();
      btn.textContent = '已取消下载';
      setTimeout(() => resetAudioAbout(btn), 1000);
    }
  };

  let updateCheck = async () => {
    try {
      const resp = await fetch(updateUrl);
      const text = await resp.text();
      if (!text) return false;
      let arr = text.split('\n');
      for (let i = 0; i < arr.length; i++) {
        if (!arr[i].includes('@version')) continue;
        remoteVersion = arr[i].split('version')[1].trim();
        if (currentVersion != remoteVersion) return true;
      }
      return false;
    } catch (e) {
      return false;
    }
  };

  let toggleDialog = () => {
    let dialog = $('.bilidown_script_system_dialog');
    dialog.style.display = dialog.style.display === 'block' ? 'none' : 'block';
    if (dialog.style.display === 'block') echoDialogChecked();
  };

  let echoDialogChecked = () => {
    Object.values(localStorageMap).forEach((v, i) => {
      let value = JSON.parse(localStorage.getItem(v));
      if (value && value === true) {
        document.querySelectorAll(
          '.bilidown_script_system__dialog__content label input'
        )[i].checked = true;
      }
    });
  };

  let renderBtnLogic = (className, text) => {
    let key = JSON.parse(localStorage.getItem(localStorageMap[text]));
    if (key && key === true) {
      let btn = document.createElement('button');
      registerEvent(btn, text);
      btn.textContent = text;
      btn.className = className;
      mount(btn, chooseEl());
    }
  };

  let registerEvent = (btn, text) => {
    if (!localStorageMap.hasOwnProperty(text)) return;
    btn.addEventListener('click', (e) => {
      if (text === '封面') {
        coverEvent(e);
      } else if (text === '音频') {
        aduioEvent(btn, e);
      } else if (text === '视频') {
        videoEvent(e);
      } else if (text === 'bilidown客户端') {
        launchEvent(e);
      }
    });
  };

  let renderDialog = () => {
    if (checkElExist('bilidown_script_system_dialog')) return;
    let target = document.createElement('div');
    target.className = 'bilidown_script_system_dialog';
    target.innerHTML = `<div class="bilidown_script_system__dialog__header"><div class="title"><img src="data:image/webp;base64,UklGRkoEAABXRUJQVlA4WAoAAAAwAAAAPwAAPwAASUNDUMgBAAAAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADZBTFBIoAAAAAFfoDaSDTr9kEn/UrmORERgqJdeT4zgpta2N3kpvwT2vncJ2etDlRAJkRAdkdAAefX7doaI/k+AJK1zTKOR3ieYT97EcH+aVhBe4GkOkmI6u5IgUT8dr2hK51EyjHm6EuLG5xYyr0N+96kins78P8p7nyrKGp/bIfYKMT6EKZ1HwVDUHlekmM6uIEgCO5CkGHqbCsKLUsxXer/ObaKRJAFWUDggtAEAAFAMAJ0BKkAAQAA/gbrNXrYxKSaoGAyqwDAJQBqv5i4QYRFzT/ewe1mJ22KucUYMTsK3jwwkf8stqz7QBvQGk6vMjaTmq2ViEiSLAR1tyBqGQgiVl9OQAsS9pi/vVcuzN4zM37Byu/vzBSbAANtKZ5uayFBaFvRGroBkEeijKjuBHPFsLGenK/cmNDrTzifuCSIJ/7i/iN3mTpvr+VDTxfR+AndcIgd4ITIALC+kAtGhEY+84lcBrJGGTL8sctYdjSwyxEymZswKkLHIQau61+hOR8OfHc9zjTsE1th9tHAMR4HsE2DBQYVAXK74u6HvEXONvCPNf+Ah+DAPYogluF+in3FCd0ceF2p704DGdxYqBDrVldeuslKulMg8w50VpbgiNKl4kAQ18A4yjKYOMrTZuNCp11UNCtSvjM9tCe1GXxDQY3fuAy24FtQaL12glbmxTCFblu/ZpuLBTgSCu1LCzsOLWdEZQAgrnNmoJkd4DzQjKGVrf4d1lQJiEfYQUxaBrK6syg07hhCi2hajnkjUg/XGWBJiQX5OvF7FLrdmlLZeCOW3tese1rensTisK4VgAAA="                alt=""><a href="https://greasyfork.org/zh-CN/scripts/459377" target="_blank">bilidown联动脚本</a></div><div class="close"><svg t="1721872624289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"                p-id="5584" width="64" height="64"><path                    d="M240.512 180.181333l271.530667 271.488 271.530666-271.488a42.666667 42.666667 0 0 1 56.32-3.541333l4.010667 3.541333a42.666667 42.666667 0 0 1 0 60.330667l-271.530667 271.530667 271.530667 271.530666a42.666667 42.666667 0 0 1-56.32 63.872l-4.010667-3.541333-271.530666-271.530667-271.530667 271.530667-4.010667 3.541333a42.666667 42.666667 0 0 1-56.32-63.872l271.488-271.530666-271.488-271.530667a42.666667 42.666667 0 0 1 60.330667-60.330667z"                    fill="#000000" p-id="5585"></path></svg></div></div><div class="bilidown_script_system__dialog__content"><ul><li><label id="cover"><span>封面</span><input type="checkbox" name="封面"></label></li><li><label id="audio"><span>音频</span><input type="checkbox" name="音频"></label></li><li><label id="video"><span>视频</span><input type="checkbox" name="视频"></label></li><li><label id="bilidown-pc"><span>bilidown客户端<a href="https://zhouql.vip/bilibili/pc/"                            target="_blank">[安装]</a></span><input type="checkbox" name="bilidown客户端"></label></li></ul></div>`;
    mount(target, chooseEl());
    let closeDialog = $('.bilidown_script_system__dialog__header .close');
    closeDialog.addEventListener('click', (e) => toggleDialog());
    document
      .querySelectorAll('.bilidown_script_system__dialog__content label input')
      .forEach((label) => {
        label.addEventListener('click', (e) => {
          localStorage.setItem(
            localStorageMap[e.target.name],
            e.target.checked
          );
          removeAllBtn();
          renderBtn();
        });
      });
  };

  let renderSystemBtn = () => {
    if (checkElExist('bilidown_script_system_btn')) return;
    let btn = document.createElement('button');
    btn.textContent = '设置';
    btn.className = 'bilidown_script_btn bilidown_script_system_btn';
    btn.addEventListener('click', (e) => toggleDialog());
    mount(btn, chooseEl());
  };

  let render = (retryNum = 5) => {
    (async function check(c) {
      if (c < 1) {
        console.error('bilidown script render timeout, stopped.');
        return;
      }
      if (checkElExist('bpx-player-ctrl-playbackrate-menu')) {
        retryCounter === 0 && console.log('bilidown script rendering...');
        injectStyle(
          'bilidown_script_style_2024',
          `.bilidown_script_btn{background-color:#0071e3;border:0;color:#fff;border-radius:100px;cursor:pointer;padding:0 6px;font-size:12px;margin:0 2px}.bilidown_script_system_dialog{width:280px;border:1px solid #ccc;padding:12px 16px;position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);background-color:#fafafa;border-radius:4px;user-select:none;display:none;z-index:99999}.bilidown_script_system__dialog__header{display:flex;align-items:center;justify-content:space-between}.bilidown_script_system__dialog__header .title{display:flex;align-items:center;flex:1}.bilidown_script_system__dialog__header a{font-size:15px;text-decoration:none;color:#232323;transition:all .1s;flex:1;margin-right:30px}.bilidown_script_system__dialog__header img{height:18px;width:18px;margin-right:4px}.bilidown_script_system__dialog__header a:hover{color:#0071e3}.bilidown_script_system__dialog__header .close{display:flex;align-items:center;justify-content:center;cursor:pointer}.bilidown_script_system__dialog__header .close svg{width:16px;height:16px}.bilidown_script_system__dialog__content{margin-top:10px}.bilidown_script_system__dialog__content ul li{list-style:none;font-size:13px;margin:16px 0}.bilidown_script_system__dialog__content ul li:last-child{margin:0}.bilidown_script_system__dialog__content ul li label{display:flex;align-items:center;justify-content:space-between;cursor:pointer;color:#454545;}.bilidown_script_system__dialog__content ul li label a{color:#0071e3;pointer-events:all}.bilidown_script_system__dialog__content ul li span{pointer-events:none}.bilidown_script_system__dialog__content ul li input{margin-right:2px}.new_version{position:relative}.new_version::after{content:'';display:inline-block;position:absolute;width:9px;height:9px;border-radius:50%;background-color:#fc3c4a;top:0;left:-10px}.system_btn_new_version::after{right:0;left:initial}`
        );
        renderDialog();
        renderSystemBtn();
        removeAllBtn();
        renderBtn();
        console.log('bilidown script render success.');
        if (retryCounter === 0) {
          const hasNewVersion = await updateCheck();
          console.log(`bilidown new version result: ${hasNewVersion}`);
          if (hasNewVersion) {
            $(
              '.bilidown_script_system__dialog__header a'
            ).textContent = `[新版本${remoteVersion},点我更新]`;
            $('.bilidown_script_system__dialog__header a').classList.add(
              'new_version'
            );
            $('.bilidown_script_system_btn').classList.add('new_version');
            $('.bilidown_script_system_btn').classList.add(
              'system_btn_new_version'
            );
          }
        }
        if (retryCounter++ < 6) {
          console.log(`bilidown script render confirm${retryCounter}.`);
          setTimeout(() => render(), 1000);
        }
      } else {
        console.log(`bilidown script waiting render${c}...`);
        setTimeout(() => check(c - 1), 1000);
      }
    })(retryNum);
  };
  window.onload = () => render(15);
})();

QQ 学习交流群

  • 群号:476705578
  • 群名:happyfe 呀的学习交流群
  • 进群口令: happyfe 呀
  • 群二维码:qr_code