普通视图

随手记 20260128

2026年1月24日 17:03

近日Typecho突然诈尸更新了版本号 1.3.0
其实与之前的beta版本相差不大..

[info]但是更新需谨慎![/info]
部分插件或者主题 在1.3.0无法继续使用..


1panel使用Typecho的过程中还是会有一些问题 使用自带的伪静态规则大概率会出现 404

需要修改伪静态为

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

因为高中时候最喜欢武侠小说,而武侠作家之中最喜欢古龙的作品,再加上古龙最喜欢借人物之口讲些人生道理.
所以我做了一个api 收集了一些古龙的语录.

  <div id="quote">加载中</div>
  <script>
    async function loadQuote() {
      try {
        const res = await fetch('https://api.zxd.im/api');
        const data = await res.json();
        document.getElementById('quote').textContent = data.text;
      } catch (e) {
        document.getElementById('quote').textContent = '加载失败';
      }
    }
    loadQuote();
  </script>

来调用古龙语录.

此项目用 Cloudflare Workers + KV 部署, 自用还行


本文由 老孙博客 原创发布

转载请注明出处

  •  

装机计划 and 环境部署

2025年12月20日 19:57

起因

最近由于内存涨价了, 我把x99平台的4条16G内存卖了, 回了一波血, 暂时不打算玩这个老旧的平台了

恰好手里头有张微星的Z590M 爆破弹, 准备换平台玩玩.

于是在拼夕夕斥巨资买了一颗i5 10600KF 花费 458..

内存就使用 几年前玩机 扔在抽屉里的 金百达 DDR4 2666MHZ 8G X2 开启XMP 可运行在3000MHZ.

硬盘还有两块铠侠 的 1T m.2 也是当年玩机买的 当时的价格貌似是599一块.

显卡使用百战老兵 AMD RX5600XT 6G 黑苹果免驱

网卡使用 BCM94360CD 黑苹果免驱 (15之前)

配置

名称型号价格渠道
主板微星Z590M爆破弹0拆机
CPUi5 10600KF如今价格458拼夕夕
内存金百达 8g * 20拼夕夕
硬盘威刚 512G M2 SSD0淘宝
硬盘三星 980 250G M2 SSD0拆机
网卡BCM94360CD0淘宝
显卡蓝宝石RX5600XT0拼夕夕
机箱航嘉0淘宝
散热器大水牛240水冷0京东
  • 很多都是很多年前装机屯的 或者 用过的就不标注价格了

计划

一块SSD安装WIN10

另一块SSD安装macOS Monterey 双系统 Opencore引导.

U盘启动盘

装系统必备U盘,大约16G左右够用 32G为佳.

使用Ventoy
下载地址 https://www.ventoy.net/en/download.html
制作好的启动盘可以引导各种iso镜像

此时我们就需要winPE镜像,我比较推荐的是WEPE https://www.wepe.com.cn/download.html
通过微PE工具箱V2.3生成WePE_64_V2.3.iso
Windows安装镜像 推荐官网下载 https://www.microsoft.com/zh-cn/software-download/windows11
如果需要Linux也可以下载Ubuntu Debian 等 放入制作好的U盘中,盘符默认为ventoy

安装Windows

把U盘启动项作为第一启动项

进入Ventoy的引导界面,选择WePE_64_V2.3.iso,进入winPE

使用Diskgenius删除整个硬盘的所有分区 保存 右键点击 该硬盘 转为GPT分区,如果已经是GPT分区跳过此步骤

新建ESP分区 大约 300MB 作为EFI引导

新建分区 大约256G 作为Windows安装分区

新建分区 剩余空间 作为软件安装分区 保存

使用WINSETUP来安装

镜像选择 下载好的官方镜像 ESP分区选择创建好的 ESP分区

C盘选择 创建的256G分区

点击安装 完成后重启 即完成安装部分

激活WINDOWS

我使用的是批量激活码KMS激活,大概半年需要激活一次,使用NAS搭建的KMS服务器
Docker镜像 使用jkjoy/kms 映射 1688 8080 端口

顺便激活Office全家桶,使用 Office tool plus https://otp.landian.vip/zh-cn/

主打一个不花钱

引导

使用开源项目 https://github.com/lzhoang2801/OpCore-Simplify

windows 下 挂上代理 安装好Python环境

git clone https://github.com/lzhoang2801/OpCore-Simplify
cd OpCore-Simplify
python OpCore-Simplify.py

根据顺序选择 自动等待下载 并生成opencore引导的EFI文件

安装macOS

安装就十分简单了,选择下载镜像

网络上有很多制作好的原版镜像dmg格式

使用Balena etcher 烧录U盘,
使用Diskgenius打开U盘的EFI分区把生成好的引导文件复制进去.

重启电脑选择 U盘为第一启动项

即可顺利进入安装界面.

截屏2025-12-27 23.56.22.png

补丁

由于macOs 14 以后的系统已经不支持原本免驱的网卡,所以需要使用打补丁的方式来驱动无线网卡

https://github.com/laobamac/OCLP-Mod

此项目也可以给集显驱动和声卡驱动打补丁
截屏2025-12-27 23.58.41.png

常用环境

首先安装brew [article id="338"]

但是奇怪的是使用brew不能正常安装nodejs,一直报错

Error: An exception occurred within a child process:
  FormulaUnavailableError: No available formula with the name "formula.jws.json".

所以使用nvm来安装

先安装nvm

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
\. "$HOME/.nvm/nvm.sh"

安装nodejs 24.12

nvm install 24

本文由 老孙博客 原创发布

转载请注明出处

  •  

规则

2025年11月14日 15:14

这制订规则的人当真是有着高高在上的傲慢.

遵守规则的我反而陷入了困境之中.

不知道为何我这个域名现在被腾讯拉入了黑名单,连带着使用了腾讯安全数据的小米手机也会提示危险

Screenshot_2025-11-14-15-07-19-551_com.android.br.jpg

我不理解,我曾经申诉过几次,有时候好一段时间,然后就又红了.

在不明真相的人眼中 这无异于败坏了我的人品.

实在不行就换个域名重来过..


本文由 老孙博客 原创发布

转载请注明出处

  •  

&quot;套娃&quot;

2025年11月9日 14:58

因为想用Cloudflare的Workers 和 D1 部署一个完全仿照Wordpress Rest API 的博客后端

所以 用AI糊了一个项目

https://github.com/jkjoy/cfblog

为了能使用这个后端 所以就又糊了一个基于vue3的前端 仿 Akina 风格

https://github.com/jkjoy/cfblog-theme-akina

同时又仿hugo主题paper 糊了一个Astro 的前端

https://github.com/jkjoy/cfblog-theme-paper

为了让这个主题能同时兼容wordpress 和 typecho

于是我又开了一个
https://github.com/jkjoy/Typecho-RestAPI

为了让wordpress 的API路由兼容更好

我又仿sunnylite主题 弄了个wordpress主题

https://www.sgcd.net


本文由 老孙博客 原创发布

转载请注明出处

  •  

Typecho RSS 全文输出美化插件

2025年10月20日 09:18

一个功能强大的 Typecho RSS 插件,支持全文输出和多种美化显示风格。

功能特点

  • 全文输出: 支持输出文章全文或摘要
  • 多种主题: 提供 4 种精美样式主题供选择
  • 美化显示: 在浏览器中直接打开 RSS 链接时显示美观的界面
  • 灵活配置: 可自定义文章数量、输出内容、版权信息等
  • 响应式设计: 完美适配桌面和移动设备
  • 接管原生路由: 完全接管 Typecho 的 /feed 路由

安装方法

  1. 下载插件到 Typecho 插件目录:

    /usr/plugins/Rss/
  2. 在 Typecho 后台启用插件:

    • 进入 控制台插件已安装插件
    • 找到 Rss 插件,点击 启用
  3. 配置插件(可选):

    • 点击插件的 设置 按钮
    • 根据需要调整配置选项

配置选项

RSS 文章数量

  • 设置 RSS 输出的文章数量
  • 默认为 20 篇
  • 设置为 0 则输出全部文章

RSS 内容输出

  • 输出全文: 在 RSS 中显示文章完整内容
  • 输出摘要: 仅显示文章摘要(前 200 字符)

RSS 样式美化

  • 启用: 在浏览器中访问时显示美化界面
  • 禁用: 输出纯 XML 格式

样式主题

插件提供 4 种不同风格的主题供选择:

  1. 渐变风格 (gradient) - 默认主题

    • 紫色渐变背景
    • 现代感强,视觉冲击力强
    • 卡片式布局,悬停动画
    • 适合个人博客、创意类网站
  2. 简洁风格 (minimal)

    • 黑白配色,简约设计
    • 清爽干净,专注内容
    • 类似报纸排版
    • 适合技术博客、文字类内容
  3. 现代风格 (modern)

    • 蓝色系商务风格
    • 专业大气,层次分明
    • 卡片阴影,渐变头部
    • 适合企业博客、专业网站
  4. 暗黑风格 (dark)

    • 深色主题,护眼舒适
    • GitHub 风格配色
    • 自定义滚动条美化
    • 适合夜间阅读、开发者

版权信息

  • 在每篇文章末尾添加自定义版权信息
  • 支持 HTML 标签
  • 示例:

    <p>本文由 <a href="https://example.com">我的博客</a> 原创发布</p>
    <p>转载请注明出处</p>

使用方法

启用插件后,可以通过以下 URL 访问 RSS:

主要访问地址

https://你的域名/feed

插件会自动接管 Typecho 原生的 /feed 路由,使用美化后的 RSS 输出。

在 RSS 阅读器中订阅

将上述任一地址复制到您的 RSS 阅读器(如 Feedly、Inoreader 等)中即可订阅。

在浏览器中访问

直接在浏览器中打开上述地址,将看到美化后的 RSS 页面。根据你在插件设置中选择的主题,会显示不同风格:

  • 渐变风格: 紫色渐变背景 + 白色卡片
  • 简洁风格: 纯白背景 + 黑白排版
  • 现代风格: 蓝色渐变头部 + 灰白背景
  • 暗黑风格: 深色背景 + 蓝色点缀

主题样式预览

渐变风格 (Gradient)

  • 视觉特点: 紫色渐变背景,白色圆角卡片
  • 配色方案: #667eea#764ba2
  • 适用场景: 个人博客、创意网站
  • 设计元素: 悬停动画、阴影效果、圆角设计

简洁风格 (Minimal)

  • 视觉特点: 黑白配色,线条分割
  • 配色方案: #000 + #fff + #666
  • 适用场景: 技术博客、专业内容
  • 设计元素: 极简排版、下划线链接、报纸式布局

现代风格 (Modern)

  • 视觉特点: 蓝色系商务风格
  • 配色方案: #0066cc#0052a3
  • 适用场景: 企业网站、专业机构
  • 设计元素: 卡片悬停效果、徽章标签、渐变头部

暗黑风格 (Dark)

  • 视觉特点: GitHub 式深色主题
  • 配色方案: #0d1117 + #58a6ff
  • 适用场景: 开发者博客、夜间阅读
  • 设计元素: 自定义滚动条、蓝色链接、暗色卡片

特色功能

1. 多样化的主题风格

  • 提供 4 种精心设计的样式主题
  • 每种主题都有独特的视觉风格
  • 可在后台随时切换,无需修改代码
  • 所有主题均支持响应式设计

2. 完整的内容支持

  • 支持 Markdown 自动解析
  • 保留文章中的图片和格式
  • 可添加自定义版权信息

3. 响应式设计

  • 完美适配各种设备屏幕
  • 移动端优化显示

4. 标准兼容

  • 符合 RSS 2.0 规范
  • 支持常见的 RSS 命名空间(content、dc、atom)
  • 兼容主流 RSS 阅读器

5. 路由接管

  • 完全接管 Typecho 原生的 /feed 路由
  • 禁用插件后自动恢复原生功能
  • 无缝替换,不影响现有订阅

文件结构

Rss/
├── Plugin.php          # 插件主文件
├── Action.php          # RSS 生成逻辑
├── rss-gradient.xsl    # 渐变风格样式
├── rss-minimal.xsl     # 简洁风格样式
├── rss-modern.xsl      # 现代风格样式
├── rss-dark.xsl        # 暗黑风格样式
└── README.md           # 说明文档

技术说明

Plugin.php

  • 插件主类,负责注册路由和配置项
  • 定义插件激活/禁用逻辑
  • 提供配置面板

Action.php

  • RSS 生成核心逻辑
  • 从数据库查询文章
  • 构建 RSS XML 输出
  • 支持 Markdown 解析
  • 根据配置动态加载样式文件

XSL 样式文件

  • 4 个独立的 XSLT 样式表文件
  • 将 XML 转换为美观的 HTML 页面
  • 每个文件包含完整的 CSS 样式
  • 互不干扰,易于定制和扩展

常见问题

Q: 启用插件后无法访问 RSS?

A: 请检查您的 Typecho 是否开启了路由重写功能。

Q: RSS 中图片无法显示?

A: 请确保文章中的图片使用的是绝对路径。

Q: 如何关闭样式美化?

A: 在插件设置中将"RSS 样式美化"设置为"禁用"即可。

Q: 可以自定义样式吗?

A: 可以,直接修改 rss.xsl 文件中的 CSS 部分。

版本历史

v1.0.0

  • 初始版本发布
  • 支持全文 RSS 输出
  • 支持 XSL 样式美化
  • 提供灵活的配置选项

许可证

本插件遵循 MIT 许可证开源。

反馈与支持

如有问题或建议,欢迎通过以下方式反馈:

  • 提交 Issue
  • 发送邮件
  • 在博客留言

项目地址

https://github.com/jkjoy/Typecho-Plugin-Rss

享受优雅的 RSS 订阅体验!


本文由 老孙博客 原创发布

转载请注明出处

  •  

Puock - 又一款Typecho主题

2025年10月16日 13:36

说明

本主题移植自开源项目
https://github.com/Licoy/wordpress-theme-puock
原项目为WordPress主题.

我觉得很酷 所以移植到了Typecho 平台.

预览

本站

使用指南

适用于Typecho 1.2.0 以上版本

[danger]注意事项[/danger]

部分功能需要使用插件Puock来实现
插件地址 Puock

项目地址

https://github.com/jkjoy/Typecho-Theme-Puock


本文由 老孙博客 原创发布

转载请注明出处

  •  

解决部分云服务器内存爆满但SWAP占用为0的问题

2025年8月9日 17:27

问题

在内存爆满几乎100%的情况下,开启了swap分区却占用一直为0,这种情况发生在阿里云 和 华为云 比较多.

排查

查看系统的 Swap 使用倾向(swappiness),执行

cat /proc/sys/vm/swappiness

最小为0最大为100 数字越小代表使用倾向越低.

我的华为云服务器查询到的数值竟然为0. 这意味着云服务商根本就不打算让人使用swap.

解决

编辑配置

sudo nano /etc/sysctl.conf

找到

vm.swappiness=

把后面的数字改为90
如果没有找到,也没关系,直接在最后添加

vm.swappiness=90

然后执行

sudo sysctl -p

可以生效了


本文由 老孙博客 原创发布

转载请注明出处

  •  

又年长一岁

2025年7月9日 15:09

昨天是我妈生日,今天是我的生日.

本来我都已经忘记了,但是在那个相亲相爱一家人的群里总有人会记得大家的生日.

小时候是很爱过生日的,总会在生日当天叫上自己的小伙伴来自家玩耍.

少年时候也是喜欢过生日的,无非是找个由头和自己喜欢的女孩子在一起玩耍.

如今却是不喜欢的,无疑是告诉自己又年长一岁.又老了一些.


总是会有些负面的情绪是因为太过于繁琐和无奈.

社会总是会磨平年少的棱角.

也会产生更多的怨气.


人生有七苦,生、老、病、死、怨憎会、爱别离、求不得.
终一世无法超脱.

Ring的父亲由于癌细胞恶化现在在医院里等待最后的时光.她在医院衣不解带伺候了好几日.
我无法安慰.
这就是人生之中难言必经的阵痛.


有时候会思考生活的意义.

最后觉得有人说的其实很对,无非就是生下来,活下去.

活着其实没有任何的意义,这一辈子不过是追求欲望的过程.

也是与自己不停和解的过程.

我想只要是人,都无法超脱.


想说的很多,不足为外人道哉.


祝自己生日快乐吧


本文由 老孙博客 原创发布

转载请注明出处

  •  

Typecho常用插件集合

2025年6月17日 15:14

写在前面

众所周知Typecho的插件多且杂.各种版本分支满天飞.
这里收集一些我自用的插件.

供同样使用Typecho的博友们下载使用

插件

Links

很多主题友情链接页面都是基于此插件来实现,当然也有很多主题不使用此插件.
或者没有友情链接页面的主题亦可以使用此插件来实现

点此下载

兼容性测试: 兼容1.3.0

Testore

一个非官方的插件商店,里面的插件万年没更新...
ps.我准备等1.3.0正式版发布维护一个插件列表..

点此下载

兼容性测试: 兼容1.3.0

Tagshelper

泽泽社长开发的插件,在编辑文章的页面展示所有的标签,点击即可添加

下载

兼容性测试: 兼容1.3.0

CommentNotifier

同样是由泽泽社长开发的插件,在有新的评论或回复时会通知到对方邮箱

下载地址

兼容性测试: 兼容1.3.0

Sitemap

网站地图 插件,这个自不必多说了.方便搜索引擎的收录.

这里收录的是 Google Sitemap 生成器 【TF社区维护版】

下载

兼容性测试: 兼容1.3.0

AISummary

一款使用AI生成文章摘要的插件.
使用AI的优点在于SEO
下载地址
兼容性测试: 兼容1.3.0

S3Upload

使用s3兼容的存储服务.支持腾讯云,阿里云,缤纷云,R2,B2 等
下载地址

FediverseSync

同步博客标题 摘要 链接 到联邦宇宙.支持 Mastodon / Pleroma / Gotosocial / Misskey 等实例..

下载

兼容性测试: 兼容1.3.0

CommentsByQQ

评论QQ通知

通过基于nonebot 协议的QQ机器人,在有新评论时通知到指定QQ.
QQ与QQ机器人必须为好友.

下载

兼容性测试: 兼容1.3.0

RobotPush

Typecho机器人提醒插件,支持登录提醒,支持评论提醒,支持钉钉、飞书、企微机器人。

点此下载


本文由 老孙博客 原创发布

转载请注明出处

  •  

2025年06月第2周记

2025年6月16日 09:30

随便记录


由于Typecho更新了1.3.0 RC 于是我跟着更新了最新的版本

顺便把数据库从mysql换为sqlite,降低服务器使用资源.

容器依旧使用的是Docker镜像 jkjoy/php83

有很多插件失效或者报错,把报错信息告诉Deepseek他会告诉你是哪里出了问题,改了改就还能用.


对于父亲节亦或者母亲节不过是些无关紧要的名头.
我们不需要.
但是有些人需要.


父母和外公打算这个周末回老家.

小姨夫因为学校暑假,刚好也回老家住两个月.

小姨觉得一个人在天津也没事,遂决定回家看看.

二姨由于没有买到票不能跟外公一起回去.作罢.

我也已经有五六年没有回去了.

本来担心老爸的身体在家会有所不便,现在看来有这么多人在家照应,也放心不少.


养猫真的麻烦,怎么会有随地大小便的家伙!

但是养都养了能怎么办呢


本文由 老孙博客 原创发布

转载请注明出处

  •  

优化Typecho的思路

2025年5月30日 19:08

服务器端

使用Nginx作为web服务端可以使用以下开启

# 启用 gzip 压缩
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_types
  application/javascript
  application/json
  application/xml
  text/css
  text/plain
  text/xml;

# 浏览器缓存控制
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
}

图片优化

使用原生js实现懒加载

document.addEventListener('DOMContentLoaded', function() {
    // 获取所有图片(如果主题默认输出 src,可以动态替换为 data-src)
    const images = document.querySelectorAll('img[src]:not([data-src])');
    
    // 防止重复处理
    images.forEach(img => {
        if (!img.getAttribute('data-src')) {
            img.setAttribute('data-src', img.src); // 把 src 存到 data-src
            img.removeAttribute('src'); // 移除 src,避免立即加载
        }
    });

    // 懒加载逻辑
    const lazyLoad = (targets) => {
        if ('IntersectionObserver' in window) {
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        const img = entry.target;
                        img.src = img.dataset.src;
                        observer.unobserve(img); // 加载后停止观察
                    }
                });
            });
            targets.forEach(img => observer.observe(img));
        } else {
            // 兼容旧浏览器(滚动监听)
            const checkImages = () => {
                targets.forEach(img => {
                    const rect = img.getBoundingClientRect();
                    if (rect.top < window.innerHeight + 100) { // 提前 100px 加载
                        img.src = img.dataset.src;
                    }
                });
            };
            window.addEventListener('scroll', checkImages);
            checkImages(); // 初始检查
        }
    };

    // 对所有 data-src 图片应用懒加载
    lazyLoad(document.querySelectorAll('img[data-src]'));
});

资源合并与压缩

压缩HTML输出

// 在主题的 functions.php 中
function compress_html($html) {
    $placeholders = [];
    $i = 0;

    // 匹配 <pre> 和 <code> 区块,替换为唯一占位符
    $html = preg_replace_callback(
        '/<(pre|code)[^>]*>.*?<\/\1>/is',
        function ($matches) use (&$placeholders, &$i) {
            $key = "###HTML_COMPRESS_IGNORE_" . $i . "###";
            $placeholders[$key] = $matches[0];
            $i++;
            return $key;
        },
        $html
    );

    // 正常压缩
    $search = array(
        '/\>[^\S ]+/s',
        '/[^\S ]+\</s',
        '/(\s)+/s'
    );
    $replace = array(
        '>',
        '<',
        '\\1'
    );
    $html = preg_replace($search, $replace, $html);

    // 恢复占位符
    if (!empty($placeholders)) {
        $html = str_replace(array_keys($placeholders), array_values($placeholders), $html);
    }

    return $html;
}
//在head.php中开启
<?php ob_start("compress_html"); ?>
//在footer.php中结束
<?php ob_end_flush(); ?>

缓存策略

浏览器缓存

<meta http-equiv="Cache-Control" content="max-age=86400" />

header.php中加入

页面缓存

在主题的 functions.php 中添加简单的页面缓存

function page_cache() {
    // 不缓存后台、登录页和提交操作
    if (defined('__TYPECHO_ADMIN__') || $_SERVER['REQUEST_METHOD'] != 'GET') {
        return;
    }
    
    $cache_dir = __TYPECHO_ROOT_DIR__ . '/cache';
    if (!is_dir($cache_dir)) {
        mkdir($cache_dir, 0755, true);
    }
    
    $url_hash = md5($_SERVER['REQUEST_URI']);
    $cache_file = $cache_dir . '/' . $url_hash . '.html';
    
    // 缓存过期时间(秒)
    $cache_time = 3600; // 1小时
    
    // 如果缓存文件存在且未过期,则直接输出
    if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time)) {
        echo file_get_contents($cache_file);
        exit;
    }
    
    // 否则开始输出缓冲
    ob_start();
}

function save_cache() {
    // 同样跳过后台等页面
    if (defined('__TYPECHO_ADMIN__') || $_SERVER['REQUEST_METHOD'] != 'GET') {
        return;
    }
    
    $cache_dir = __TYPECHO_ROOT_DIR__ . '/cache';
    $url_hash = md5($_SERVER['REQUEST_URI']);
    $cache_file = $cache_dir . '/' . $url_hash . '.html';
    
    // 保存缓冲内容到文件
    $content = ob_get_contents();
    file_put_contents($cache_file, $content);
}
// 在页面开始处调用
page_cache();
// 在页面结束前调用
register_shutdown_function('save_cache');

Impact-Site-Verification: 52b4ec5d-e5be-481e-ad19-28bba5ed5c58


本文由 老孙博客 原创发布

转载请注明出处

  •  

Typecho文章置顶(非插件)

2025年5月26日 10:12

置顶

最近才发现原来用的文章置顶还是有些问题的,置顶N篇文章,翻页时文章列表中文章还是维持原有的翻页逻辑,那么列表中文章的总数中就会减少N篇文章.

实现

正常逻辑应该是从文章列表中查找需要置顶的文章展示在首页列表,把原有的文章列表向后压.翻页时会筛选已经置顶的文章.不再重复显示.

步骤

  1. index.php中插入代码

    <?php  
    $sticky = $this->options->sticky;
    $db = Typecho_Db::get();
    $pageSize = $this->options->pageSize;
    if ($sticky && !empty(trim($sticky))) {
     $sticky_cids = array_filter(explode('|', $sticky));
     if (!empty($sticky_cids)) {
         $sticky_html = " <span class='sticky--post'><svg xmlns='http://www.w3.org/2000/svg' width='16px' height='16px' fill='none' viewBox='0 0 24 24' class='bk'>
    <path fill='#242424' fill-rule='evenodd' d='M12.333 16.993a7.4 7.4 0 0 1-1.686-.12 7.25 7.25 0 1 1 8.047-4.334v.001a7.2 7.2 0 0 1-.632 1.188 7.26 7.26 0 0 1-4.708 3.146l-.07.013q-.466.083-.951.105m.356.979a8.4 8.4 0 0 1-1.377 0l-2.075 5.7a.375.375 0 0 1-.625.13l-2.465-2.604-3.563.41a.375.375 0 0 1-.395-.501l2.645-7.267a8.25 8.25 0 1 1 14.333 0l2.645 7.267a.375.375 0 0 1-.396.5l-3.562-.41-2.465 2.604a.375.375 0 0 1-.625-.13zm5.786-3.109a8.25 8.25 0 0 1-4.775 2.962l1.658 4.554 1.77-1.87.344-.362.496.057 2.558.294zm-12.95 0L3.476 20.5l2.557-.295.497-.057.344.363 1.77 1.87 1.658-4.555a8.25 8.25 0 0 1-4.775-2.961' clip-rule='evenodd'></path></svg></span> ";
         
         // 保存原始对象状态
         $originalRows = $this->row;
         $originalStack = $this->stack;
         $originalLength = $this->length;
         $totalOriginal = $this->getTotal();
         
         // 重置当前对象状态
         $this->row = [];
         $this->stack = [];
         $this->length = 0;
         
         if (isset($this->currentPage) && $this->currentPage == 1) {
             // 查询置顶文章
             $selectSticky = $this->select()->where('type = ?', 'post');
             foreach ($sticky_cids as $i => $cid) {
                 if ($i == 0) 
                     $selectSticky->where('cid = ?', $cid);
                 else 
                     $selectSticky->orWhere('cid = ?', $cid);
             }
             $stickyPosts = $db->fetchAll($selectSticky);
             
             // 添加置顶文章到结果集
             foreach ($stickyPosts as &$stickyPost) {
                 $stickyPost['isSticky'] = true;
                 $stickyPost['stickyHtml'] = $sticky_html;
                 $this->push($stickyPost);
             }
             
             // 计算当前页应显示的普通文章数量
             $standardPageSize = $pageSize - count($stickyPosts);
             
             // 确保第一页不会显示太多文章
             if ($standardPageSize <= 0) {
                 $standardPageSize = 0; // 如果置顶文章已经填满或超过一页,则不显示普通文章
             }
         } else {
             // 非第一页显示正常数量的文章
             $standardPageSize = $pageSize;
         }
         
         // 查询普通文章
         if ($this->currentPage == 1) {
             // 第一页需要排除置顶文章并限制数量
             $selectNormal = $this->select()
                 ->where('type = ?', 'post')
                 ->where('status = ?', 'publish')
                 ->where('created < ?', time());
                 
             // 排除所有置顶文章
             foreach ($sticky_cids as $cid) {
                 $selectNormal->where('table.contents.cid != ?', $cid);
             }
             
             $selectNormal->order('created', Typecho_Db::SORT_DESC)
                 ->limit($standardPageSize)
                 ->offset(0);
         } else {
             // 非第一页的查询
             // 计算正确的偏移量:(当前页码-1) * 每页数量 - 置顶文章数
             // 这样可以确保不会漏掉文章
             $offset = ($this->currentPage - 1) * $pageSize - count($sticky_cids);
             $offset = max($offset, 0); // 确保偏移量不为负
             
             $selectNormal = $this->select()
                 ->where('type = ?', 'post')
                 ->where('status = ?', 'publish')
                 ->where('created < ?', time());
                 
             // 排除所有置顶文章
             foreach ($sticky_cids as $cid) {
                 $selectNormal->where('table.contents.cid != ?', $cid);
             }
             
             $selectNormal->order('created', Typecho_Db::SORT_DESC)
                 ->limit($pageSize)
                 ->offset($offset);
         }
     } else {
         // 没有有效的置顶文章ID,正常查询
         $selectNormal = $this->select()
             ->where('type = ?', 'post')
             ->where('status = ?', 'publish')
             ->where('created < ?', time())
             ->order('created', Typecho_Db::SORT_DESC)
             ->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
     }
    } else {
     // 没有设置置顶文章,正常查询
     $selectNormal = $this->select()
         ->where('type = ?', 'post')
         ->where('status = ?', 'publish')
         ->where('created < ?', time())
         ->order('created', Typecho_Db::SORT_DESC)
         ->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
    }
    
    // 添加私有文章查询条件
    if ($this->user->hasLogin()) {
     $uid = $this->user->uid;
     if ($uid) {
         $selectNormal->orWhere('authorId = ? AND status = ?', $uid, 'private');
     }
    }
    
    // 获取普通文章
    $normalPosts = $db->fetchAll($selectNormal);
    
    // 如果没有置顶文章或在前面的代码中没有重置对象状态,则在这里重置
    if (empty($sticky) || empty(trim($sticky)) || empty($sticky_cids)) {
     $this->row = [];
     $this->stack = [];
     $this->length = 0;
    }
    
    // 将普通文章添加到结果集
    foreach ($normalPosts as $normalPost) {
     $this->push($normalPost);
    }
    ?>
  2. 在主题的functions.php文件中查找

    function themeConfig($form) {

    之后插入代码,会增加一个主题设置项

     $sticky = new Typecho_Widget_Helper_Form_Element_Text('sticky', NULL, NULL, _t('置顶文章cid'), _t('多篇文章以`|`符号隔开'), _t('展示需要置顶的文章。'));
     $form->addInput($sticky);
  3. 在文章列表中合适的地方插入

    <?php if (isset($this->isSticky) && $this->isSticky): ?>
    <?php echo $this->stickyHtml; ?>
    <?php endif; ?>

本文由 老孙博客 原创发布

转载请注明出处

  •  

Typecho根据slug添加icon

2025年5月5日 10:50

使用穷举的方式来匹配自定义icon

根据分类的slug来匹配

                            <?php 
                            switch($categories->slug) {
                                case 'images': echo '<i class="bi bi-images me-1"></i>';
                            break;
                                case 'share': echo '<i class="bi bi-share-fill me-1"></i>';
                            break;
                                case 'NULL': echo '<i class="bi bi-speaker-fill me-1"></i>';
                            break;
                                case 'memos': echo '<i class="bi bi-chat me-1"></i>';
                            break;
                                case 'codes': echo '<i class="bi bi-code me-1"></i>';
                            break;
                                case 'logs': echo '<i class="bi bi-person-fill me-1"></i>';
                            break;
                                case 'test': echo '<i class="bi bi-calendar-fill me-1"></i>';
                            break;
                                case 'tools': echo '<i class="bi bi-tools me-1"></i>';
                            break;
                                case 'music': echo '<i class="bi bi-music-note me-1"></i>';
                            break;
                                case 'links': echo '<i class="bi bi-link me-1"></i>';
                            break;
                                case 'video': echo '<i class="bi bi-camera-video me-1"></i>';
                            break;
                                case 'life': echo '<i class="bi bi-heart-fill me-1"></i>';
                            break;
                                case 'study': echo '<i class="bi bi-book-fill me-1"></i>';
                            break;
                                case 'news': echo '<i class="bi bi-newspaper me-1"></i>';
                            break;
                                case 'themes': echo '<i class="bi bi-palette me-1"></i>';
                            break;
                                case 'plugins': echo '<i class="bi bi-gear-fill me-1"></i>';
                            break;
                                case 'photo': echo '<i class="bi bi-images me-1"></i>';
                            break;
                                default: echo '<i class="bi bi-folder-fill me-1"></i>';
                            } ?>

同样也可以根据自定义页面的slug匹配

<?php $pages = Typecho_Widget::widget('Widget_Contents_Page_List'); ?>
<?php while($pages->next()): ?>
<li>
    <a href="<?php $pages->permalink(); ?>">
    <?php 
    switch($pages->slug) {
        case 'about': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="7" r="4" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg> '; // 关于页面
        break;
        case 'links': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5" /><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5" /></svg>'; // 链接页面
        break;
        case 'archives': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><rect x="3" y="4" width="18" height="4" rx="2" /><path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" /><line x1="10" y1="12" x2="14" y2="12" /></svg>'; // 归档页面
        break;
        case 'gbook': echo '<svg  xmlns="http://www.w3.org/2000/svg"  class="icon icon-tabler icon-tabler-article" width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>'; // 博客页面
        break;
        case 'messages': echo '<svg  xmlns="http://www.w3.org/2000/svg"  class="icon icon-tabler icon-tabler-messages" width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-messages"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" /><path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" /></svg>'; // 留言页面
        break;
        default: echo '<svg  xmlns="http://www.w3.org/2000/svg"  class="icon icon-tabler icon-tabler-file" width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-file"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" /></svg>'; // 默认图标
    } ?>
    <span><?php $pages->title(); ?></span>
    </a>
</li>
<?php endwhile; ?>

本文由 老孙博客 原创发布

转载请注明出处

  •  

Mastodon新通知推送NoneBot

2025年4月21日 16:25

起因

看了@1900 长毛象新通知推送TGBot

深受启发,于是通过ChatGPT 4.1 写了一个python脚本

定时检测Mastodon消息,有新通知会通过QQbot的API URL 发送消息通知给指定的QQ用户(Nonebot兼容)

使用

构建了一个docker镜像jkjoy/mastodon2qqbot

代码仓库在 https://github.com/jkjoy/dockerfile/blob/main/mastodon2qqbot/main.py

使用docker run 命令启动

docker run -d \
  -e MASTODON_INSTANCE="https://你的mastodon实例" \
  -e MASTODON_TOKEN="你的token" \
  -e QQ_API="https://bot.0tz.top/send_private_msg" \
  -e QQ_ID="你的QQ号码" \
  -e CHECK_INTERVAL="30" \
  jkjoy/mastodon2qqbot

30秒检查一次 Mastodon 消息

通过默认的QQ机器人2280858259 发送消息给我使用的QQ80116747

默认的QQ API 是https://bot.0tz.top/send_private_msg

使用默认QQ API需要添加QQ机器人2280858259为好友


本文由 老孙博客 原创发布

转载请注明出处

  •  

Typecho 批量插入附件 并实现图片预览[2025/04/25更新]

2025年4月7日 10:52

在文章的附件选项页加入批量插入所有附件的按钮

并自动识别图片与普通文件,实现图片预览功能

2025-04-07T03:20:33.png

Markdown语法格式自动修正为

![2025-04-07T02:49:18.png](https://img.imsun.org/2025/04/830007048.png)

具体代码实现
在主题的functions.php最后插入

/**
 * Typecho后台附件增强:图片预览、批量插入、保留官方删除按钮与逻辑
 * @author jkjoy
 * @date 2025-04-25
 */
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('AttachmentHelper', 'addEnhancedFeatures');
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('AttachmentHelper', 'addEnhancedFeatures');

class AttachmentHelper {
    public static function addEnhancedFeatures() {
        ?>
        <style>
        #file-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:15px;padding:15px;list-style:none;margin:0;}
        #file-list li{position:relative;border:1px solid #e0e0e0;border-radius:4px;padding:10px;background:#fff;transition:all 0.3s ease;list-style:none;margin:0;}
        #file-list li:hover{box-shadow:0 2px 8px rgba(0,0,0,0.1);}
        #file-list li.loading{opacity:0.7;pointer-events:none;}
        .att-enhanced-thumb{position:relative;width:100%;height:150px;margin-bottom:8px;background:#f5f5f5;overflow:hidden;border-radius:3px;display:flex;align-items:center;justify-content:center;}
        .att-enhanced-thumb img{width:100%;height:100%;object-fit:contain;display:block;}
        .att-enhanced-thumb .file-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:40px;color:#999;}
        .att-enhanced-finfo{padding:5px 0;}
        .att-enhanced-fname{font-size:13px;margin-bottom:5px;word-break:break-all;color:#333;}
        .att-enhanced-fsize{font-size:12px;color:#999;}
        .att-enhanced-factions{display:flex;justify-content:space-between;align-items:center;margin-top:8px;gap:8px;}
        .att-enhanced-factions button{flex:1;padding:4px 8px;border:none;border-radius:3px;background:#e0e0e0;color:#333;cursor:pointer;font-size:12px;transition:all 0.2s ease;}
        .att-enhanced-factions button:hover{background:#d0d0d0;}
        .att-enhanced-factions .btn-insert{background:#467B96;color:white;}
        .att-enhanced-factions .btn-insert:hover{background:#3c6a81;}
        .att-enhanced-checkbox{position:absolute;top:5px;right:5px;z-index:2;width:18px;height:18px;cursor:pointer;}
        .batch-actions{margin:15px;display:flex;gap:10px;align-items:center;}
        .btn-batch{padding:8px 15px;border-radius:4px;border:none;cursor:pointer;transition:all 0.3s ease;font-size:10px;display:inline-flex;align-items:center;justify-content:center;}
        .btn-batch.primary{background:#467B96;color:white;}
        .btn-batch.primary:hover{background:#3c6a81;}
        .btn-batch.secondary{background:#e0e0e0;color:#333;}
        .btn-batch.secondary:hover{background:#d0d0d0;}
        .upload-progress{position:absolute;bottom:0;left:0;width:100%;height:2px;background:#467B96;transition:width 0.3s ease;}
        </style>
        <script>
        $(document).ready(function() {
            // 批量操作UI按钮
            var $batchActions = $('<div class="batch-actions"></div>')
                .append('<button type="button" class="btn-batch primary" id="batch-insert">批量插入</button>')
                .append('<button type="button" class="btn-batch secondary" id="select-all">全选</button>')
                .append('<button type="button" class="btn-batch secondary" id="unselect-all">取消全选</button>');
            $('#file-list').before($batchActions);

            // 插入格式
            Typecho.insertFileToEditor = function(title, url, isImage) {
                var textarea = $('#text'), 
                    sel = textarea.getSelection(),
                    insertContent = isImage ? '![' + title + '](' + url + ')' : 
                                            '[' + title + '](' + url + ')';
                textarea.replaceSelection(insertContent + '\n');
                textarea.focus();
            };

            // 批量插入
            $('#batch-insert').on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var content = '';
                $('#file-list li').each(function() {
                    if ($(this).find('.att-enhanced-checkbox').is(':checked')) {
                        var $li = $(this);
                        var title = $li.find('.att-enhanced-fname').text();
                        var url = $li.data('url');
                        var isImage = $li.data('image') == 1;
                        content += isImage ? '![' + title + '](' + url + ')\n' : '[' + title + '](' + url + ')\n';
                    }
                });
                if (content) {
                    var textarea = $('#text');
                    var pos = textarea.getSelection();
                    var newContent = textarea.val();
                    newContent = newContent.substring(0, pos.start) + content + newContent.substring(pos.end);
                    textarea.val(newContent);
                    textarea.focus();
                }
            });

            $('#select-all').on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                $('#file-list .att-enhanced-checkbox').prop('checked', true);
                return false;
            });
            $('#unselect-all').on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                $('#file-list .att-enhanced-checkbox').prop('checked', false);
                return false;
            });

            // 防止复选框冒泡
            $(document).on('click', '.att-enhanced-checkbox', function(e) {e.stopPropagation();});

            // 增强文件列表样式,但不破坏li原结构和官方按钮
            function enhanceFileList() {
                $('#file-list li').each(function() {
                    var $li = $(this);
                    if ($li.hasClass('att-enhanced')) return;
                    $li.addClass('att-enhanced');
                    // 只增强,不清空li
                    // 增加批量选择框
                    if ($li.find('.att-enhanced-checkbox').length === 0) {
                        $li.prepend('<input type="checkbox" class="att-enhanced-checkbox" />');
                    }
                    // 增加图片预览(如已有则不重复加)
                    if ($li.find('.att-enhanced-thumb').length === 0) {
                        var url = $li.data('url');
                        var isImage = $li.data('image') == 1;
                        var fileName = $li.find('.insert').text();
                        var $thumbContainer = $('<div class="att-enhanced-thumb"></div>');
                        if (isImage) {
                            var $img = $('<img src="' + url + '" alt="' + fileName + '" />');
                            $img.on('error', function() {
                                $(this).replaceWith('<div class="file-icon">🖼️</div>');
                            });
                            $thumbContainer.append($img);
                        } else {
                            $thumbContainer.append('<div class="file-icon">📄</div>');
                        }
                        // 插到插入按钮之前
                        $li.find('.insert').before($thumbContainer);
                    }

                });
            }

            // 插入按钮事件
            $(document).on('click', '.btn-insert', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $li = $(this).closest('li');
                var title = $li.find('.att-enhanced-fname').text();
                Typecho.insertFileToEditor(title, $li.data('url'), $li.data('image') == 1);
            });

            // 上传完成后增强新项
            var originalUploadComplete = Typecho.uploadComplete;
            Typecho.uploadComplete = function(attachment) {
                setTimeout(function() {
                    enhanceFileList();
                }, 200);
                if (typeof originalUploadComplete === 'function') {
                    originalUploadComplete(attachment);
                }
            };

            // 首次增强
            enhanceFileList();
        });
        </script>
        <?php
    }
}
?>

本文由 老孙博客 原创发布

转载请注明出处

  •  

清明节 · 香港一日游

2025年4月5日 07:56

作为一个在广东生活了十几年的人来说,没去过香港算不算得上是一件很稀罕的事儿呢?

老板请客

不得不说,香港的物价真的离谱.

香港街头

摩天轮
游客贼多,摩天轮都要排队两小时

菲佣节日聚会
天桥下,地下通道出入口很多菲律宾人

摆渡轮
坐摆渡轮也排队一个小时

星光大道

星光大道

星光大道李小龙

太空馆


本文由 老孙博客 原创发布

转载请注明出处

  •  

QQ-weather-bot

2025年3月25日 17:23

由于最近QQ风控似乎更加严格了,有两个QQ号码被封号,还有一个经常掉线.

为了检测QQ机器人是否在线 我就让 Claude 3.5 写了一个定时发送消息的 python 程序

单纯只发个消息未免 单调,于是 就调用高德地图 的 天气查询 APi 查询 指定地点的天气情况

于是 实现了 指定QQ机器人 给 指定 QQ用户 指定频率 发送 天气情况的功能

2025-03-25T09:21:37.png

我真是太机智了,一下子就解决了 两个问题

Docker镜像 jkjoy/qq-weather-bot


本文由 老孙博客 原创发布

转载请注明出处

  •  

最近一周 &amp; 不吐不快

2025年3月25日 17:06

本来日子平平淡淡.没有波澜.
什么也不想写.


把主题更新了一下,把之前没有完善的功能完善一番,优化了部分功能的实现方式


重新移植了一个主题,就是因为上班摸鱼随便弄着玩.


前两天荆门市宣传部突然打电话找我,说我已备案的网站上出现了不合规定的内容责令我删除.

主要内容有两处:

  1. 含有vmess科学上网的内容
  2. 含有ChatGPT相关内容

翻墙我可以理解,但是chatgpt 为啥就不行呢
我好奇问了一句

给我回复是,chatgpt 国内能用不?是不是得翻墙?

此话一出我竟无言以对.

于是删除了相关页面.


本以为这事情就完事了

昨天,又打电话来询问.
问能不能提供文章删除的证明.
我说文章都已经删除了,怎么还需要我证明我已经删除了呢?
你访问不到不就证明我删除了么


本文由 老孙博客 原创发布

转载请注明出处

  •  
❌