使用额外的JavaScript脚本优化知乎推荐流的浏览体验 [draft]
最近刷知乎的时候实在受不了首页给我推荐的低质量内容了,故学习了一番JavaScript,写了一个脚本(知乎推荐流优化)来改善知乎网页端的浏览体验。现将学习过程记录一二,希望以后能够用上…… 其实这篇文章已经在编辑器里躺了很久了,一直没有来写完 确定目标 首先注意到知乎上质量低劣的键政内容主要是由默认头像用户产生的,因此只要把默认头像(以及满足一定额外条件的)用户的回答卡片隐藏起来就可以了。由于曾用过知乎美化这样的油猴脚本,了解到油猴脚本应该可以实现所对应的功能,决定开始学习JavaScript。 油猴脚本初探 参考维基百科上的描述,GreaseMonkey是Firefox的一个附加组件,允许用户在网页上插入自定义的JavaScript脚本以增强网页的功能。但现在已经有了更多更先进的用户脚本管理器,我选择使用的是开源的ViolentMonkey,因为另一个流行的用户脚本管理器Tampermonkey的某个版本在使用前面提到的知乎美化脚本时在某些情况下有严重的性能问题,会拖慢整个浏览器最后不得不关掉重启;另外暴力猴还支持跟踪脚本文件的修改情况,从而使用外部编辑器(我喜欢VSCode)编写脚本。 打开暴力猴的脚本编辑器,它会自动创建一个模板: // ==UserScript== // @name New script // @namespace Violentmonkey Scripts // @match *://example.org/* // @grant none // @version 1.0 // @author - // @description 2024/7/1 19:44:25 // ==/UserScript== 脚本最开始的几行是元数据,描述了脚本的各项信息。在模板之外还有一部分如icon、license、run-at、downloadURL之类的项目,脚本管理器会通过这些信息来提供更友好的显示;元数据的详情可以参考TamperMonkey的介绍。 绝大部分油猴脚本的主要结构都是这样的: (function() { 'use strict'; // 一系列代码 })(); 在JavaScript中,有着类似上面这样结构的函数被称之为立即调用函数表达式1。这是一种设计模式,旨在创建函数后立刻执行;在脚本管理器中的体现是它会在读取到最后一行的分号后立刻开始执行脚本。 代码中第二行的'use strict';指示开启JavaScript的严格模式2。严格模式可以使用异常处理并在某种程度上提升了速度。 对知乎网页的分析 知乎主站前端主要是使用React编写的,每项推送内容都被包裹在一个 <div class="Card TopstoryItem TopstoryItem-isRecommend" tabindex="0">元素中,可以使用document.getElementsByClassName()或者document.querySelectorAll()方法获得一个当前页面的所有卡片div对象的集合。 对于每一个卡片,其内部结构是由多层div嵌套来确定各个元素所在位置的,并且在属性中会包含此卡片的一些元数据信息。对前段中提到的每个卡片对象,可以在它的子树中搜索属性为ContentItem的元素;其中含有的data-zop属性以JSON格式存储了此卡片的一些信息,比如作者、问题全文、卡片ID等,另一个属性data-za-extra-module中包含了author_member_hash_id项,即答案作者的标识符(与用户名的关系类似微信的wxid之于微信号)。 对每一个知乎用户,可以通过移动端APIhttps://api.zhihu.com/people/${userId}/profile?profile_new_version=1来获得此用户的公开信息,例如头像URL、回答数量、关注者数量、答案数量等;其中${userId}是前面提到的用户hash id信息。 开始编写 理清知乎每个卡片的数据关系后,就可以开始编写脚本用于移除不想看到的卡片了。Javascript中提供了被称为MutationObserver3的事件监听器,可以针对DOM树中不同的更改执行相应的回调函数。这里我们要监听的是DOM树中子节点发生变化(被添加进来)的事件,这个事件会在每次重载网页或者随着推荐流向下划、新卡片加入到整个首页的推荐流中被触发;由于MutationObserver记录到的并不都是我们想要的添加新卡片的记录,因此要从其中筛选新卡片对象并将其存入数组中等待使用。 待加入的新卡片数量达到设定的阈值后,就可以对其进行逐一检查了。这个阈值不宜设置得太大,由于知乎推荐流存在预加载的情况(即在用户往下滑到底之前就开始请求新的推荐内容,虽然这部分内容还不会被浏览器渲染出现在窗口里),若设的太大会发生符合条件的卡片在用户眼前消失的情况;经过测试,将新加入的卡片数量阈值设置为5能提供比较好的体验。 对每个答案卡片,首先检查的是答案作者是否为默认头像(毕竟是本项目的初衷所在)。检查头像时并没有必要将头像的图片下载下来;对于使用默认头像的用户,知乎会直接使用图床里的默认头像URL4,因此只需要比较头像图片的URL中是否包含特定的hash值abed1a8c04700ba7d72b45195223e0ff就可以了。对其他的默认头像也可以使用此方法,只需要建立一个默认头像的hash值库并遍历比对即可。 使用关键词屏蔽问题是知乎盐选会员的功能,但知乎那一堆盐选专享小说实在令人敬谢不敏。油猴为每个用户脚本提供了配置储存的功能,于是可以让用户输入屏蔽词组并对每个卡片所属问题标题进行匹配(直接使用string.includes()即可)。 对于匹配上关键词的问题,通过点击『不感兴趣』按钮可以让知乎的推荐算法了解到你的喜好,少推这类问题。虽然通过抓包可以看到『不感兴趣』功能是通过向一个知乎的RESTful API发送带参数的get请求实现的,但实际上直接通过XHR请求这个API会报HTTP 403,即使带上Cookie做好伪装也不行;于是此功能只能通过模拟点击来实现。 对于每个答案卡片,底部操作栏最右侧的三点菜单就是一个包含着svg图像(也就是那三个点)的button实现,因此可以轻易地用JavaScript获取此对象并模拟点击。点击后出现的悬浮窗是整个网页body下的一个小div,位置与触发此悬浮窗的卡片绑定。对于预加载的部分这个点击同样有效,只是坐标超出了浏览器窗口范围不会被用户看到;于是只需要监听document.body的变化后模拟点击此按钮,并在悬浮窗中点击『不感兴趣』就可以让知乎少推此类内容了。 在默认头像和屏蔽词两个方式都检测过后,对符合条件的卡片我们会将其挑选出来。脚本中以全局数组的形式存储了每个符合条件将要被移除的对象;这里是通过在卡片div中添加hidden属性让用户看不到此卡片,眼不见心不烦。如果选择使用Element.remove()实现移除会导致点击知乎顶部『推荐』按钮获取新推荐流时报错,推测此功能是通过将所有卡片移除后再拉取新卡片实现的,用户脚本自己移除会导致remove时找不到对象而报错。 总结 虽然经过数周实际体验和迭代,这个项目已经到了能用的水平,并且实测在使用脚本多次点击『不感兴趣』后知乎会再也不推送相关内容,但还是有一些问题尚待解决。 首先是不能对展开评论区里的默认头像用户进行移除:虽然已经编写好了这部分代码,但实际测试时发现对评论的操作并不是十分稳定,并且由于评论区结构是树状交织的(因为有回复和对回复的回复)移除后会显得很奇怪遂暂时作罢;其次在运行时会发生脚本运行在卡片加载完成之前导致检查失败(控制台中出现类似Uncaught (in promise) TypeError: card....
使用LockWorkStation让笔记本的可编程按键发挥作用
总感觉已经许久没有写过blog了……虽然有在给域名续费,但都做的是DDNS方面的用途(顺便推荐一下Cloudflare的DNS解析服务,虽然在国内比较慢但还算方便)。写这个文章主要是想起来之前曾就怎么利用惠普笔记本上自带的F12可编程按键(可以通过系统中的HP Progrommable Key的UWP程序来编辑功能)实现按下即锁定计算机折腾过一番,特来灌水一篇( 已经忘记git和hugo该怎么用了,还是临时去查的 准备工作 因为并不是多么复杂的程序,所以只需要如果对这部分不感兴趣可以直接拉到文章底部,下载编译好的程序并为编程键设置按下启动即可。 首先可以看看这个API在MSDN上的文档:它于Windows XP中引入,功能有且只有一个:以类似于Windows键+L的方式锁定工作站(名字都直接写出来了==)。你可以在命令行中使用rundll32直接调用到user32.dll(对应的头文件为winuser.h)中的这个API: rundll32.exe user32.dll,LockWorkStation 当然,惠普的这个可编程键并不支持在启动程序时附加参数,也不支持调用bat或者vbs之类的脚本……想要使用可编程键直接锁定电脑只能编译一个程序间接调用这个API了。 使用 将下面的代码在Windows平台上使用gcc或msvc编译后,在HP Programmable Key中设置为按下后启动即可。 #include <Windows.h> #include <winuser.h> int main(){ return(!LockWorkStation()); } 使用gcc编译时不需要额外的静态库链接,gcc应当会自动处理与系统自带的user32.dll的动态链接: gcc lockstation.cpp -o lockstation.exe 这个程序在我的电脑上使用TDM-GCC 10.3.0在Windows 10 专业版 22H2 19045.4170下编译通过并能够正常使用。 编译好的文件可以在这里找到:https://github.com/lisolaris/lockscreen/releases/tag/alpha 拓展 有关rundll32的使用,你可以参考MSDN或是这篇文章关于利用rundll32执行程序的分析,挺有趣的。
教程:如何在服务器上使用内网穿透 [draft]
上一篇文章讲到了如何在Windows机器上架设Minecraft服务器,现在我们来看看如何在内网环境下让其他朋友也能访问到自己的服务器。本篇也同样适用于一些其他支持局域网联机的游戏。 前置知识 IP地址 不看也可以 可能不少同学都或多或少地听过IP地址这个词。这里我们不讲严格定义,你只需要明白IP地址可以用来区分每一台联网的计算机就可以了。但由于技术上的限制和历史原因,IP地址是有限的(并且上限还不高,前几年已经完全分配完,也就是枯竭了),然而对于连接公网(国际互联网)来说IP地址又是不可或缺的一部分。于是,像三大运营商和部分小运营商(广电、长城之流)就会让许多用户使用同一个公网IP地址,由运营商的设备负责映射连接。 这种复用缓解了公网IP地址紧缺的问题,但代价就是用户访问公网不得不经过NAT(网络地址转换,也就是前文提到的多对一映射)设备,有时甚至要经过许多层NAT。把这个映射想象成一棵大树,树叶就是NAT后的每台计算机,树根就是多个计算机共同使用的一个IP地址。如果A树上叶子的蚂蚁a想要访问另一棵B树数据结构上的蚂蚁b,那么它必须经过许多个分叉到达A树干,然后爬到B树干后准确来到b所在树叶的分支。显然这非常麻烦,而且在计算机网络的世界中实现起来非常复杂。 但如果蚂蚁b就住在地面上,那么这种访问就变得没那么复杂了。蚂蚁a只需要爬下来就能轻易找到蚂蚁B,而且(更棒的是)a和b成功建立沟通后,后续它们之间的访问就变得非常轻松了。 如何查询自己的IP 很简单,直接百度IP地址就可以了,百度结果的第一项会显示你的IP地址。或者,你也可以使用IP138、IPIP这样的网站查看到自己的IP。 最好的方法:申请公网IPv4地址 虽然国家近年来推行IPv6的力度非常之大(尤其是教育网方面,几乎所有学校都推广开了IPv6),IPv4仍然是当前最广泛、最通用的IP协议。需要说明的是,这种方法基本仅适用于使用家庭宽带的用户,而且电信宽带用户要比其他运营商更容易申请到(因为电信手上的地址数量比另外两家多。不过根据V2EX上的帖子,现在电信正在逐渐收紧公网IP的获取,可能过不了多久其他地区的新用户就难以拿到公网IP了)。 如何申请 前提条件 很简单,首先你得是机主,或者至少知道宽带安装的信息(机主身份证号码、楼栋、宽带帐号、机主姓名等)。如果你使用了路由器,那你还需要能够访问到路由器后台管理页面。 操作方法 打电话给你的运营商(如电信的10000)客服转人工,和客服说明要申请公网IP。如果客服问及你要做什么就回答装监控。顺利的话客服会将你转至处理这方面的师傅,核实装机信息后由运营商那边为你修改账户信息,然后他们会告诉你半小时后重启路由器和光猫就可以了。 按照电脑连接到路由器,由路由器拨号上网这一节的内容在路由器上设置端口转发。 不要忘记重启路由器和光猫!然后你就可以在路由器后台的上网信息里看到你的IP地址和百度/查询网站上得到的IP一致,说明成功了。 让你的朋友通过这个IP和你在路由器端口转发里设置的外部端口连接,愉快地游戏吧! DLC内容:使用DDNS服务 通过DDNS服务提供商 由于运营商会在你每次拨号时下发一个新的IP地址,因此你的公网IP并不是一直不变的。你可以到花生壳这样的DDNS网站注册,并申请一个DDNS域名,然后到路由器后台设置。不过由于花生壳是国内服务商,注册域名时会要求你提供身份信息和手持身份证照片,有隐私泄露风险。使用其他DDNS服务提供商也可以,但是要确定你的路由器支持,而且其他服务商并不全是免费的。 购买一个域名 你也可以购买一个域名,像我现在使用的org域名一年大概是10美刀,如果是其他后缀(比如说top、site之类的)会比较便宜些;然后使用Cloudflare或者腾讯云DNSPOD提供的解析服务,加上本地电脑和类似https://github.com/jeessy2/ddns-go这样的项目做DDNS更新,虽然成本会比较高但是折腾的过程也很好玩,这里权做抛砖引玉。 我使用的域名注册商是Namesilo(*链接内含aff,可以当作是对我的支持:D),这家注册商提供com/net/org和其他各种奇怪顶级域名的注册,并且价格还算合理;能用支付宝付款,不需要外币账户;最重要的是提供WHOIS隐私保护服务,不会在ICANN的域名信息查询中显示注册人的身份信息! 使用Softether VPN提供的免费DDNS服务 你也可以使用Softether的VPN服务端。这是日本筑波大学的一个功能强大的VPN软件项目,可以让你出门在外也如同在家里的内网一般访问内容,它安装了特殊插件的客户端还能起到魔法的效果;但是在本节,我们要买椟还珠,仅使用它的DDNS服务。 可能会有点麻烦:使用公网IPv6地址 就像前面说的,国内正在大力推行IPv6地址,三大运营商基本都已经跟进了,也就是说,这个地址不用你额外操作就能获得;即使是教育网用户也能基本能拿到 一个 2001:da8的/128公网v6地址。有了这个基础,使用公网IPv6进行远程联机也并非不可能。 但是,大部分路由器都默认开启了IPv6防火墙,并且没有提供v6端口转发功能。为了玩转这个部分,你可能需要许多的计算机知识。(或者你运气很好,路由器提供了这个功能或是能够关闭IPv6防火墙;但本节的目的不是这个,我推荐打开百度查一查你的路由器支不支持。) 但如果你使用的是教育网…… 不少高校为了避免受到攻击,都会将防火墙配置为拒绝传入连接。这样好不容易拿到的公网v6地址也没什么用了,实在是坏文明! 但你还是可以照着本节的内容试一下,如果没禁的话那就是最好不过了。 如何配置 本节的讲解针对OpenWRT系统。如果你没听过OpenWRT,那么接下来的内容可能会比较晦涩;但是学习也是冲浪的一部分:D 给路由器刷入OpenWRT 这是一个比较复杂的操作,如果你从没接触过相关内容,我推荐你充分查询资料并考虑后再去操作。这里推荐一个论坛:恩山无线论坛,几乎所有市售的路由器在这里都有讨论,还能找到现成的刷机包;你也可以当一个硬核玩家,手动编译路由器固件镜像并烧录到路由器的ROM。 在OpenWRT下开启IPv6 等待施工 OpenWRT端口转发操作指南 本节内容仅适用于有ip6tables的OpenWRT版本,并且不需要socat之类的插件。如果你使用了带有socat插件的固件,那设置v6端口转发是非常简单的,不用怎么配置。 使用FRP进行内网穿透 你有一个公网IP,但是现在人在学校/公司 现在假设你已经通过前面的教程申请到了一个公网的IP地址,但是人并不总是在家里的,而是经常在公司或是学校;针对这种情况,我们可以使用GoFRP来架设端口转发的服务端。在配置端口转发时,除了在路由器防火墙中配置想要转发的端口之外,还应该开放FRP的控制端口;此外,我推荐尽可能选择不常用的高位端口,并使用GoFRP自带的token等方式进行鉴权,避免你的FRP服务成为黑客的跳板。 FRP的配置可以参考这里 你什么都没有 对于这种情况,可能需要租用带有公网地址的云服务器来完成操作了。 使用贝锐(花生壳)提供的VPN组网服务 使用Softether提供的VPN Azure服务
教程:如何在Windows机器上架设Minecraft服务器
本篇是应群友需求,来介绍一下如何在Windows机器上启动Minecraft服务端供内网用户游玩,以使用服务端上才能运行的插件之类的。关于如何让别人远程连接到你的服务器,可以参见下一篇教程:如何在服务器上使用内网穿透 。 准备工作 首先呢我们需要准备一个服务端,这里选用PaperMC,访问官网后点进右上角Downloads按需下载版本就行了。下载后得到的一个jar文件,先放着。 PaperMC属于Bukkit系的服务端,兼容大部分Bukkit和Spigot的插件,同时应用了许多针对原版的优化,令其运行速度大大快于原版vanilla;但代价就是许多原版的bug特性在Paper端不再可用,需要在高级配置中开启或是安装额外的插件(比如用于造刷沙机的跨世界实体方块更新逻辑在Paper端就需要安装这个插件)。 需要注意的是官网首页的版本是跟进到最新稳定版的,比如目前是1.19.3,那么1.19.2等不再支持的客户端需要一直往下滑点击More才能看到。此外,更旧的不再支持的大版本服务端(如1.18.2及更早)被归在Legacy里面,点进去后会提示这些版本不再受支持,你提出的任何bug都不会修补云云。 为了让游戏运行起来,我们还需要Java运行环境。你可以下载OpenJDK或是Oracle JDK,二者的区别在开源与否。 此外,从Minecraft 1.17开始,Mojang就要求玩家使用16及更高版本的JRE,因此我推荐你下载最新的JDK。从我个人的使用体验而言,OpenJDK和Oracle JDK并没有察觉到明显的差异,下载哪个都无所谓了……不过我推荐下载zip打包的JDK,可以省去配置环境变量这一步,后续要升级也很方便。 让服务器跑起来 准备好文件 现在我们就有了一个.zip的JDK压缩包,一个.jar的Java软件包。我们找个文件夹把它们装起来,解压JDK,看起来像是这样的: 打开得到的jdk文件夹,进入bin目录内,找到java.exe,按住Shift的同时右键它,在弹出的菜单中选择『复制文件地址』: 如果想运行安装了Forge或者Fabric的Mod服务器,之后我有空会再补充的。这里先介绍原版。 启动脚本 回到刚刚的目录,新建一个文本文档,填入刚刚复制的java.exe的文件地址,在后面加入参数-Xmx<最大限制内存>M -Xms<最小分配内存>M -Dfile.encoding=utf-8 -Dnative.encoding=utf-8 -jar <服务端文件路径>,比如像这样: chcp 65001 ipconfig /all "D:\MinecraftServer\jdk-19.0.2\bin\java.exe" -Xmx4096M -Xms768M -Dfile.encoding=utf-8 -Dnative.encoding=utf-8 -jar "D:\MinecraftServer\paper-1.19.3-448.jar" pause 解释一下: 最大限制内存:允许Java虚拟机使用的最大的内存,可以理解为服务端使用的最大内存量。应当按照你电脑拥有的实际内存来设置。一般有16G内存的电脑,和三五个好友一起玩设置为4096(4GB)已经够用了,人数更多可酌情调整,但也需要更强的CPU性能。 最小分配内存:至少给Java虚拟机分配这么多内存,合理小于最大限制就可以了。 服务端文件路径:可以用和复制java.exe的文件路径相同的方法得到,粘贴进去就可以了。注意双引号不要删掉。 -Dfile.encoding=utf-8 -Dnative.encoding=utf-8:这两个参数用于指定使用的文本编码,避免控制台日志的中文显示为乱码。 保存文件,将后缀名改为bat,双击运行一次;第一次的运行会自动下载原版的服务端并在它的基础上修改(为了规避版权问题不能直接提供修改好的服务端),需要一些时间。等它下完开始滚动日志后没一会就会报错停止,这是正常的,因为我们还没有同意最终用户许可协议(同样是版权问题)。 这时在游戏文件夹里已经生成了一堆文件和文件夹,暂时不管。我们打开eula.txt,把最后一行的false改成true,保存,再次启动服务端,正常运行。 弹出防火墙提示的话记得允许放行。 这个时候可以看到命令行窗口最后一行日志是Timings reset,并有一个>命令提示符等待用户输入。以后可以从这里输入Minecraft指令了。 在客户端中填入服务器地址 我们打开游戏,启动多人模式,添加一个服务器为127.0.0.1:25565看看能不能正常进去: 打开F3调试选项,左上角可以看到进入了"Paper"服务器。 配置服务端 在控制台输入stop后回车停止服务端。然后打开服务端目录下的server.properties,对服务端进行配置。这里可以参考我的: #Minecraft server properties #Mon Jan 16 16:16:38 CST 2023 allow-flight=true allow-nether=true broadcast-console-to-ops=true broadcast-rcon-to-ops=true debug=false difficulty=normal enable-command-block=true enable-jmx-monitoring=false enable-query=false enable-rcon=false enable-status=true enforce-secure-profile=true enforce-whitelist=false entity-broadcast-range-percentage=100 force-gamemode=false function-permission-level=2 gamemode=survival generate-structures=true generator-settings={} hardcore=false hide-online-players=false initial-disabled-packs= initial-enabled-packs=vanilla level-name=world level-seed= level-type=default max-chained-neighbor-updates=1000000 max-players=20 max-tick-time=60000 max-world-size=29999984 motd= network-compression-threshold=256 online-mode=false op-permission-level=4 player-idle-timeout=0 prevent-proxy-connections=false previews-chat=false pvp=true query....
Hello World
你好,欢迎来到我的博客! 在拖延好几个星期后,我终于完成了个人页面的搭建(通过Github Pages托管)。今后应该会不定期更新一些感想和发现……也希望你能看得开心:D 关于我 这个人很懒,什么都没有留下…… 关于本站 就像前面说的,这里主要是分享我的感想和发现。对于我原创的内容,你可以随意转载、复制、修改后再发布,但请标明原作者和出处;如果我使用了你的未授权的内容,请发邮件联系我,我会尽快处理的。谢谢你! 致谢 本站使用Hugo搭建,并使用了PaperMod主题。感谢你们! 其实作为第一篇博文应该写长一些的,但我还没想到要写什么……就先这样吧:)