【油猴脚本】Bilibili 网页版播放列表随机播放
Bilibili 网页版播放列表随机播放
网页挂后台方便点
Bilibili 推荐区随机播放
关注露米 Lumi 谢谢喵~:
露米 Lumi https://space.bilibili.com/2000609327
xiaohack博客专注前沿科技动态与实用技术干货分享,涵盖 AI 代理、大模型应用、编程工具、文档解析、SEO 实战、自动化部署等内容,提供开源项目教程、科技资讯日报、工具使用指南,助力开发者、AI 爱好者获取前沿技术与实战经验。
网页挂后台方便点
Bilibili 推荐区随机播放
关注露米 Lumi 谢谢喵~:
露米 Lumi https://space.bilibili.com/2000609327
Notable changes
Implemented support for SSO with OpenID Connect, https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect
Updated web vault to 2025.12.0
Added support for future mobile apps with versions 2026.1.0+
This is the first vaultwarden release using immutable releases and release attestation!
更多信息参照:
机翻版本
Google 向来低调,即便是 Gemini 3 这种顶级产品的发布,也不像其他公司一样搞得那么隆重,有一些小产品更是低调到发了很久都没人知道,比如这个面向程序员的产品 CodeWiki
和 Code Wiki 属于同一类产品,功能完全一致。
DeepWiki 底层可能采用了多家大模型,具体是哪个不太清楚,但是 CodeWiki 是基于 Gemini 模型的,Gemini 具有超长上下文,也就决定了 CodeWiki 非常适合做大型项目的代码分析
你只需要把 GitHub 仓库地址扔给它,它自动帮你写好这个项目的详细解析文档,甚至超过绝大多数仓库自带的 README 文档
最近闲的没事,找了个项目(github 上的 Hello-Agents)看看,太久没看书了,都快阅读障碍了,还好有 Gemini 陪伴
Gemini 的活人感还是很足的,情绪价值也给的蛮足滴。课后习题也通通交给 Gemini
此 docker 是在 perplexity-ai 基础上,增加了 mcp 功能,具体看上一个帖子。目前原项目没做自动更新 token 功能,token 一个月过期。
除了 mcp 之外的问题,提 issue 请找原项目
从这里继续讨论,https://linux.do/t/topic/1371904 增加 http 远程调用,因此封装了个 docker 自用,发出来给需要的人。
github workflow 自动化构建还没时间搞(让 ai 写了一版,没改变量,也没测)
docker compose 一键部署
注意,socks 代理没有测试过
services: perplexity-mcp: shancw/perplexity-mcp:latest container_name: perplexity-mcp ports: - "${MCP_PORT:-8000}:8000" environment: # MCP 认证密钥 - MCP_TOKEN=${MCP_TOKEN:-sk-123456} # Perplexity 账户凭证 (可选,用于高级功能) - PPLX_NEXT_AUTH_CSRF_TOKEN=${PPLX_NEXT_AUTH_CSRF_TOKEN:-} - PPLX_SESSION_TOKEN=${PPLX_SESSION_TOKEN:-} # SOCKS 代理配置 (可选) # 格式: socks5://[user[:pass]@]host[:port][#remark] # 示例: socks5://127.0.0.1:1080 或 socks5://user:pass@proxy.example.com:1080 # - SOCKS_PROXY=${SOCKS_PROXY:-} restart: unless-stopped .env 环境变量
# Perplexity MCP Server 环境变量配置 # 复制此文件为 .env 并填入实际值 # ============================================ # MCP 服务配置 # ============================================ # MCP 服务端口
MCP_PORT=8000
# MCP API 认证密钥 (客户端需要在 Authorization header 中携带此密钥)
MCP_TOKEN=sk-123456
# ============================================ # Perplexity 账户凭证 (可选) # 用于解锁高级功能: Pro 模式、Reasoning 模式、Deep Research # 不配置则只能使用 auto 模式 # ============================================ # 从 Perplexity 网站 Cookie 中获取 # 打开 perplexity.ai -> F12 开发者工具 -> Application -> Cookies
PPLX_NEXT_AUTH_CSRF_TOKEN=
PPLX_SESSION_TOKEN=

mcp 配置
{ "perplexity": { "type": "http", "url": "http://127.0.0.1:8000/mcp", "headers": { "Authorization": "Bearer sk-123456" } } } github
这是一本讲网页排版书籍,书中内容有在线示例与代码,是这么多年来对我极其有用的平面设计书籍之一。原书是英文,翻译了在 L 站分享给大家。
此书为 90% 的 AI 翻译,完全保留原书排版,由个人粗略校对确保大部分用词统一。
书名:Better Web Typography for a Better Web
译名:更好的网页排版,造就更好的互联网
2018 年,我还在大学社团做海报,那时候对排版的理解更多是靠 “直觉”。直到接触到了 Matej Latin 的网页排版公开课,也就是这本书的起源。课和中提出 “排版的完美等边三角形”—— 即字号、行高与行宽之间的动态平衡,以及 “模块化比例” 的概念,彻底改变了我观察网页的视角。我至今铭记在心:“要获得完美的段落,需要三样东西:字体大小、行高和行宽。它们需要达到平衡。”
从那以后,这三要素就成了我做阅读文字时绕不开的底层直觉。每当我看到一个网页,总会下意识地去拆解它的文字比例是否协调。尽管此书以英文为基准,其中的排版规律对多种语言均适用。
虽然这本书写在八年前,在互联网平面设计风格迭代如此之快的今天,它讨论的核心规则却依然稳固。甚至可以说,如今主流的网页排版方向,就如书中的所言。如果你想摆脱 “凭感觉” 排版,真正理解排版的美学逻辑,倾情您阅读推荐这本书。
Better Web Typography for a Better Web 是一本源自高分在线课程的著作,旨在向网页设计师和开发人员等网站构建者讲解排版。作者 Matej Latin 将垂直节奏(vertical rhythm)、模块化比例(modular scale)和页面构成(page composition)等复杂概念,以通俗易懂的方式进行了深入浅出的解析。本书配有实时代码示例,读者在阅读过程中将亲历设计并构建一个示例网站的完整流程。这是一本针对新媒介的排印新书:基本规则虽未改变,但除此之外的一切都已焕然一新。
CloudFlare - R2 直链下载:
标签: 排版,字体排印,平面设计,网页设计
全自动看网课 ((支持倍速))
这才是真正得解放了我的生产力,解决了某些网站没有脚本自动完成的难题
另外补一个 timer hooker 脚本(源自油猴某大佬,适用于所有网站加速)
// ==UserScript==
// @name 计时器掌控者|视频广告跳过|视频广告加速器
// @name:en TimerHooker
// @namespace https://gitee.com/HGJing/everthing-hook/
// @version 1.0.62
// @description 控制网页计时器速度|加速跳过页面计时广告|视频快进(慢放)|跳过广告|支持几乎所有网页.
// @description:en it can hook the timer speed to change.
// @include *
// @require https://greasyfork.org/scripts/372672-everything-hook/code/Everything-Hook.js?version=881251
// @author Cangshi
// @match http://*/*
// @run-at document-start
// @grant none
// @license GPL-3.0-or-later
// @downloadURL https://update.greasyfork.org/scripts/372673/%E8%AE%A1%E6%97%B6%E5%99%A8%E6%8E%8C%E6%8E%A7%E8%80%85%7C%E8%A7%86%E9%A2%91%E5%B9%BF%E5%91%8A%E8%B7%B3%E8%BF%87%7C%E8%A7%86%E9%A2%91%E5%B9%BF%E5%91%8A%E5%8A%A0%E9%80%9F%E5%99%A8.user.js
// @updateURL https://update.greasyfork.org/scripts/372673/%E8%AE%A1%E6%97%B6%E5%99%A8%E6%8E%8C%E6%8E%A7%E8%80%85%7C%E8%A7%86%E9%A2%91%E5%B9%BF%E5%91%8A%E8%B7%B3%E8%BF%87%7C%E8%A7%86%E9%A2%91%E5%B9%BF%E5%91%8A%E5%8A%A0%E9%80%9F%E5%99%A8.meta.js
// ==/UserScript==
/**
* ---------------------------
* Time: 2017/11/20 19:28.
* Author: Cangshi
* View: http://palerock.cn
* ---------------------------
*/
/**
* 1. hook Object.defineProperty | Object.defineProperties
* 2. set configurable: true
* 3. delete property
* 4. can set property for onxx event method
*/
window.isDOMLoaded = false;
window.isDOMRendered = false;
document.addEventListener('readystatechange', function () {
if (document.readyState === "interactive" || document.readyState === "complete") {
window.isDOMLoaded = true;
}
});
~function (global) {
var workerURLs = [];
var extraElements = [];
var suppressEvents = {};
var helper = function (eHookContext, timerContext, util) {
return {
applyUI: function () {
var style = '._th-container ._th-item{margin-bottom:3px;position:relative;width:0;height:0;cursor:pointer;opacity:.3;background-color:aquamarine;border-radius:100%;text-align:center;line-height:30px;-webkit-transition:all .35s;-o-transition:all .35s;transition:all .35s;right:30px}._th-container ._th-item,._th-container ._th-click-hover,._th_cover-all-show-times ._th_times{-webkit-box-shadow:-3px 4px 12px -5px black;box-shadow:-3px 4px 12px -5px black}._th-container:hover ._th-item._item-x2{margin-left:18px;width:40px;height:40px;line-height:40px}._th-container:hover ._th-item._item-x-2{margin-left:17px;width:38px;height:38px;line-height:38px}._th-container:hover ._th-item._item-xx2{width:36px;height:36px;margin-left:16px;line-height:36px}._th-container:hover ._th-item._item-xx-2{width:32px;height:32px;line-height:32px;margin-left:14px}._th-container:hover ._th-item._item-reset{width:30px;line-height:30px;height:30px;margin-left:10px}._th-click-hover{position:relative;-webkit-transition:all .5s;-o-transition:all .5s;transition:all .5s;height:45px;width:45px;cursor:pointer;opacity:.3;border-radius:100%;background-color:aquamarine;text-align:center;line-height:45px;right:0}._th-container:hover{left:-5px}._th-container{font-size:12px;-webkit-transition:all .5s;-o-transition:all .5s;transition:all .5s;left:-35px;top:20%;position:fixed;-webkit-box-sizing:border-box;box-sizing:border-box;z-index:100000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}._th-container ._th-item:hover{opacity:.8;background-color:#5fb492;color:aliceblue}._th-container ._th-item:active{opacity:.9;background-color:#1b3a26;color:aliceblue}._th-container:hover ._th-click-hover{opacity:.8}._th-container:hover ._th-item{opacity:.6;right:0}._th-container ._th-click-hover:hover{opacity:.8;background-color:#5fb492;color:aliceblue}._th_cover-all-show-times{position:fixed;top:0;right:0;width:100%;height:100%;z-index:99999;opacity:1;font-weight:900;font-size:30px;color:#4f4f4f;background-color:rgba(0,0,0,0.1)}._th_cover-all-show-times._th_hidden{z-index:-99999;opacity:0;-webkit-transition:1s all;-o-transition:1s all;transition:1s all}._th_cover-all-show-times ._th_times{width:300px;height:300px;border-radius:50%;background-color:rgba(127,255,212,0.51);text-align:center;line-height:300px;position:absolute;top:50%;right:50%;margin-top:-150px;margin-right:-150px}';
var displayNum = (1 / timerContext._percentage).toFixed(2);
// 在页面左边添加一个半圆便于修改
var html = '<div class="_th-container">\n' +
' <div class="_th-click-hover _item-input">\n' +
' x' + displayNum + '\n' +
' </div>\n' +
' <div class="_th-item _item-x2">></div>\n' +
' <div class="_th-item _item-x-2"><</div>\n' +
' <div class="_th-item _item-xx2">>></div>\n' +
' <div class="_th-item _item-xx-2"><<</div>\n' +
' <div class="_th-item _item-reset">O</div>\n' +
'</div>\n' +
'<div class="_th_cover-all-show-times _th_hidden">\n' +
' <div class="_th_times">x' + displayNum + '</div>\n' +
'</div>' +
'';
var stylenode = document.createElement('style');
stylenode.setAttribute("type", "text/css");
if (stylenode.styleSheet) {// IE
stylenode.styleSheet.cssText = style;
} else {// w3c
var cssText = document.createTextNode(style);
stylenode.appendChild(cssText);
}
var node = document.createElement('div');
node.innerHTML = html;
var clickMapper = {
'_item-input': function () {
changeTime();
},
'_item-x2': function () {
changeTime(2, 0, true);
},
'_item-x-2': function () {
changeTime(-2, 0, true);
},
'_item-xx2': function () {
changeTime(0, 2);
},
'_item-xx-2': function () {
changeTime(0, -2);
},
'_item-reset': function () {
changeTime(0, 0, false, true);
}
};
Object.keys(clickMapper).forEach(function (className) {
var exec = clickMapper[className];
var targetEle = node.getElementsByClassName(className)[0];
if (targetEle) {
targetEle.onclick = exec;
}
});
if (!global.isDOMLoaded) {
document.addEventListener('readystatechange', function () {
if ((document.readyState === "interactive" || document.readyState === "complete") && !global.isDOMRendered) {
document.head.appendChild(stylenode);
document.body.appendChild(node);
global.isDOMRendered = true;
console.log('Time Hooker Works!');
}
});
} else {
document.head.appendChild(stylenode);
document.body.appendChild(node);
global.isDOMRendered = true;
console.log('Time Hooker Works!');
}
},
applyGlobalAction: function (timer) {
// 界面半圆按钮点击的方法
timer.changeTime = function (anum, cnum, isa, isr) {
if (isr) {
global.timer.change(1);
return;
}
if (!global.timer) {
return;
}
var result;
if (!anum && !cnum) {
var t = prompt("输入欲改变计时器变化倍率(当前:" + 1 / timerContext._percentage + ")");
if (t == null) {
return;
}
if (isNaN(parseFloat(t))) {
alert("请输入正确的数字");
timer.changeTime();
return;
}
if (parseFloat(t) <= 0) {
alert("倍率不能小于等于0");
timer.changeTime();
return;
}
result = 1 / parseFloat(t);
} else {
if (isa && anum) {
if (1 / timerContext._percentage <= 1 && anum < 0) {
return;
}
result = 1 / (1 / timerContext._percentage + anum);
} else {
if (cnum <= 0) {
cnum = 1 / -cnum
}
result = 1 / ((1 / timerContext._percentage) * cnum);
}
}
timer.change(result);
};
global.changeTime = timer.changeTime;
},
applyHooking: function () {
var _this = this;
// 劫持循环计时器
eHookContext.hookReplace(window, 'setInterval', function (setInterval) {
return _this.getHookedTimerFunction('interval', setInterval);
});
// 劫持单次计时
eHookContext.hookReplace(window, 'setTimeout', function (setTimeout) {
return _this.getHookedTimerFunction('timeout', setTimeout)
});
// 劫持循环计时器的清除方法
eHookContext.hookBefore(window, 'clearInterval', function (method, args) {
_this.redirectNewestId(args);
});
// 劫持循环计时器的清除方法
eHookContext.hookBefore(window, 'clearTimeout', function (method, args) {
_this.redirectNewestId(args);
});
var newFunc = this.getHookedDateConstructor();
eHookContext.hookClass(window, 'Date', newFunc, '_innerDate', ['now']);
Date.now = function () {
return new Date().getTime();
};
eHookContext.hookedToString(timerContext._Date.now, Date.now);
var objToString = Object.prototype.toString;
Object.prototype.toString = function toString() {
'use strict';
if (this instanceof timerContext._mDate) {
return '[object Date]';
} else {
return objToString.call(this);
}
};
eHookContext.hookedToString(objToString, Object.prototype.toString);
eHookContext.hookedToString(timerContext._setInterval, setInterval);
eHookContext.hookedToString(timerContext._setTimeout, setTimeout);
eHookContext.hookedToString(timerContext._clearInterval, clearInterval);
timerContext._mDate = window.Date;
this.hookShadowRoot();
},
getHookedDateConstructor: function () {
return function () {
if (arguments.length === 1) {
Object.defineProperty(this, '_innerDate', {
configurable: false,
enumerable: false,
value: new timerContext._Date(arguments[0]),
writable: false
});
return;
} else if (arguments.length > 1) {
var definedValue;
switch (arguments.length) {
case 2:
definedValue = new timerContext._Date(
arguments[0],
arguments[1]
);
break;
case 3:
definedValue = new timerContext._Date(
arguments[0],
arguments[1],
arguments[2],
);
break;
case 4:
definedValue = new timerContext._Date(
arguments[0],
arguments[1],
arguments[2],
arguments[3],
);
break;
case 5:
definedValue = new timerContext._Date(
arguments[0],
arguments[1],
arguments[2],
arguments[3],
arguments[4]
);
break;
case 6:
definedValue = new timerContext._Date(
arguments[0],
arguments[1],
arguments[2],
arguments[3],
arguments[4],
arguments[5]
);
break;
default:
case 7:
definedValue = new timerContext._Date(
arguments[0],
arguments[1],
arguments[2],
arguments[3],
arguments[4],
arguments[5],
arguments[6]
);
break;
}
Object.defineProperty(this, '_innerDate', {
configurable: false,
enumerable: false,
value: definedValue,
writable: false
});
return;
}
var now = timerContext._Date.now();
var passTime = now - timerContext.__lastDatetime;
var hookPassTime = passTime * (1 / timerContext._percentage);
// console.log(__this.__lastDatetime + hookPassTime, now,__this.__lastDatetime + hookPassTime - now);
Object.defineProperty(this, '_innerDate', {
configurable: false,
enumerable: false,
value: new timerContext._Date(timerContext.__lastMDatetime + hookPassTime),
writable: false
});
};
},
getHookedTimerFunction: function (type, timer) {
var property = '_' + type + 'Ids';
return function () {
var uniqueId = timerContext.genUniqueId();
var callback = arguments[0];
if (typeof callback === 'string') {
callback += ';timer.notifyExec(' + uniqueId + ')';
arguments[0] = callback;
}
if (typeof callback === 'function') {
arguments[0] = function () {
var returnValue = callback.apply(this, arguments);
timerContext.notifyExec(uniqueId);
return returnValue;
}
}
// 储存原始时间间隔
var originMS = arguments[1];
// 获取变速时间间隔
arguments[1] *= timerContext._percentage;
var resultId = timer.apply(window, arguments);
// 保存每次使用计时器得到的id以及参数等
timerContext[property][resultId] = {
args: arguments,
originMS: originMS,
originId: resultId,
nowId: resultId,
uniqueId: uniqueId,
oldPercentage: timerContext._percentage,
exceptNextFireTime: timerContext._Date.now() + originMS
};
return resultId;
};
},
redirectNewestId: function (args) {
var id = args[0];
if (timerContext._intervalIds[id]) {
args[0] = timerContext._intervalIds[id].nowId;
// 清除该记录id
delete timerContext._intervalIds[id];
}
if (timerContext._timeoutIds[id]) {
args[0] = timerContext._timeoutIds[id].nowId;
// 清除该记录id
delete timerContext._timeoutIds[id];
}
},
registerShortcutKeys: function (timer) {
// 快捷键注册
addEventListener('keydown', function (e) {
switch (e.keyCode) {
case 57:
if (e.ctrlKey || e.altKey) {
// custom
timer.changeTime();
}
break;
// [=]
case 190:
case 187: {
if (e.ctrlKey) {
// console.log('+2');
timer.changeTime(2, 0, true);
} else if (e.altKey) {
// console.log('xx2');
timer.changeTime(0, 2);
}
break;
}
// [-]
case 188:
case 189: {
if (e.ctrlKey) {
// console.log('-2');
timer.changeTime(-2, 0, true);
} else if (e.altKey) {
// console.log('xx-2');
timer.changeTime(0, -2);
}
break;
}
// [0]
case 48: {
if (e.ctrlKey || e.altKey) {
// console.log('reset');
timer.changeTime(0, 0, false, true);
}
break;
}
default:
// console.log(e);
}
});
},
/**
* 当计时器速率被改变时调用的回调方法
* @param percentage
* @private
*/
percentageChangeHandler: function (percentage) {
// 改变所有的循环计时
util.ergodicObject(timerContext, timerContext._intervalIds, function (idObj, id) {
idObj.args[1] = Math.floor((idObj.originMS || 1) * percentage);
// 结束原来的计时器
this._clearInterval.call(window, idObj.nowId);
// 新开一个计时器
idObj.nowId = this._setInterval.apply(window, idObj.args);
});
// 改变所有的延时计时
util.ergodicObject(timerContext, timerContext._timeoutIds, function (idObj, id) {
var now = this._Date.now();
var exceptTime = idObj.exceptNextFireTime;
var oldPercentage = idObj.oldPercentage;
var time = exceptTime - now;
if (time < 0) {
time = 0;
}
var changedTime = Math.floor(percentage / oldPercentage * time);
idObj.args[1] = changedTime;
// 重定下次执行时间
idObj.exceptNextFireTime = now + changedTime;
idObj.oldPercentage = percentage;
// 结束原来的计时器
this._clearTimeout.call(window, idObj.nowId);
// 新开一个计时器
idObj.nowId = this._setTimeout.apply(window, idObj.args);
});
},
hookShadowRoot: function () {
var origin = Element.prototype.attachShadow;
eHookContext.hookAfter(Element.prototype, 'attachShadow',
function (m, args, result) {
extraElements.push(result);
return result;
}, false);
eHookContext.hookedToString(origin, Element.prototype.attachShadow);
},
hookDefine: function () {
const _this = this;
eHookContext.hookBefore(Object, 'defineProperty', function (m, args) {
var option = args[2];
var ele = args[0];
var key = args[1];
var afterArgs = _this.hookDefineDetails(ele, key, option);
afterArgs.forEach((arg, i) => {
args[i] = arg;
})
});
eHookContext.hookBefore(Object, 'defineProperties', function (m, args) {
var option = args[1];
var ele = args[0];
if (ele && ele instanceof Element) {
Object.keys(option).forEach(key => {
var o = option[key];
var afterArgs = _this.hookDefineDetails(ele, key, o);
args[0] = afterArgs[0];
delete option[key];
option[afterArgs[1]] = afterArgs[2]
})
}
})
},
hookDefineDetails: function (target, key, option) {
if (option && target && target instanceof Element && typeof key === 'string' && key.indexOf('on') >= 0) {
option.configurable = true;
}
if (target instanceof HTMLVideoElement && key === 'playbackRate') {
option.configurable = true;
console.warn('[Timer Hook]', '已阻止默认操作视频倍率');
key = 'playbackRate_hooked'
}
return [target, key, option];
},
suppressEvent: function (ele, eventName) {
if (ele) {
delete ele['on' + eventName];
delete ele['on' + eventName];
delete ele['on' + eventName];
ele['on' + eventName] = undefined;
}
if (!suppressEvents[eventName]) {
eHookContext.hookBefore(EventTarget.prototype, 'addEventListener',
function (m, args) {
var eName = args[0];
if (eventName === eName) {
console.warn(eventName, 'event suppressed.')
args[0] += 'suppressed';
}
}, false);
suppressEvents[eventName] = true;
}
},
changePlaybackRate: function (ele, rate) {
delete ele.playbackRate;
delete ele.playbackRate;
delete ele.playbackRate;
ele.playbackRate = rate
if (rate !== 1) {
timerContext.defineProperty.call(Object, ele, 'playbackRate', {
configurable: true,
get: function () {
return 1;
},
set: function () {
}
});
}
}
}
};
var normalUtil = {
isInIframe: function () {
let is = global.parent !== global;
try {
is = is && global.parent.document.body.tagName !== 'FRAMESET'
} catch (e) {
// ignore
}
return is;
},
listenParentEvent: function (handler) {
global.addEventListener('message', function (e) {
var data = e.data;
var type = data.type || '';
if (type === 'changePercentage') {
handler(data.percentage || 0);
}
})
},
sentChangesToIframe: function (percentage) {
var iframes = document.querySelectorAll('iframe') || [];
var frames = document.querySelectorAll('frame');
if (iframes.length) {
for (var i = 0; i < iframes.length; i++) {
iframes[i].contentWindow.postMessage(
{type: 'changePercentage', percentage: percentage}, '*');
}
}
if (frames.length) {
for (var j = 0; j < frames.length; j++) {
frames[j].contentWindow.postMessage(
{type: 'changePercentage', percentage: percentage}, '*');
}
}
}
};
var querySelectorAll = function (ele, selector, includeExtra) {
var elements = ele.querySelectorAll(selector);
elements = Array.prototype.slice.call(elements || []);
if (includeExtra) {
extraElements.forEach(function (element) {
elements = elements.concat(querySelectorAll(element, selector, false));
})
}
return elements;
};
var generate = function () {
return function (util) {
// disable worker
workerURLs.forEach(function (url) {
if (util.urlMatching(location.href, 'http.*://.*' + url + '.*')) {
window['Worker'] = undefined;
console.log('Worker disabled');
}
});
var eHookContext = this;
var timerHooker = {
// 用于储存计时器的id和参数
_intervalIds: {},
_timeoutIds: {},
_auoUniqueId: 1,
// 计时器速率
__percentage: 1.0,
// 劫持前的原始的方法
_setInterval: window['setInterval'],
_clearInterval: window['clearInterval'],
_clearTimeout: window['clearTimeout'],
_setTimeout: window['setTimeout'],
_Date: window['Date'],
__lastDatetime: new Date().getTime(),
__lastMDatetime: new Date().getTime(),
videoSpeedInterval: 1000,
defineProperty: Object.defineProperty,
defineProperties: Object.defineProperties,
genUniqueId: function () {
return this._auoUniqueId++;
},
notifyExec: function (uniqueId) {
var _this = this;
if (uniqueId) {
// 清除 timeout 所储存的记录
var timeoutInfos = Object.values(this._timeoutIds).filter(
function (info) {
return info.uniqueId === uniqueId;
}
);
timeoutInfos.forEach(function (info) {
_this._clearTimeout.call(window, info.nowId);
delete _this._timeoutIds[info.originId]
})
}
// console.log(uniqueId, 'called')
},
/**
* 初始化方法
*/
init: function () {
var timerContext = this;
var h = helper(eHookContext, timerContext, util);
h.hookDefine();
h.applyHooking();
// 设定百分比属性被修改的回调
Object.defineProperty(timerContext, '_percentage', {
get: function () {
return timerContext.__percentage;
},
set: function (percentage) {
if (percentage === timerContext.__percentage) {
return percentage;
}
h.percentageChangeHandler(percentage);
timerContext.__percentage = percentage;
return percentage;
}
});
if (!normalUtil.isInIframe()) {
console.log('[TimeHooker]', 'loading outer window...');
h.applyUI();
h.applyGlobalAction(timerContext);
h.registerShortcutKeys(timerContext);
} else {
console.log('[TimeHooker]', 'loading inner window...');
normalUtil.listenParentEvent((function (percentage) {
console.log('[TimeHooker]', 'Inner Changed', percentage)
this.change(percentage);
}).bind(this))
}
},
/**
* 调用该方法改变计时器速率
* @param percentage
*/
change: function (percentage) {
this.__lastMDatetime = this._mDate.now();
this.__lastDatetime = this._Date.now();
this._percentage = percentage;
var oldNode = document.getElementsByClassName('_th-click-hover');
var oldNode1 = document.getElementsByClassName('_th_times');
var displayNum = (1 / this._percentage).toFixed(2);
(oldNode[0] || {}).innerHTML = 'x' + displayNum;
(oldNode1[0] || {}).innerHTML = 'x' + displayNum;
var a = document.getElementsByClassName('_th_cover-all-show-times')[0] || {};
a.className = '_th_cover-all-show-times';
this._setTimeout.bind(window)(function () {
a.className = '_th_cover-all-show-times _th_hidden';
}, 100);
this.changeVideoSpeed();
normalUtil.sentChangesToIframe(percentage);
},
changeVideoSpeed: function () {
var timerContext = this;
var h = helper(eHookContext, timerContext, util);
var rate = 1 / this._percentage;
rate > 16 && (rate = 16);
rate < 0.065 && (rate = 0.065);
var videos = querySelectorAll(document, 'video', true) || [];
if (videos.length) {
for (var i = 0; i < videos.length; i++) {
h.changePlaybackRate(videos[i], rate);
}
}
}
};
// 默认初始化
timerHooker.init();
return timerHooker;
}
};
if (global.eHook) {
global.eHook.plugins({
name: 'timer',
/**
* 插件装载
* @param util
*/
mount: generate()
});
}
}(window);
佬友们,分享一下我最近开发的 Excel 智能数据分析软件–ExcelMind
算是在 L 站第一次分享我的开源项目,希望佬友们多多提 Issue,多多 Star。
下面是演示视频:
这个项目是基于 LangGraph 开发的,支持自然语言查询、多轮对话、流式输出、ECharts 图表可视化 和可视化思考过程。
GitHub 地址:GitHub - stark-456/ExcelMind: AI 智能分析 Excel 文件,对话式完成多场景 Excel 分析任务,解决 Excel 报表分析复杂、效率低等痛点
上传 Excel 文件后,我们可以用自然语言跟 AI 对话,AI 会自主决策,自主调用工具,完成 Excel 的分析任务。
原理呢,其实是只给 AI 看一部分 Excel 的局部,让 AI 了解表结构之后,调用十个工具来完成分析任务,避免 AI 直接看数据做分析带来幻觉,是我觉得做分析必须要考虑到的。
AI 的分析过程,我都尽可能做了显式的输出,并做了前端优化让工具调用更易于阅读,目的是让 AI 做的每一步都是易于追溯的,这样可以让分析过程摆脱黑盒,让我们对分析过程掌控度更高,即使是出错了,也容易改正。
这里我先加入了 bar (柱状图), line (折线图), pie (饼图),scatter (散点图), radar (雷达图), funnel (漏斗图)。你可以指定 AI 输出什么图,如果不指定,AI 会自主决策输出什么图
考虑到有时候我们的 Excel 文档有很多 AI 不易理解的字段或信息,我加入了知识库功能,会在每次问答前进行召回,这样,有一些特殊需求我们就可以放在知识库里
这个是考虑到有时候需要多表联查,但是很多朋友没有数据库基础,这里选定两张表,可以触发 AI 推荐联表的外键跟连接方式,实测,还是很准的,基本上不用自己去考虑怎么联表方式。
实测,模型能力越强,回答越精准,所以推荐佬们用 Sota 模型,站里很多公益站的模型就很不错!
大概功能就是这些,希望有建议的佬随时互动,我会认真看并改进。
以后会不断开发 AI 智能体项目,并开源给大家,希望多多支持!
GitHub 地址:GitHub - stark-456/ExcelMind: AI 智能分析 Excel 文件,对话式完成多场景 Excel 分析任务,解决 Excel 报表分析复杂、效率低等痛点
书接上回:【Github 开源】Obsidian 插件:obsidian-github-stars-manager
小工具,加了一些小更新,不多, GitHub: 代码仓库
1、Obsidian 插件上架周期大概多久,PR 一直处于原地打转的状态
2、有没有什么检验代码规范的插件或者其他工具,每次 PR 都要等待,然后解决 bot 提的不规范代码,目前用的 eslint-plugin-obsidianmd(Claude 给的建议)
3、当前使用 json 存储数据,但是有同学反馈 star 数超过 600 会有卡顿现象,有没有必要换 SQLite 或者 LokiJS(AI 给的建议)
添加标签颜色,同步选择显示颜色
标签信息可修改
删除主题切换功能,鸡肋
优化性能
Anyrouter、Wong、随时跑路、Cubence、Duckcoding、我爱 996
看过 afimory 这样的开源项目,更多像是纯粹的的照片展览了,与我的需求稍微有点不一样
mo gallary 是一个基于 nextjshonojs 的项目,由于 honojs 的特性,可以直接部署在 vercel,专为摄影爱好者打造的照片博客平台。
类似于博客,但其更偏向于照片叙事,生活分享,个人展示方面
当前存在博客菜单,感觉和其本质有点出入,未来可能会将博客砍掉
全部 vibe coding 的项目,bug 有点多,就不便直接开源,待我再打磨打磨
预览地址:
移动端访问问题较多,建议 pc 访问
欢迎提供建议
下载地址:
特点:
下载最新版本 esimplus,输入优惠码 NYGIFT2026(不是 AFF,是官方活动)就能获取一个月免费美国手机号,快去试试,随时失效,反正我领到了,好像说是只能 ios。领取后要邮件联系激活手机号,怕麻烦的可以润了,我挺怕麻烦的
为了验证是否是个例,我又注册了一个账号,又领到了,不过一个 ip 只能领取一次,这个号码可以更换的,不过是随机更换
一直没注意过 其实挺惊讶的 瑞典这地方游戏开发这么强
快速阅读:
https://www.thegamer.com/steam-valve-global-generated-revenue-alinea-analytics/
https://store.epicgames.com/zh-CN/p/viewfinder-61691c
使用一台拍立得相机,挑战你的认知、重新定义现实,并重塑周围的世界。《Viewfinder》是一款全新单人游戏,让玩家在数小时的有趣体验中揭开开遗留的谜团。
看着佬友开源了基于智谱的手机助手的 Android 应用,感觉很高级,使用了一下,感觉还是不是我想的那种效果,想到以前的 autojs 就支持了类似的自动化,想着给它加上 mcp 服务端,不就是一个 ai 的自动化工具了吗。
在驱动 codex-5_2 进行几轮对话后,就帮我集成了这个,通过 mcp 就能够驱动 autojs 来完成相应的
源码地址: jinhan1414/AutoX: A UiAutomator on android, does not need root access (安卓平台上的 JavaScript 自动化工具)
文档: AutoX/docs/MCP_USAGE.md at setup-v7 · jinhan1414/AutoX
安装包: Release test · jinhan1414/AutoX
——————事情又双叒是这样的——————
由于我本人想在 Mac 上爽看小说,但是苦于各大平台广告贼多,像类似于 Android 的开源阅读【Legado】在 Mac 端找不到相关的资源。于是,趁着手头上还有一些闲下来的时间,用 Swift 和 SwiftUI 自己写了一个在 Mac 端上运行的阅读 App【Legado for Mac】,基于原版 Andorid 的开源阅读。
最重要的书源、订阅,都可以平滑地从安卓中迁移过来,在【导入书源】后【解析】,基本实现了【在线找书】-> 【阅读】 → 【换源】 和 【订阅】 → 【解析】超方便的一体化工具链。并且在阅读逻辑上,基本保持了人类的阅读习惯。
下载地址:Release Legado for Mac v1.0.0 · Kequans/legado-for-mac-pub · GitHub
书架页面:
阅读页面:
书籍搜索页面:
书源管理页面:
订阅管理页面:
关于页面:
项目简介Legado macOS 版本
这是 Legado(开源阅读)的 macOS 原生版本,使用 Swift 和 SwiftUI 构建。
功能特性
核心功能
- 自定义书源管理 - 支持导入、编辑、分组管理书源
- 本地 TXT/EPUB 阅读 - 支持多种格式本地书籍
- 在线书籍搜索和阅读 - 基于书源规则的网络书籍搜索
- 书架管理 - 书籍分组、排序、封面管理
- 阅读进度同步 - 章节索引和滚动位置的精确记忆
- 自定义阅读界面 - 字体、颜色、行距等可调
- 替换规则和净化功能 - 自定义文本替换和内容过滤
高级特性
- JavaScript 书源支持 - 部分支持书源中的 JS 脚本(java.ajax, java.get/put 等)
- 智能章节缓存 - 自动预加载后续章节,提升阅读体验
- 精确位置恢复 - 记住每本书的滚动位置,重新打开直达上次阅读处
- 并发网络请求 - 批量搜索和章节预加载的性能优化
- 自动过期清理 - 智能管理缓存空间,自动清理 30 天前的章节
系统要求
- macOS 13.0 或更高版本
使用方法用户安装步骤:
- 下载
Legado.app或Legado.dmg- 拖拽到「应用程序」文件夹
- 首次运行时在「系统偏好设置 > 隐私与安全性」中允许
后续代码会陆续开源。
(有 BUG,可以积极回帖反馈呀,虽然可能改不动 wuwuwu)
开源地址:
(因为目前一些拓展功能还在开发,所以暂时先把部分核心代码开源了,还有一些拓展功能后续再做…… 不知道有没有时间)
下载地址:Release Legado for Mac v1.0.0 · Kequans/legado-for-mac-pub · GitHub
觉得本项目不错的话,可以用 LDC 赞助一下帮帮我,Vibe coding 的额度要上天了,让我回回血呜呜呜 ~~~~||
——————————
-------------------- 附赞助链接:(LDC 打赏,最低 0.01,都是支持!)-----------------------
在数学推理任务中,相比经 vLLM 优化的 Qwen3-8B,速度提升 3–6 倍
在大多数基准测试中,性能超越原始的 Qwen3-8B-Instruct
原生支持 KV Cache(兼容 FlashAttention、PagedAttention、CUDA Graphs)
之前尝试过各种方法想用上反重力,但一直没能成功。今天终于借助 Antigravity-Manager 这个项目实现了,体验相当刺激!
前往 v3.2.0 Release 页面 下载适合 Mac 的安装包。
安装完成后,在终端执行以下命令解除系统的安全限制:
sudo xattr -rd com.apple.quarantine "/Applications/Antigravity Tools.app" 打开应用后,登录你的账号:
进入 API 反代设置页面:
点击 "启动" 按钮:
配置完成后,我测试了几个模型,效果不错。
使用 OpenAI SDK 调用 Claude Opus 4.5 模型:
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8045/v1",
api_key="sk-7cb9cb5ac43243b2b44c6139ecd8e24e"
)
response = client.chat.completions.create(
model="claude-opus-4-5-thinking",
messages=[{
"role": "user",
"content": "在白板上解:"一把钥匙开一把锁。现在有5把锁和相应的5把钥匙,但是全部放乱了,最多试几次,就可以打开全部的锁。",清晰地展示解题步骤"
}]
)
print(response.choices[0].message.content)
运行结果:
这个模型支持生成图像,可以通过 size 参数或模型后缀指定输出比例:
from openai import OpenAI
import base64
client = OpenAI(
base_url="http://127.0.0.1:8045/v1",
api_key="sk-7cb9cb5ac43243b2b44c6139ecd8e24e"
)
response = client.chat.completions.create(
model="gemini-3-pro-image",
# 方式 1: 使用 size 参数(推荐) # 方式 2: 使用模型后缀 # 例如: gemini-3-pro-image-16-9, gemini-3-pro-image-4-3 # model="gemini-3-pro-image-16-9",
messages=[{
"role": "user",
"content": "在白板上解:"一把钥匙开一把锁。现在有5把锁和相应的5把钥匙,但是全部放乱了,最多试几次,就可以打开全部的锁。",清晰地展示解题步骤"
}]
)
# 提取并保存图像
temp = response.choices[0].message.content
image_data = temp.split("[1][:-1]
with open("/Users/darbra/Downloads/result.jpg", 'wb') as f:
f.write(base64.b64decode(image_data))
生成的白板图:
整个配置过程其实并不复杂,关键是找到靠谱的工具。Antigravity-Manager 这个项目维护得不错,Mac 端体验流畅。
正如那句话说的:“得不到的永远在骚动”—— 真正用上了之后,好像也就那么回事。不过既然成功了,分享出来给有需要的朋友。
本人是在 Windows 环境配合 vscode、cursor 使用。
1、下载 exe 文件,并把路径配置到系统环境变量 Path:
2、进入代码项目
执行 codanna init,这步会自动生成配置文件 settings.toml
3、调整 settings.toml, 比如嵌入模型、开启文档索引等
[semantic_search] enabled = true # Model to use for embeddings # Note: Changing models requires re-indexing (codanna index --force) # - AllMiniLML6V2: English-only, 384 dimensions (default) # - MultilingualE5Small: 94 languages including, 384 dimensions (recommended for multilingual) # - MultilingualE5Base: 94 languages, 768 dimensions (better quality) # - MultilingualE5Large: 94 languages, 1024 dimensions (best quality) # - BGESmallZHV15: Chinese-specialized, 512 dimensions # - See documentation for full list of available models model = "MultilingualE5Large" # Similarity threshold for search results (0.0 to 1.0) threshold = 0.6 [documents] enabled = true [documents.collections.docs] paths = ["."]
patterns = ["**/*.md", "**/*.txt"]
4、开始建立索引,这步会下载模型(首次),需要配置好网络,下载速度视网络情况决定
代码索引:
codanna index . --force
文档索引(文档编码需要是 utf-8 格式):
codanna documents index --progress
5、MCP 配置(我的样例,仅供参考)
Vscode:
{
"mcpServers": {
"codanna": {
"command": "D:\\soft\\codanna\\codanna.exe",
"args": [
"serve",
"--watch"
],
"alwaysAllow": [
"analyze_impact",
"find_callers",
"get_index_info",
"find_symbol",
"semantic_search_docs",
"semantic_search_with_context",
"search_symbols",
"get_calls",
"search_documents"
]
}
}
}
Cursor:
{
"mcpServers": {
"codanna": {
"command": "D:\\soft\\codanna\\codanna.exe",
"args": [
"--config",
"D:\\code\\xxx\\settings.toml",
"serve",
"--watch"
],
"alwaysAllow": [
"analyze_impact",
"find_callers",
"get_index_info",
"find_symbol",
"semantic_search_docs",
"semantic_search_with_context",
"search_symbols",
"get_calls",
"search_documents"
]
}
}
}
6、提示词加入:使用codanna mcp进行语义检索
从原贴继续:IDEA 版 Claude Code GUI 插件 开发记录帖
本项目主要解决 IDEA 使用 Claude Code 没有可视化操作页面的问题
不知不觉已经开发一个多月了
以为老帖子已经无法修改编辑了,所以更换到本帖进行 v0.2 版本的开发记录
目前版本为 v0.1.3 版本(还未发布)
正在内测 Codex Cli 功能,预计本周三发商店版
以下均为实机演示,功能已经实现,正在内测中(内测两天找 BUG,静静期待)
本插件已上架 Jetbrains 市场,搜索 claude code gui 安装即可
本项目后续不打算进行任何商业化行为,请放心食用
声明:政府部门,国企,学校 修改本项目代码,可遵循 MIT 协议,不需要遵守 AGPL-3.0 协议
感谢所有帮助 IDEA-Claude-Code-GUI 变得更好的贡献者!和感谢 L 站平台
本插件在极速更新中,每天预计发布 1 版,佬友们记得及时更新哦~