Greasy Fork is available in English.

IHateLivehime

在个人直播间添加“开始直播”与“结束直播”按钮,让低粉丝数的用户也能绕开强制要求的直播姬开播。

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        IHateLivehime
// @name:zh-CN  我讨厌直播姬
// @description 在个人直播间添加“开始直播”与“结束直播”按钮,让低粉丝数的用户也能绕开强制要求的直播姬开播。
// @author      Puqns67
// @copyright   2025, Puqns67 (https://github.com/Puqns67)
// @license     GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt
// @version     0.1.5.2
// @icon        https://i0.hdslb.com/bfs/static/jinkela/long/images/favicon.ico
// @homepageURL https://github.com/Puqns67/IHateLivehime
// @supportURL  https://github.com/Puqns67/IHateLivehime/issues
// @namespace   https://github.com/Puqns67
// @match       https://live.bilibili.com/*
// @require     https://cdn.jsdelivr.net/npm/[email protected]/md5.min.js
// @grant       GM_addStyle
// @grant       GM_setClipboard
// ==/UserScript==

(async function () {
	'use strict';

	const APPKEY = "aae92bc66f3edfab";
	const APPSEC = "af125a0d5279fd576c1b4418a3e8276d";

	function sleep(time) {
		return new Promise((resolve) => setTimeout(resolve, time));
	}

	function api_alert(object) {
		alert(`${object.msg}\n错误代码:${object.code}\n详细信息:\n${JSON.stringify(object)}`);
	}

	async function get_element_with_wait(selectors, timeout = 3200, retry_count = 32) {
		let timeout_once = timeout / 32;
		let retry = 1;

		while (retry <= retry_count) {
			let result = document.querySelector(selectors);
			if (result !== null) return result;
			await sleep(timeout_once);
			retry++;
		}

		return null;
	}

	function get_cookie(name) {
		let re = new RegExp(`(?:^|; *)${name}=([^=]+?)(?:;|$)`).exec(document.cookie);
		return re === null ? null : re[1];
	}

	async function get_timestemp() {
		return await fetch("https://api.bilibili.com/x/report/click/now").then(r => r.json());
	}

	async function get_current_liveime_version() {
		return await fetch('https://api.live.bilibili.com/xlive/app-blink/v1/liveVersionInfo/getHomePageLiveVersion?system_version=2').then(r => r.json());
	}

	async function get_current_user_info() {
		return await fetch("https://api.bilibili.com/x/space/myinfo", { "credentials": "include" }).then(r => r.json());
	}

	async function get_room_info_by_room_id(id) {
		return await fetch(`https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${id}`, { "credentials": "include" }).then(r => r.json());
	}

	async function get_room_info_by_user_id(id) {
		return await fetch(`https://api.live.bilibili.com/live_user/v1/Master/info?uid=${id}`, { "credentials": "include" }).then(r => r.json());
	}

	async function start_live(room_id) {
		let bili_jct = get_cookie("bili_jct");
		if (bili_jct === null) {
			alert("无法开始直播\nCookie \"bili_jct\" 不存在,请尝试重新登录!");
			return;
		}

		let room_info = await get_room_info_by_room_id(room_id);
		if (room_info.code !== 0) {
			api_alert(room_info);
			return;
		}
		if (room_info.data.live_status === 1) {
			alert("无法开始直播\n房间已开播!");
			return;
		}

		let current_timestemp = await get_timestemp();
		if (current_timestemp.code !== 0) {
			api_alert(current_timestemp);
			return;
		}

		let current_liveime_version = await get_current_liveime_version();
		if (current_liveime_version.code !== 0) {
			api_alert(current_liveime_version);
			return;
		}

		let data = {
			"appkey": APPKEY,
			"area_v2": room_info.data.area_id,
			"build": current_liveime_version.data.build,
			"csrf": bili_jct,
			"platform": "pc_link",
			"room_id": room_id,
			"ts": current_timestemp.data.now,
			"version": current_liveime_version.data.curr_version
		};
		data.sign = md5(new URLSearchParams(data).toString() + APPSEC);

		let params = new URLSearchParams(data).toString();

		let response = await fetch("https://api.live.bilibili.com/room/v1/Room/startLive?" + params, { "method": "POST", "credentials": "include" }).then(r => r.json());
		if (response.code !== 0) {
			api_alert(response);
			return;
		}

		GM_setClipboard(response.data.rtmp.code);
		alert(`开始直播成功!\n推流密钥已经发送至剪贴板~\n\n推流地址:${response.data.rtmp.addr}\n推流密钥:${response.data.rtmp.code}`);
	}

	async function stop_live(room_id) {
		let bili_jct = get_cookie("bili_jct");
		if (bili_jct === null) {
			alert("无法关闭直播\nCookie \"bili_jct\" 不存在,请尝试重新登录!");
			return;
		}

		let room_info = await get_room_info_by_room_id(room_id);
		if (room_info.code !== 0) {
			api_alert(room_info);
			return;
		}
		if ([0, 2].includes(room_info.data.live_status)) {
			alert("无法关闭直播\n房间未开播!");
			return;
		}

		let params = new URLSearchParams({
			"platform": "pc_link",
			"room_id": room_id,
			"csrf": bili_jct
		}).toString();

		let response = await fetch("https://api.live.bilibili.com/room/v1/Room/stopLive?" + params, { "method": "POST", "credentials": "include" }).then(r => r.json());
		if (response.code !== 0) {
			api_alert(response);
			return;
		}

		alert("关闭直播成功!");
	}

	let path_room_id = /^\/(\d+)/.exec(document.location.pathname);
	if (path_room_id === null) {
		console.warn("当前页面并非直播间");
		return;
	}

	let room_id = Number(path_room_id[1]);

	let current_user_info = await get_current_user_info();
	if (current_user_info.code === -101) {
		console.warn("账户未登录");
		return;
	}

	let current_room_info = await get_room_info_by_room_id(room_id);

	if (current_user_info.data.mid !== current_room_info.data.uid) {
		console.warn("当前直播间不为自己的直播间");
		return;
	}

	let button_area = await get_element_with_wait(".left-header-area");
	if (button_area === null) {
		console.warn("页面元素不存在");
		return;
	}

	let start_live_button = document.createElement("button");
	start_live_button.appendChild(document.createTextNode("开始直播"));
	start_live_button.addEventListener("click", async () => start_live(room_id));

	let stop_live_button = document.createElement("button");
	stop_live_button.appendChild(document.createTextNode("结束直播"));
	stop_live_button.addEventListener("click", async () => stop_live(room_id));

	button_area.appendChild(start_live_button);
	button_area.appendChild(stop_live_button);

	console.log("开/下播按钮已添加");

	// 修复在直播间实验室中启用深色模式后无法点击顶栏中元素的问题(上游 BUG)
	GM_addStyle("html[lab-style*='dark'] #head-info-vm.bg-bright-filter::before { pointer-events: none }");
}());