您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在哔哩哔哩主页显示 IP 属地。仅支持显示个人主页。为了获得完整体验,建议配合使用 哔哩哔哩网页版显示 IP 属地(https://greasyfork.org/scripts/466815)脚本。
当前为
// ==UserScript== // @name 哔哩哔哩主页 IP 属地 // @namespace https://maxchang.me // @version 0.0.1 // @description 在哔哩哔哩主页显示 IP 属地。仅支持显示个人主页。为了获得完整体验,建议配合使用 哔哩哔哩网页版显示 IP 属地(https://greasyfork.org/scripts/466815)脚本。 // @author maxchang3 // @match https://space.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @grant GM.registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM.xmlHttpRequest // @grant unsafeWindow // @require https://update.greasyfork.org/scripts/400945/1055319/libBilibiliToken.js // @require https://fastly.jsdelivr.net/npm/[email protected] // @run-at document-idle // @license MIT // ==/UserScript== /// <reference path="./types/global.d.ts" /> // @ts-check /** * @typedef {'log' | 'error'} LogLevel */ // biome-ignore format: keep type annotation const logger = (/*** @returns {Record<LogLevel, (...args: unknown[]) => void>} */() => { const { name: scriptname, version: scriptversion } = GM_info.script /** * @param {LogLevel} logMethod * @param {string} tag * @param {unknown[]} args */ const log = (logMethod, tag, ...args) => { const colors = { log: '#2c3e50', error: '#ff4500', } const fontFamily = "font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;" console[logMethod]( `%c ${scriptname} %c v${scriptversion} %c ${tag} `, `padding: 2px 6px; border-radius: 3px 0 0 3px; color: #fff; background: #FF6699; font-weight: bold; ${fontFamily}`, `padding: 2px 6px; color: #fff; background: #FF9999; font-weight: bold; ${fontFamily}`, `padding: 2px 6px; border-radius: 0 3px 3px 0; color: #fff; background: ${colors[logMethod]}; font-weight: bold; ${fontFamily}`, ...args ) } return { log: (...args) => log('log', 'LOG', ...args), error: (...args) => log('error', 'ERROR', ...args), } })() const tokenClient = new BilibiliToken() const updateAccessKey = async () => { const tokenData = await tokenClient.getToken() if (tokenData) { GM_setValue('aceess_key', tokenData.access_token) return true } logger.error('获取 token 失败') return false } const queryStringify = (/** @type {Record<string, string>} */ data) => Object.entries(data) .map(([k, v]) => `${k}=${v}`) .join('&') const getLocation = async (/** @type {string} */ vmid) => { if (!hasToken) { logger.error('请先获取 Access Key') return null } const params = BilibiliToken.signQuery( queryStringify({ access_key: accessKey, appkey: BilibiliToken.appKey, build: tokenClient.build, mobi_app: tokenClient.mobiApp, vmid, }) ) try { const data = await BilibiliToken.XHR({ GM: true, anonymous: true, method: 'GET', url: `https://app.bilibili.com/x/v2/space?${params}`, responseType: 'json', headers: tokenClient.headers, }) if (!data?.body) { logger.error('获取数据失败', data) return null } /** * @type {import('./types/space').SpaceResponse} */ const spaceResponse = data.body if (spaceResponse.code !== 0) { logger.error('获取数据失败', spaceResponse) return null } const locationCards = spaceResponse.data.card.space_tag.filter( (tag) => tag.type === 'location' ) if (locationCards.length === 0) { logger.error('该 UP 主无 IP 属地') return null } return locationCards[0].title } catch (error) { logger.error('请求出错', error) return null } } const injectLocation = ( /** @type {string} */ location, /** @type {HTMLDivElement} */ upInfoMainElement ) => { const upInfoTopElement = upInfoMainElement.querySelector( '.upinfo-detail__top' ) if (!upInfoTopElement) { logger.error('未找到 UP 主信息元素') return } const locationElement = document.createElement('div') Object.assign(locationElement.style, { color: '#fff', fontSize: '10px', backgroundColor: 'rgba(0, 0, 0, 0.5)', borderRadius: '4px', padding: '.4em', marginLeft: '.4em', display: 'inline-block', }) locationElement.className = 'location' locationElement.innerText = location upInfoTopElement.appendChild(locationElement) } logger.log('脚本加载完成') const acquireAccessKey = async () => { try { const success = await updateAccessKey() if (success) { alert('获取 Access Key 成功') } else { alert( '获取 Access Key 失败。若首次使用,可能会导致账号下线,请刷新后尝试重新登录后刷新重新获取。' ) window.location.reload() } return success } catch (err) { logger.error('获取 Access Key 出错', err) alert('获取过程出错,请稍后重试') return false } } const accessKey = GM_getValue('aceess_key') const hasToken = Boolean(accessKey) const requireAccessKey = async () => { if (!unsafeWindow.__BiliUser__.isLogin) { logger.error('未登录,无法获取 Access Key') } else { const confirmMessage = `[${GM_info.script.name}] 未获取 Access Key,需要获取才能正常使用\n\n` + '首次获取可能会导致账号下线,需要登录后再次获取\n' + '是否立即获取?' if (confirm(confirmMessage)) { acquireAccessKey().then((success) => { if (success) window.location.reload() }) } } } if (hasToken) logger.log('已获取 Access Key', accessKey) GM.registerMenuCommand( `${hasToken ? '【✅ 已获取】' : '【❌ 未获取】'}获取 Access Key`, async () => { await acquireAccessKey() } ) // 等待 Header 中的信息加载出来 GmExtra.querySelector(document.body, '.upinfo__main').then( async (upInfoMainElement) => { if (!hasToken) { requireAccessKey() return } if (!upInfoMainElement) { logger.error('未找到 UP 主信息元素') return } const vmidMatch = window.location.href.match( /space\.bilibili\.com\/(\d+)(?:\/|$)/ ) if (!vmidMatch) { logger.error('未找到 vmid', window.location.href) return } if (!hasToken) return const location = await getLocation(vmidMatch[1]) if (!location) { logger.error('获取 IP 属地失败') return } logger.log('获取 IP 属地成功', location) injectLocation( location, /** @type {HTMLDivElement} */ (upInfoMainElement) ) } )