MoreRSS

site iconlklog | 龙鲲修改

马拉松爱好者,喜欢捣腾新鲜的事物。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

lklog | 龙鲲的 RSS 预览

在线文本提取链接自动转换二维码工具

2025-07-25 18:56:54

本文于 2025年7月25日 11:04 更新,注意查看最新内容

======2025.07.25 23:00 更新======

原有提取链接的正则表达式不够严谨,自己使用时提取出现了问题,现进行修复。

// 使用更精确的正则表达式
const urlRegex = /(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g;

参考:https://www.cnblogs.com/speeding/p/5097790.html

======2025.07.25原文======

因为个人需求,经常需要从文本中提取链接转换成二维码使用,于是用AI写了一个小工具。

核心功能:提取文本中的单个链接转换成二维码

线上地址:https://lab.lklog.cn/QRcode/

代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>链接转二维码</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            text-align: center;
        }
        h1 {
            margin: 20px 0;
            color: #333;
        }
        textarea {
            width: 100%;
            height: 150px;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
        }
        button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 12px 24px;
            text-align: center;
            text-decoration: none;
            display: block;
            font-size: 16px;
            margin: 10px auto;
            cursor: pointer;
            border-radius: 4px;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #45a049;
        }
        #qrcode {
            margin: 30px auto;
            padding: 20px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
            display: none;
        }
        .result-url {
            margin-top: 20px;
            word-break: break-all;
            color: #666;
            font-size: 14px;
        }
        .error-message {
            color: red;
            margin-top: 10px;
            display: none;
        }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/qrcode.min.js"></script>
</head>
<body>
    <h1>链接转二维码</h1>
    <textarea id="textInput" placeholder="请粘贴包含单个链接的文本"></textarea>
    <button onclick="generateQRCode()">生成二维码</button>
    <div class="error-message" id="errorMessage"></div>
    <div id="qrcode"></div>
    <div class="result-url" id="resultUrl"></div>

    <script>
        function generateQRCode() {
            const textInput = document.getElementById('textInput').value.trim();
            const urlRegex = /(https?:\/\/[^\s]+)/g;
            const matches = textInput.match(urlRegex);
            
            // 隐藏之前的结果和错误信息
            document.getElementById('qrcode').style.display = 'none';
            document.getElementById('resultUrl').textContent = '';
            document.getElementById('errorMessage').style.display = 'none';
            
            if (!textInput) {
                showError('请输入内容');
                return;
            }
            
            if (!matches) {
                showError('未找到有效的HTTP/HTTPS链接');
                return;
            }
            
            if (matches.length > 1) {
                showError('找到多个链接,请只输入一个链接');
                return;
            }
            
            const link = matches[0];
            
            // 显示提取的链接
            document.getElementById('resultUrl').textContent = `提取的链接: ${link}`;
            
            // 生成二维码
            const qr = qrcode(4, 'L');
            qr.addData(link);
            qr.make();
            
            const qrcodeDiv = document.getElementById('qrcode');
            // 调整二维码大小为原来的60%
            qrcodeDiv.innerHTML = qr.createImgTag(6, 0);
            qrcodeDiv.style.display = 'inline-block';
        }
        
        function showError(message) {
            const errorMessage = document.getElementById('errorMessage');
            errorMessage.textContent = message;
            errorMessage.style.display = 'block';
        }
    </script>
</body>
</html>

 

在线文本提取链接自动转换二维码工具最先出现在龙鲲博客

在线文本比对工具(附源码)

2025-07-14 16:07:51

本文于 2025年7月25日 6:36 更新,注意查看最新内容

======2025.07.25日更新======

1、上传功能修复:原代码中const editor = doc.editor(side); 改成 const editor = doc.cm(side); (感谢友链@倦意博客指正)

2、部分样式调整:由于原代码内字体大小及颜色不够明显,线上部署版本进行了如下调整:

 /* 新增右侧编辑器样式 */
.mergely-editor .mergely.ch.ina.rhs {
    color: #FFA500 !important;
    text-decoration: underline;
}
.CodeMirror {
    font-size: 1.125rem; /* 默认字体大小 */
}

======2025.07.14日原文======

因为经常使用在线文本对比,但不太放心使用公共的在线文本比对工具,于是使用AI编写了相关的功能,代码如下(若要实现离线使用,将静态资源文件全部替换为本地文件即可):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>在线文本比对工具</title>
    <meta name="description" content="纯前端在线文本比对工具,无需网络即可使用。支持文本差异高亮显示、文件上传等功能。">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css" rel="stylesheet">
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/lib/codemirror.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/theme/material.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/lib/mergely.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/codemirror.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/addon/search/searchcursor.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/mergely.min.js"></script>
    
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#4B5168',
                        secondary: '#8D426C',
                        tertiary: '#5199D3',
                        accent: '#22C994',
                        warning: '#F0C612',
                        danger: '#D2465D',
                        info: '#FFAB4D',
                    },
                    fontFamily: {
                        inter: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .text-shadow {
                text-shadow: 0 2px 4px rgba(0,0,0,0.1);
            }
            .bg-gradient-primary {
                background: linear-gradient(90deg, #8D426C 16%, #5199D3 16%, #5199D3 32%, #22C994 32%, #22C994 48%, #F0C612 48%, #F0C612 64%, #FFAB4D 64%, #FFAB4D 82%, #D2465D 82%);
            }
            .border-gradient {
                border-image: linear-gradient(90deg, #8D426C 16%, #5199D3 16%, #5199D3 32%, #22C994 32%, #22C994 48%, #F0C612 48%, #F0C612 64%, #FFAB4D 64%, #FFAB4D 82%, #D2465D 82%) 1;
            }
        }
    </style>
</head>
<body class="font-inter bg-gray-50 min-h-screen flex flex-col">
    <!-- 顶部导航栏 -->
    <header class="bg-primary text-white shadow-md sticky top-0 z-50">
        <div class="container mx-auto px-4 py-3 flex items-center justify-between">
            <div class="flex items-center">
                <i class="fa fa-file-code-o text-2xl mr-2 text-accent"></i>
                <h1 class="text-xl font-bold">文本比对工具</h1>
            </div>
            
            <button class="md:hidden text-white focus:outline-none" id="mobile-menu-button">
                <i class="fa fa-bars text-xl"></i>
            </button>
        </div>
        
        <!-- 底部渐变线 -->
        <div class="h-1 bg-gradient-primary"></div>
    </header>

    <!-- 移动端菜单 (默认隐藏) -->
    <div class="md:hidden hidden bg-primary text-white shadow-lg absolute w-full z-40" id="mobile-menu">
        <!-- 空内容,移除帮助和关于链接 -->
    </div>

    <!-- 主内容区 -->
    <main class="flex-grow container mx-auto px-4 py-8">
        <div class="max-w-7xl mx-auto">
            <!-- 工具标题和操作区 -->
            <div class="mb-6 flex flex-col md:flex-row md:items-center md:justify-between">
                <div class="mb-4 md:mb-0">
                    <h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-gray-800 flex items-center">
                        <i class="fa fa-file-signature text-accent mr-3"></i>
                        在线文本比对工具
                    </h2>
                    <p class="text-gray-600 mt-1">比较两个文本的差异,高亮显示修改、添加和删除的部分</p>
                </div>
                
                <div class="flex flex-wrap gap-2">
                    <button id="uploadFile" class="px-4 py-2 bg-warning hover:bg-warning/90 text-gray-800 rounded-lg shadow transition-all duration-200 flex items-center">
                        <i class="fa fa-cloud-upload-alt mr-2"></i>上传文件
                    </button>
                </div>
            </div>
            
            <!-- 文件上传区域 (默认隐藏) -->
            <div id="fileArea" class="mb-6 bg-gray-100 p-4 rounded-lg shadow-sm hidden transition-all duration-300">
                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div>
                        <label for="lhsFile" class="block text-sm font-medium text-gray-700 mb-1">左侧文件</label>
                        <input type="file" id="lhsFile" class="w-full text-sm text-gray-500
                            file:mr-4 file:py-2 file:px-4
                            file:rounded-lg file:border-0
                            file:text-sm file:font-medium
                            file:bg-accent file:text-white
                            hover:file:bg-accent/90">
                    </div>
                    <div>
                        <label for="rhsFile" class="block text-sm font-medium text-gray-700 mb-1">右侧文件</label>
                        <input type="file" id="rhsFile" class="w-full text-sm text-gray-500
                            file:mr-4 file:py-2 file:px-4
                            file:rounded-lg file:border-0
                            file:text-sm file:font-medium
                            file:bg-tertiary file:text-white
                            hover:file:bg-tertiary/90">
                    </div>
                </div>
            </div>
            
            <!-- 文本比对区域 -->
            <div class="bg-white rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl">
                <div id="compare1" class="w-full h-[60vh] min-h-[400px]"></div>
            </div>
            
            <!-- 功能说明卡片 -->
            <div class="mt-8 bg-white rounded-xl shadow-lg p-6 transition-all duration-300 hover:shadow-xl">
                <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center">
                    <i class="fa fa-info-circle text-tertiary mr-2"></i>使用说明
                </h3>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div class="flex items-start">
                        <div class="flex-shrink-0 bg-tertiary/10 p-3 rounded-full">
                            <i class="fa fa-edit text-tertiary text-xl"></i>
                        </div>
                        <div class="ml-4">
                            <h4 class="text-lg font-medium text-gray-800">直接输入文本</h4>
                            <p class="mt-1 text-gray-600">在左右两个编辑器中直接输入或粘贴要比较的文本,系统会自动高亮显示差异。</p>
                        </div>
                    </div>
                    <div class="flex items-start">
                        <div class="flex-shrink-0 bg-accent/10 p-3 rounded-full">
                            <i class="fa fa-upload text-accent text-xl"></i>
                        </div>
                        <div class="ml-4">
                            <h4 class="text-lg font-medium text-gray-800">上传文件</h4>
                            <p class="mt-1 text-gray-600">点击"上传文件"按钮,选择要比较的两个文本文件(支持TXT、HTML、JSON等纯文本格式)。</p>
                        </div>
                    </div>
                    <div class="flex items-start">
                        <div class="flex-shrink-0 bg-warning/10 p-3 rounded-full">
                            <i class="fa fa-search text-warning text-xl"></i>
                        </div>
                        <div class="ml-4">
                            <h4 class="text-lg font-medium text-gray-800">查看差异</h4>
                            <p class="mt-1 text-gray-600">绿色标记表示新增内容,红色标记表示删除内容,黄色标记表示修改内容。</p>
                        </div>
                    </div>
                    <div class="flex items-start">
                        <div class="flex-shrink-0 bg-info/10 p-3 rounded-full">
                            <i class="fa fa-mobile text-info text-xl"></i>
                        </div>
                        <div class="ml-4">
                            <h4 class="text-lg font-medium text-gray-800">响应式设计</h4>
                            <p class="mt-1 text-gray-600">在任何设备上都能获得良好的使用体验,支持从手机到桌面的各种屏幕尺寸。</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </main>

    <!-- 页脚 -->
    <footer class="bg-primary text-white mt-12">
        <!-- 顶部渐变线 -->
        <div class="h-1 bg-gradient-primary"></div>
        
        <div class="container mx-auto px-4 py-6">
            <div class="flex justify-center items-center">
                <p class="text-sm text-gray-300">&copy; 2025 文本比对工具</p>
            </div>
        </div>
    </footer>

    <!-- 模态框 (用于提示信息) -->
    <div id="alertModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
        <div class="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 transform transition-all">
            <div class="text-center">
                <div id="modalIcon" class="mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4">
                    <i class="fa fa-check text-green-500 text-2xl"></i>
                </div>
                <h3 id="modalTitle" class="text-lg font-medium text-gray-900 mb-2">操作成功</h3>
                <p id="modalMessage" class="text-gray-500 mb-6">您的操作已成功完成。</p>
                <button id="modalClose" class="px-4 py-2 bg-primary hover:bg-primary/90 text-white rounded-lg shadow transition-all duration-200">
                    确定
                </button>
            </div>
        </div>
    </div>

    <script>
        // 全局变量存储Mergely实例
        let doc;

        document.addEventListener('DOMContentLoaded', function() {
            // 初始化Mergely实例
            initMergely();

            // 移动端菜单切换
            document.getElementById('mobile-menu-button').addEventListener('click', function() {
                const mobileMenu = document.getElementById('mobile-menu');
                mobileMenu.classList.toggle('hidden');
            });

            // 上传文件区域显示/隐藏
            document.getElementById('uploadFile').addEventListener('click', function() {
                const fileArea = document.getElementById('fileArea');
                fileArea.classList.toggle('hidden');
                
                // 添加动画效果
                if (!fileArea.classList.contains('hidden')) {
                    fileArea.style.maxHeight = '0';
                    setTimeout(() => {
                        fileArea.style.maxHeight = '200px';
                    }, 10);
                } else {
                    fileArea.style.maxHeight = '200px';
                    setTimeout(() => {
                        fileArea.style.maxHeight = '0';
                    }, 10);
                }
            });

            // 处理文件上传
            document.getElementById('lhsFile').addEventListener('change', function() {
                handleFileInput(this, 'lhs');
            });

            document.getElementById('rhsFile').addEventListener('change', function() {
                handleFileInput(this, 'rhs');
            });

            // 窗口大小改变时调整Mergely大小
            window.addEventListener('resize', function() {
                if (doc) {
                    doc.resize();
                }
            });
        });

        // 初始化Mergely实例
        function initMergely() {
            doc = new Mergely('#compare1', {
                width: 'auto',
                license: 'lgpl-separate-notice',
                cmsettings: {
                    readOnly: false,
                    lineWrapping: true,
                    theme: 'material',
                    lineNumbers: true
                },
                lhs: setValue => setValue('欢迎使用文本比对工具\n请在此处输入或粘贴要比较的第一个文本'),
                rhs: setValue => setValue('Welcome to the text comparison tool\nPlease enter or paste the second text to compare here')
            });
            
            // 同时保存到window对象以保持向后兼容性
            window.doc = doc;
        }

        // 处理文件输入
        function handleFileInput(input, side) {
            const file = input.files[0];
            if (!file) return;
            
            // 检查文件大小 (限制为30MB)
            if (file.size > 30 * 1024 * 1024) {
                showAlert('上传内容不能大于 30 MB!', 'error');
                return;
            }
            
            const reader = new FileReader();
            reader.onload = e => {
                if (doc) {
                    // 使用editor方法获取CodeMirror实例并设置值
                    const editor = doc.editor(side);
                    if (editor) {
                        editor.setValue(e.target.result);
                        showAlert(`已成功加载文件: ${file.name}`, 'success');
                    }
                }
            };
            reader.onerror = () => showAlert('读取文件时发生错误', 'error');
            reader.readAsText(file, "UTF-8");
        }

        // 显示提示框
        function showAlert(message, type = 'success') {
            const modal = document.getElementById('alertModal');
            const title = document.getElementById('modalTitle');
            const msg = document.getElementById('modalMessage');
            const icon = document.getElementById('modalIcon');
            
            // 设置提示类型
            if (type === 'success') {
                title.textContent = '操作成功';
                icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4';
                icon.innerHTML = '<i class="fa fa-check text-green-500 text-2xl"></i>';
            } else if (type === 'error') {
                title.textContent = '操作失败';
                icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-red-100 mb-4';
                icon.innerHTML = '<i class="fa fa-times text-red-500 text-2xl"></i>';
            } else if (type === 'warning') {
                title.textContent = '警告';
                icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-yellow-100 mb-4';
                icon.innerHTML = '<i class="fa fa-exclamation-triangle text-yellow-500 text-2xl"></i>';
            } else if (type === 'info') {
                title.textContent = '信息';
                icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-blue-100 mb-4';
                icon.innerHTML = '<i class="fa fa-info-circle text-blue-500 text-2xl"></i>';
            }
            
            // 设置提示消息
            msg.textContent = message;
            
            // 显示模态框
            modal.classList.remove('hidden');
            modal.classList.add('flex');
            
            // 添加动画效果
            const modalContent = modal.querySelector('div');
            modalContent.classList.add('scale-95', 'opacity-0');
            setTimeout(() => {
                modalContent.classList.remove('scale-95', 'opacity-0');
                modalContent.classList.add('scale-100', 'opacity-100');
            }, 10);
        }

        // 关闭提示框
        document.getElementById('modalClose').addEventListener('click', function() {
            const modal = document.getElementById('alertModal');
            const modalContent = modal.querySelector('div');
            
            // 添加关闭动画
            modalContent.classList.remove('scale-100', 'opacity-100');
            modalContent.classList.add('scale-95', 'opacity-0');
            
            setTimeout(() => {
                modal.classList.add('hidden');
                modal.classList.remove('flex');
            }, 300);
        });

        // 点击模态框背景关闭
        document.getElementById('alertModal').addEventListener('click', function(e) {
            if (e.target === this) {
                document.getElementById('modalClose').click();
            }
        });
    </script>
</body>
</html>

目前代码运行良好,待实现一键清空文本框内容。

在线预览地址:https://lab.lklog.cn/duibis/

参考网站:https://www.jyshare.com/front-end/8006/

在线文本比对工具(附源码)最先出现在龙鲲博客

利用热胀冷缩解决螺丝滑丝问题

2025-07-13 17:05:16

本文于 2025年7月13日 5:05 更新,注意查看最新内容

家里卫生间的锁有些不太灵,于是买了一把球形锁,打算自己换上。

在换的过程中发现原有球形锁的螺帽出现了不同程度的生锈,以及拧动过程中导致的滑丝,在搜索了诸多解决方案之后,发现了一个相对有效果的方法,这里记录一下,以备日后查阅。

使用热吹风对着螺丝吹5分钟以上,吹完之后迅速泼上冷水(冰水效果更佳),然后再尝试使用螺丝刀拧松。

原理就是热胀冷缩。在尝试此方法之前,我使用了网传的加电胶布,以及暴力拆除,均没有太多效果。

此次更换卫生间门把手,也让我得到了一些启发,第一就是在容易发生生锈的环境中,不一定要等到物品最终丧失功能再选择更换,完全可以提前更换,这样就可以避免等到真正丧失物理功能需要更换时,拆卸过程中螺丝滑丝等诸多问题。

第二就是为什么在更换卫生间球形锁的过程中,我会进入心流状态,短暂忘却了现实时间的流逝,一门心思只想如何完成更换,目前有诸多猜测,但无法形成完整结论,待进一步探讨。

第三,在安装完成之后,我意识到普通锁的内部结构其实并不是特别复杂,也没有特别的精妙,这似乎也应证了那一句,这世上本没有锁,偷的人多了,才有了锁,以及锁只防君子,防不了小人。

利用热胀冷缩解决螺丝滑丝问题最先出现在龙鲲博客

PotPlayer自动识别字幕并实时翻译(简化版)

2025-07-05 13:54:29

本文于 2025年7月5日 1:54 更新,注意查看最新内容

长话短说。需要观看一个没有字幕的外语视频,然后发现PotPlayer(以下简称:Pot)支持自动识别字幕并且可以实时翻译,这里记录一下折腾过程。

一共分为两步,第一步是自动识别字幕

Pot内单击右键,选择字幕->创建有声字幕->创建有声字幕。

转换引擎:选择Whisper-Faster,点击下载按钮,下载引擎。

型号:选择small(更高版本对算力要求更高,视情况选择)。

型号由于众所周知的原因,无法直接进行下载,可以选择使用魔搭社区进行下载。

faster-whisper-small https://www.modelscope.cn/models/angelala00/faster-whisper-small/files

下载后点击型号两字,会跳转型号存放路径,将下载的文件移动至该文件即可。

然后点击开始按钮,即可开始生成当前语言的字幕文件。

第二步则是实时翻译,原理是借助翻译产品的API来实时进行翻译。

目前内置的翻译产品API除必应首年免费以外,其他均需收费使用。

PS:必应首年免费,注册也略微麻烦,非必要不推荐尝试。

这里有热心网友开发了国内百度翻译的插件,下载地址如下:

https://github.com/fjqingyou/PotPlayer_Subtitle_Translate_Baidu

文件下载之后移动至PotPlayer\Extension\Subtitle\Translate路径,重启Pot即可正常使用。

将百度翻译的API填入Pot内,即可正常开始实时翻译,由于国内产品注册没有难度,这里不做过多介绍。

路径:选择字幕->实时字幕翻译->实时字幕翻译设置。

个人使用之后感觉效果还可以,但是问题也很多,例如,电脑配置较低的情况下,字幕识别的速度相对来说比较慢,再者实时翻译可能存在接口的调用频率限制,例如限制在多少秒钟内只允许调用多少次,所以导致部分内容无法正常显示,当然也有可能是原字幕文件本身夹杂了多种语言,这种情况下,手动翻译都会出错,而API只是代替手动,自然而然也会出错了。

参考:

https://blog.csdn.net/duke_ding2/article/details/144973709

https://www.cnblogs.com/chasingdreams2017/p/18746470

https://andi.wang/2025/03/11/potplayer+whisper+ollama%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E5%AD%97%E5%B9%95%E5%B9%B6%E7%BF%BB%E8%AF%91/

http://www.potplayercn.com/course/potplayer-generate-subtitles-from-voice.html

PotPlayer自动识别字幕并实时翻译(简化版)最先出现在龙鲲博客

学习必备:B站全自动跳过固定片头片尾 V1.4

2025-07-04 10:29:30

本文于 2025年7月4日 1:57 更新,注意查看最新内容

======2025.7.4 13:44 更新======
因为除了用B站学习以外,可能还会浏览一些其他视频,但其他视频并不需要跳转片头片尾。
所以,迭代这个版本,增加功能如下:
1、对指定BV号视频跳过片头片尾
2、可自定义每个视频的跳过时间
如果没有这两个需求,可以继续使用V1.2的版本。
PS:两个版本均兼容B站空降助手。

// ==UserScript==
// [url=home.php?mod=space&uid=170990]@name[/url]         B站指定视频片头片尾跳过
// [url=home.php?mod=space&uid=467642]@namespace[/url]    [url=http://tampermonkey.net/]http://tampermonkey.net/[/url]
// [url=home.php?mod=space&uid=1248337]@version[/url]      1.4
// @description  仅对指定BV号视频跳过片头片尾,可自定义每个视频的跳过时间
// [url=home.php?mod=space&uid=686208]@AuThor[/url] l9zp6
// [url=home.php?mod=space&uid=195849]@match[/url]        [url=https://www.bilibili.com/video/]https://www.bilibili.com/video/[/url]*
// @match        [url=https://www.bilibili.com/bangumi/play/]https://www.bilibili.com/bangumi/play/[/url]*
// [url=home.php?mod=space&uid=609072]@grant[/url]        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==
 
(function() {
    'use strict';
 
    // ========================== 配置区域 ==========================
    // 默认跳过时间(当未指定视频的自定义时间时使用)
    const DEFAULT_INTRO_SKIP_TIME = 0;  // 默认不跳过片头
    const DEFAULT_OUTRO_SKIP_TIME = 0;  // 默认不跳过片尾
 
    // 指定视频的自定义跳过时间(支持多个BV号)
    // 格式: 'BV号': { intro: 片头时间, outro: 片尾时间 }
    const CUSTOM_SKIP_TIMES = {
        'BV14yzCYgE9Y': { intro: 10, outro: 5 },  // 示例1:自定义此BV号的跳过时间
        'BV14yzCYgE9S': { intro: 5, outro: 3 },   // 示例2:添加更多BV号
        // 可继续添加更多BV号配置...
    };
    // =============================================================
 
    // 存储状态信息
    let lastVideoKey = '';          // 视频唯一标识
    let lastUrl = '';               // 上次的完整URL
    let isWaitingForNextVideo = false;  // 是否等待下一个视频
    let lastProcessTime = 0;        // 上次处理时间
    let skipCooldown = false;       // 跳过冷却标志
    let forceProcess = false;       // 强制处理标志
 
    // 添加样式
    GM_addStyle(`
        .skip-hint {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 10px 20px;
            border-radius: 5px;
            font-size: 18px;
            z-index: 10000;
            opacity: 0;
            transition: opacity 0.3s;
        }
        .skip-hint.show {
            opacity: 1;
        }
    `);
 
    // 创建提示元素
    function createHintElement() {
        const hint = document.createElement('div');
        hint.className = 'skip-hint';
        document.body.appendChild(hint);
        return hint;
    }
 
    // 获取播放器元素
    function getPlayer() {
        return document.querySelector('bwp-video, video');
    }
 
    // 获取播放器容器
    function getPlayerContainer() {
        return document.querySelector('#bilibili-player, .bpx-player-container');
    }
 
    // 显示提示
    function showHint(text) {
        let hint = document.querySelector('.skip-hint');
        if (!hint) {
            hint = createHintElement();
        }
 
        hint.textContent = text;
        hint.classList.add('show');
 
        // 获取播放器容器
        const playerContainer = getPlayerContainer();
 
        // 检查是否全屏
        const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement;
 
        if (playerContainer && !isFullscreen) {
            // 非全屏状态,将提示添加到播放器容器内
            playerContainer.appendChild(hint);
 
            // 重置位置样式
            hint.style.top = '50%';
            hint.style.left = '50%';
            hint.style.transform = 'translate(-50%, -50%)';
        } else {
            // 全屏状态,提示保持在body内
            document.body.appendChild(hint);
        }
 
        setTimeout(() => {
            hint.classList.remove('show');
        }, 2000);  // 提示显示2秒
    }
 
    // 获取当前视频的BV号
    function getCurrentBVId() {
        const pathMatch = location.pathname.match(/\/video\/(BV\w+)/);
        return pathMatch ? pathMatch[1] : '';
    }
 
    // 生成视频唯一标识(只考虑BV号和p参数)
    function generateVideoKey() {
        const bvid = getCurrentBVId();
        if (!bvid) return '';
 
        const pMatch = location.search.match(/[?&]p=(\d+)/);
        const pValue = pMatch ? `_p${pMatch[1]}` : '';
 
        return `${bvid}${pValue}`;
    }
 
    // 获取当前视频的跳过时间配置
    function getSkipTimes() {
        const bvid = getCurrentBVId();
        // 如果是指定视频,使用自定义时间,否则使用默认时间
        if (CUSTOM_SKIP_TIMES[bvid]) {
            console.log(`使用自定义跳过时间 for ${bvid}:`, CUSTOM_SKIP_TIMES[bvid]);
            return CUSTOM_SKIP_TIMES[bvid];
        }
        return {
            intro: DEFAULT_INTRO_SKIP_TIME,
            outro: DEFAULT_OUTRO_SKIP_TIME
        };
    }
 
    // 检查视频是否在指定列表中
    function isVideoIncluded() {
        const bvid = getCurrentBVId();
        return !!CUSTOM_SKIP_TIMES[bvid];
    }
 
    // 获取视频标题
    function getVideoTitle() {
        const titleElement = document.querySelector('h1.title, .media-info-title');
        return titleElement ? titleElement.textContent.trim() : '';
    }
 
    // 检查是否是新视频
    function isNewVideo() {
        const currentVideoKey = generateVideoKey();
        const currentUrl = location.href;
        let isNew = currentVideoKey !== lastVideoKey;
 
        // 如果URL相同但需要强制处理
        if (!isNew && currentUrl === lastUrl && forceProcess) {
            console.log('强制处理相同URL的视频');
            isNew = true;
            forceProcess = false;
        }
 
        if (isNew) {
            console.log('检测到新视频:', currentVideoKey);
            lastVideoKey = currentVideoKey;
            lastUrl = currentUrl;
        } else if (currentUrl !== lastUrl) {
            console.log('URL参数变化但视频未变:', currentUrl);
            lastUrl = currentUrl;
        }
 
        return isNew;
    }
 
    // 获取并更新记忆进度
    function processMemoryProgress(player) {
        // 如果不是指定视频,直接返回
        if (!isVideoIncluded()) return;
 
        const videoKey = generateVideoKey();
        if (!videoKey) return;
 
        const memoryTime = GM_getValue(`bilibili_memory_${videoKey}`, 0);
        const { intro } = getSkipTimes();
 
        // 如果记忆进度在片头时间之后,不做处理
        if (memoryTime > intro) {
            console.log(`检测到记忆进度:${memoryTime}秒,超过片头时间${intro}秒,不跳过`);
            return;
        }
 
        // 如果是新视频且进度为0,跳转到片头跳过时间
        if (player.currentTime <= intro) {
            console.log(`跳转到片头跳过时间:${intro}秒`);
            player.currentTime = intro;
            showHint(`已跳过${intro}秒片头`);
        }
    }
 
    // 检查片尾并跳过
    function checkAndSkipOutro(player, duration) {
        // 如果不是指定视频,直接返回
        if (!isVideoIncluded()) return;
 
        // 防止无限循环跳转
        if (skipCooldown) return;
 
        const { outro } = getSkipTimes();
        const currentTime = player.currentTime;
 
        // 检查是否在片尾区域
        if (duration - currentTime <= outro) {
            // 防止在短时间内重复跳转
            const now = Date.now();
            if (now - lastProcessTime < 2000) {
                console.log('检测到重复跳转尝试,跳过此次操作');
                return;
            }
            lastProcessTime = now;
 
            // 设置冷却期
            skipCooldown = true;
            setTimeout(() => {
                skipCooldown = false;
            }, 2000);
 
            // 跳转到离片尾0.5秒处,确保能触发下一集
            player.currentTime = duration - 0.5;
            console.log(`已跳过${outro}秒片尾,跳转到${duration - 0.5}秒`);
            showHint(`已跳过${outro}秒片尾`);
 
            // 标记为等待下一个视频,并设置强制处理
            isWaitingForNextVideo = true;
            forceProcess = true;
        }
    }
 
    // 检查是否应该处理新视频
    function shouldProcessNewVideo(player) {
        if (!player) return false;
 
        // 如果正在等待下一个视频且播放器已加载新视频
        if (isWaitingForNextVideo && player.currentTime < 5) {
            console.log('检测到自动连播的新视频');
            isWaitingForNextVideo = false;
            return true;
        }
 
        // 检查是否是新视频
        return isNewVideo();
    }
 
    // 主处理函数
    function processVideo() {
        const player = getPlayer();
        if (!player) return;
 
        // 检查是否应该处理新视频
        if (shouldProcessNewVideo(player)) {
            console.log('检测到需要处理的新视频:', getVideoTitle());
            // 处理记忆进度
            processMemoryProgress(player);
        }
 
        // 保存进度
        const videoKey = generateVideoKey();
        if (videoKey) {
            GM_setValue(`bilibili_memory_${videoKey}`, player.currentTime);
        }
 
        // 检查片尾
        if (player.duration && player.duration > 0) {
            checkAndSkipOutro(player, player.duration);
        }
    }
 
    // 初始化
    function init() {
        console.log('B站片头片尾跳过脚本已加载');
 
        // 监听播放器状态变化
        const player = getPlayer();
        if (player) {
            player.addEventListener('loadedmetadata', () => {
                console.log('播放器加载了新的元数据');
                processVideo();
            });
 
            player.addEventListener('timeupdate', () => {
                // 播放器时间更新时检查
                processVideo();
            });
        }
 
        // 监听URL变化
        new MutationObserver(() => {
            console.log('检测到URL变化');
            setTimeout(() => {
                processVideo();
            }, 300);
        }).observe(document, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['href']
        });
 
        // 定时检查
        setInterval(() => {
            const player = getPlayer();
            if (player && !player.paused) {
                processVideo();
            }
        }, 1000);
 
        // 初始处理
        setTimeout(processVideo, 1000);
    }
 
    // 等待页面加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

 

======2025.7.4 原文======

前言

众所周知,目前B站已经成为了最大的学习乐园之一,拥有海量的学习资源。

但在学习的过程中,很多的学习视频会考虑宣传等目的,在片头或者片尾加上与视频内容实质无关的片段,极其影响学习。

因为太久没写Javascript,以及不喜欢重复造轮子的想法,在尝试完市面上大部分的脚本之后。

我选择使用AI编写了脚本的大部分代码,目前已稳定支持全自动跳过固定片头片尾

代码

// ==UserScript==
// @name         B站片头片尾跳过
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  自动跳过B站前10秒片头和后5秒片尾,支持任意URL参数
// @author       l9zp6
// @match        https://www.bilibili.com/video/*
// @match        https://www.bilibili.com/bangumi/play/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // 配置参数
    const INTRO_SKIP_TIME = 10; // 片头跳过时间(秒)
    const OUTRO_SKIP_TIME = 5;  // 片尾跳过时间(秒)
    const SHOW_TIME = 2;        // 提示显示时间(秒)

    // 存储上一次的视频信息
    let lastVideoKey = '';      // 视频唯一标识(忽略无关参数)
    let lastUrl = '';           // 上次的完整URL
    let isWaitingForNextVideo = false; // 是否正在等待下一个视频
    let lastProcessTime = 0;    // 上次处理时间
    let skipCooldown = false;   // 跳过冷却标志
    let forceProcess = false;   // 强制处理标志

    // 添加样式
    GM_addStyle(`
        .skip-hint {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 10px 20px;
            border-radius: 5px;
            font-size: 18px;
            z-index: 10000;
            opacity: 0;
            transition: opacity 0.3s;
        }
        .skip-hint.show {
            opacity: 1;
        }
    `);

    // 创建提示元素
    function createHintElement() {
        const hint = document.createElement('div');
        hint.className = 'skip-hint';
        document.body.appendChild(hint);
        return hint;
    }

    // 获取播放器元素
    function getPlayer() {
        return document.querySelector('bwp-video, video');
    }

    // 获取播放器容器
    function getPlayerContainer() {
        return document.querySelector('#bilibili-player, .bpx-player-container');
    }

    // 显示提示
    function showHint(text) {
        let hint = document.querySelector('.skip-hint');
        if (!hint) {
            hint = createHintElement();
        }

        hint.textContent = text;
        hint.classList.add('show');

        // 获取播放器容器
        const playerContainer = getPlayerContainer();

        // 检查是否全屏
        const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement;

        if (playerContainer && !isFullscreen) {
            // 非全屏状态,将提示添加到播放器容器内
            playerContainer.appendChild(hint);

            // 重置位置样式
            hint.style.top = '50%';
            hint.style.left = '50%';
            hint.style.transform = 'translate(-50%, -50%)';
        } else {
            // 全屏状态,提示保持在body内
            document.body.appendChild(hint);
        }

        setTimeout(() => {
            hint.classList.remove('show');
        }, SHOW_TIME * 1000);
    }

    // 生成视频唯一标识(只考虑BV号和p参数)
    function generateVideoKey() {
        const pathMatch = location.pathname.match(/\/video\/(BV\w+)/);
        if (!pathMatch) return '';

        const bvid = pathMatch[1];
        const pMatch = location.search.match(/[?&]p=(\d+)/);
        const pValue = pMatch ? `_p${pMatch[1]}` : '';

        return `${bvid}${pValue}`;
    }

    // 获取视频标题
    function getVideoTitle() {
        const titleElement = document.querySelector('h1.title, .media-info-title');
        return titleElement ? titleElement.textContent.trim() : '';
    }

    // 检查是否是新视频
    function isNewVideo() {
        const currentVideoKey = generateVideoKey();
        const currentUrl = location.href;
        let isNew = currentVideoKey !== lastVideoKey;

        // 如果URL相同但需要强制处理
        if (!isNew && currentUrl === lastUrl && forceProcess) {
            console.log('强制处理相同URL的视频');
            isNew = true;
            forceProcess = false;
        }

        if (isNew) {
            console.log('检测到新视频:', currentVideoKey);
            lastVideoKey = currentVideoKey;
            lastUrl = currentUrl;
        } else if (currentUrl !== lastUrl) {
            console.log('URL参数变化但视频未变:', currentUrl);
            lastUrl = currentUrl;
        }

        return isNew;
    }

    // 获取并更新记忆进度
    function processMemoryProgress(player) {
        const videoKey = generateVideoKey();
        if (!videoKey) return;

        const memoryTime = GM_getValue(`bilibili_memory_${videoKey}`, 0);

        // 如果记忆进度在片头时间之后,不做处理
        if (memoryTime > INTRO_SKIP_TIME) {
            console.log(`检测到记忆进度:${memoryTime}秒,超过片头时间,不跳过`);
            return;
        }

        // 如果是新视频且进度为0,跳转到片头跳过时间
        if (player.currentTime <= INTRO_SKIP_TIME) {
            console.log(`跳转到片头跳过时间:${INTRO_SKIP_TIME}秒`);
            player.currentTime = INTRO_SKIP_TIME;
            showHint(`已跳过${INTRO_SKIP_TIME}秒片头`);
        }
    }

    // 检查片尾并跳过
    function checkAndSkipOutro(player, duration) {
        // 防止无限循环跳转
        if (skipCooldown) return;

        const currentTime = player.currentTime;

        // 检查是否在片尾区域
        if (duration - currentTime <= OUTRO_SKIP_TIME) {
            // 防止在短时间内重复跳转
            const now = Date.now();
            if (now - lastProcessTime < 2000) {
                console.log('检测到重复跳转尝试,跳过此次操作');
                return;
            }
            lastProcessTime = now;

            // 设置冷却期
            skipCooldown = true;
            setTimeout(() => {
                skipCooldown = false;
            }, 2000);

            // 跳转到离片尾0.5秒处,确保能触发下一集
            player.currentTime = duration - 0.5;
            console.log(`已跳过${OUTRO_SKIP_TIME}秒片尾,跳转到${duration - 0.5}秒`);
            showHint(`已跳过${OUTRO_SKIP_TIME}秒片尾`);

            // 标记为等待下一个视频,并设置强制处理
            isWaitingForNextVideo = true;
            forceProcess = true;
        }
    }

    // 检查是否应该处理新视频
    function shouldProcessNewVideo(player) {
        if (!player) return false;

        // 如果正在等待下一个视频且播放器已加载新视频
        if (isWaitingForNextVideo && player.currentTime < 5) {
            console.log('检测到自动连播的新视频');
            isWaitingForNextVideo = false;
            return true;
        }

        // 检查是否是新视频
        return isNewVideo();
    }

    // 主处理函数
    function processVideo() {
        const player = getPlayer();
        if (!player) return;

        // 检查是否应该处理新视频
        if (shouldProcessNewVideo(player)) {
            console.log('检测到需要处理的新视频:', getVideoTitle());
            // 处理记忆进度
            processMemoryProgress(player);
        }

        // 保存进度
        const videoKey = generateVideoKey();
        if (videoKey) {
            GM_setValue(`bilibili_memory_${videoKey}`, player.currentTime);
        }

        // 检查片尾
        if (player.duration && player.duration > OUTRO_SKIP_TIME) {
            checkAndSkipOutro(player, player.duration);
        }
    }

    // 初始化
    function init() {
        console.log('B站片头片尾跳过脚本已加载');

        // 监听播放器状态变化
        const player = getPlayer();
        if (player) {
            player.addEventListener('loadedmetadata', () => {
                console.log('播放器加载了新的元数据');
                processVideo();
            });

            player.addEventListener('timeupdate', () => {
                // 播放器时间更新时检查
                processVideo();
            });
        }

        // 监听URL变化
        new MutationObserver(() => {
            console.log('检测到URL变化');
            setTimeout(() => {
                processVideo();
            }, 300);
        }).observe(document, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['href']
        });

        // 定时检查
        setInterval(() => {
            const player = getPlayer();
            if (player && !player.paused) {
                processVideo();
            }
        }, 1000);

        // 初始处理
        setTimeout(processVideo, 1000);
    }

    // 等待页面加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

说明

本脚本是专门为了学习而编写的脚本,也只有在学习时使用该脚本才有可能有相应的效果,严禁将此脚本内容用于任何商业或非法用途,对于因违反此说明而产生的任何法律后果,用户需自行承担全部责任。

学习必备:B站全自动跳过固定片头片尾 V1.4最先出现在龙鲲博客

解决WPS“我们遇到了一个无法恢复的问题”折中方案

2025-06-24 22:54:42

本文于 2025年6月24日 10:54 更新,注意查看最新内容

晚上家里台式电脑在通过微信打开表格文件时,开始频繁出现“我们遇到了一个无法恢复的问题,您的文件已经自动保存到备份文件夹中,请勿担忧。如果此错误反复发生,请将错误报告发送给我们”。

在网上查找了相关的解决方案,例如关闭火绒的文件扫描、重置WPS的自带设置、安装WPS教育版,均无法很好的解决问题。

这里记录一下折中的解决方案:

安装WPS教育专版https://ncre.neea.edu.cn/html1/report/1507/861-1.htm

优势:具备通用性,功能全面,不会出现教育版以及特制版本使用异常的问题,包括不限于过期、无法登录等。

劣势:没有WPS最新功能,UI相对最新版不够好看,可能不支持宏之类的高级功能(一般人也用不上)。

解决WPS“我们遇到了一个无法恢复的问题”折中方案最先出现在龙鲲博客