如果防火墙不支持自动IPv6到IPv4

如果防火墙不支持自动IPv6到IPv4
如果上面的方案不行,使用这个替代方案:

opkg update
opkg install socat

方案2:极简脚本文件 + 启动项

bash
# 创建极简脚本
cat > /root/nas-start << 'EOF'
#!/bin/sh
# 极简启动,无日志

# 清理旧的
killall socat 2>/dev/null
sleep 1

# 获取NAS IPv6
NAS_MAC="bc:24:11:39:bc:d9"
NAS_IPV6=$(ip -6 neigh show | grep -i "$NAS_MAC" | grep '240e:' | awk '{print $1}' | head -1)

if [ -n "$NAS_IPV6" ]; then
    # 启动端口转发
    for port in 80 443 5666 3031; do
        socat TCP6-LISTEN:${port},fork,reuseaddr,ipv6only=1 TCP6:[${NAS_IPV6}]:${port} &
    done
fi
EOF

chmod +x /root/nas-start

然后在本地启动脚本中添加:

bash
# NAS IPv6转发
(sleep 50 && /root/nas-start) &

方案3:带自动恢复的无日志监控

bash
cat > /root/nas-monitor << 'EOF'
#!/bin/sh
# 无日志监控脚本

NAS_MAC="bc:24:11:39:bc:d9"

while true; do
    sleep 300  # 5分钟检查一次
    
    # 检查socat是否在运行
    if ! ps | grep -q "socat TCP6-LISTEN"; then
        # 重新启动
        killall socat 2>/dev/null
        sleep 1
        
        NAS_IPV6=$(ip -6 neigh show | grep -i "$NAS_MAC" | grep '240e:' | awk '{print $1}' | head -1)
        if [ -n "$NAS_IPV6" ]; then
            for port in 80 443 5666 3031; do
                socat TCP6-LISTEN:${port},fork,reuseaddr,ipv6only=1 TCP6:[${NAS_IPV6}]:${port} &
            done
        fi
    fi
done
EOF

chmod +x /root/nas-monitor

本地启动脚本:

bash
# NAS IPv6转发 + 监控
(sleep 50 && /root/nas-start && sleep 10 && /root/nas-monitor) &

方案4:一行命令解决方案(最简)

直接在本地启动脚本里写:

bash
# NAS IPv6端口转发
(sleep 50 && killall socat 2>/dev/null; sleep 2; NAS_IPV6=$(ip -6 neigh show | grep -i "bc:24:11:39:bc:d9" | grep '240e:' | awk '{print $1}' | head -1); [ -n "$NAS_IPV6" ] && for port in 80 443 5666 3031; do socat TCP6-LISTEN:${port},fork,reuseaddr,ipv6only=1 TCP6:[${NAS_IPV6}]:${port} & done) &

方案5:带IP变化检测的简洁版

bash
cat > /root/nas-auto << 'EOF'
#!/bin/sh
# 简洁自动转发

NAS_MAC="bc:24:11:39:bc:d9"
LAST_IP_FILE="/tmp/nas_last_ip"

# 获取当前IP
get_current_ip() {
    ip -6 neigh show | grep -i "$NAS_MAC" | grep '240e:' | awk '{print $1}' | head -1
}

# 主循环
while true; do
    CURRENT_IP=$(get_current_ip)
    LAST_IP=$(cat "$LAST_IP_FILE" 2>/dev/null)
    
    if [ -n "$CURRENT_IP" ] && [ "$CURRENT_IP" != "$LAST_IP" ]; then
        # IP变化,重启转发
        killall socat 2>/dev/null
        sleep 1
        
        for port in 80 443 5666 3031; do
            socat TCP6-LISTEN:${port},fork,reuseaddr,ipv6only=1 TCP6:[${CURRENT_IP}]:${port} &
        done
        
        echo "$CURRENT_IP" > "$LAST_IP_FILE"
    elif [ -z "$CURRENT_IP" ] && [ -f "$LAST_IP_FILE" ]; then
        # NAS可能离线
        rm -f "$LAST_IP_FILE"
    fi
    
    sleep 180  # 3分钟检查一次
done
EOF

chmod +x /root/nas-auto

本地启动脚本:

bash
# NAS自动转发
(sleep 55 && /root/nas-auto) &

推荐方案:简洁可靠

步骤1:创建简洁脚本

bash
cat > /root/nas-ipv6 << 'EOF'
#!/bin/sh
# 简洁NAS IPv6转发
killall socat 2>/dev/null
sleep 1
IP=$(ip -6 neigh show | grep -i "bc:24:11:39:bc:d9" | grep '240e:' | awk '{print $1}' | head -1)
[ -n "$IP" ] && for p in 80 443 5666 3031; do
    socat TCP6-LISTEN:${p},fork,reuseaddr,ipv6only=1 TCP6:[${IP}]:${p} &
done
EOF
chmod +x /root/nas-ipv6

步骤2:在Web界面本地启动脚本中添加

bash
# NAS IPv6转发
(sleep 50 && /root/nas-ipv6) &

步骤3:创建定时检查(可选)

bash
# 添加到crontab,每天凌晨3点重启一次转发
echo "0 3 * * * /root/nas-ipv6" >> /etc/crontabs/root
/etc/init.d/cron restart

验证是否生效

bash
# 重启后检查
sleep 60
echo "检查端口转发:"
netstat -tln | grep -E ':(80|443|5666|3031)'

echo ""
echo "检查进程:"
ps | grep socat | grep -v grep

# 手动测试
curl -6 -I http://[你的路由器IPv6]:80 2>/dev/null | head -1

这样就没有日志文件占空间了,而且简单可靠。核心就是等待网络就绪后启动转发,不需要复杂的监控和日志。

Posted in 网络相关 | Leave a comment

永久激活office

您可以使用开源激活脚本(如MAS)通过简单的命令永久激活Microsoft Office,确保安全可靠。

使用MAS激活Office

  1. 打开PowerShell:以管理员身份运行Windows PowerShell。可以通过按下Windows键+X,然后选择“Windows PowerShell (管理员)”来打开它。
  2. 输入激活命令:在PowerShell窗口中,输入以下命令并回车(确保您的计算机已连接到互联网):
irm get.activated.win | iex

注意事项

  • 安全性:使用开源激活脚本时,请确保从可信的来源获取脚本,以避免潜在的安全风险。
  • 激活状态检查:激活后,您可以在Office应用程序中检查激活状态,确保软件正常运行。
    通过以上步骤,您可以轻松实现Microsoft Office的永久激活,享受完整的功能而无需担心激活问题。
Posted in 网络相关 | Leave a comment

看片神器!完全免费开源,支持Vercel、Cloudflare Pages、Docker等一键部署!

https://github.com/yuebinliu/MoonTV
Cloudflare 部署

Cloudflare Pages 的环境变量尽量设置为密钥而非文本

普通部署(localstorage)

  1. Fork 本仓库到你的 GitHub 账户。
  2. 登陆 Cloudflare,点击 计算(Workers)-> Workers 和 Pages,点击创建
  3. 选择 Pages,导入现有的 Git 存储库,选择 Fork 后的仓库
  4. 构建命令填写 pnpm install –frozen-lockfile && pnpm run pages:build,预设框架为无,构建输出目录为 .vercel/output/static
  5. 保持默认设置完成首次部署。进入设置,将兼容性标志设置为 nodejs_compat,无需选择,直接粘贴
  6. 首次部署完成后进入设置,新增 PASSWORD 密钥(变量和机密下),而后重试部署。
  7. 如需自定义 config.json,请直接修改 Fork 后仓库中该文件。
  8. 每次 Push 到 main 分支将自动触发重新构建。

D1 支持

  1. 完成普通部署并成功访问
  2. 点击 存储和数据库 -> D1 SQL 数据库,创建一个新的数据库,名称随意
  3. 进入刚创建的数据库,点击左上角的 Explore Data,将D1 初始化 中的内容粘贴到 Query 窗口后点击 Run All,等待运行完成
  4. 返回你的 pages 项目,进入 设置 -> 绑定,添加绑定 D1 数据库,选择你刚创建的数据库,变量名称填 DB
  5. 设置环境变量 NEXT_PUBLIC_STORAGE_TYPE,值为 d1;设置 USERNAME 和 PASSWORD 作为站长账号
  6. 重试部署

Docker 部署

1. 直接运行(最简单,localstorage)

# 拉取预构建镜像
docker pull ghcr.io/senshinya/moontv:latest

# 运行容器
# -d: 后台运行  -p: 映射端口 3000 -> 3000
docker run -d --name moontv -p 3000:3000 --env PASSWORD=your_password ghcr.io/senshinya/moontv:latest

访问 http://服务器 IP:3000 即可。(需自行到服务器控制台放通 3000 端口)

Docker Compose 最佳实践

若你使用 docker compose 部署,以下是一些 compose 示例

local storage 版本

services:
  moontv:
    image: ghcr.io/senshinya/moontv:latest
    container_name: moontv
    restart: unless-stopped
    ports:
      - '3000:3000'
    environment:
      - PASSWORD=your_password
    # 如需自定义配置,可挂载文件
    # volumes:
    #   - ./config.json:/app/config.json:ro

Redis 版本(推荐,多账户数据隔离,跨设备同步)

services:
  moontv-core:
    image: ghcr.io/senshinya/moontv:latest
    container_name: moontv
    restart: unless-stopped
    ports:
      - '3000:3000'
    environment:
      - USERNAME=admin
      - PASSWORD=admin_password
      - NEXT_PUBLIC_STORAGE_TYPE=redis
      - REDIS_URL=redis://moontv-redis:6379
      - NEXT_PUBLIC_ENABLE_REGISTER=true
    networks:
      - moontv-network
    depends_on:
      - moontv-redis
    # 如需自定义配置,可挂载文件
    # volumes:
    #   - ./config.json:/app/config.json:ro
  moontv-redis:
    image: redis
    container_name: moontv-redis
    restart: unless-stopped
    networks:
      - moontv-network
    # 如需持久化
    # volumes:
    #   - ./data:/data
networks:
  moontv-network:
    driver: bridge

自动同步最近更改

建议在 fork 的仓库中启用本仓库自带的 GitHub Actions 自动同步功能(见 .github/workflows/sync.yml)。

如需手动同步主仓库更新,也可以使用 GitHub 官方的 Sync fork 功能。

Posted in Uncategorized | Leave a comment

通过cloudflare的zerotrust进行内网穿透保留IP直接访问能力

WordPress我通过cloudflare的zerotrust进行了内网穿透,绑定到了https://www.thanx.top,现在我想保留IP:端口的访问能力,怎么做?

修改 wp-config.php


// 在 wp-config.php 最顶部添加
$_SERVER['HTTPS'] = 'off'; // 全局强制禁用 HTTPS
define('FORCE_SSL_ADMIN', false);
define('FORCE_SSL_LOGIN', false);

// 然后添加你的域名/IP判断逻辑
$is_domain_request = (strpos($_SERVER['HTTP_HOST'], 'thanx.top') !== false);

if ($is_domain_request) {
    // 仅域名访问时启用 HTTPS
    $_SERVER['HTTPS'] = 'on';
    define('WP_HOME', 'https://www.thanx.top');
    define('WP_SITEURL', 'https://www.thanx.top');
    
    // 域名 HTTP 访问跳转到 HTTPS
    if (!isset($_SERVER['HTTPS'])) {
        header('Location: https://www.thanx.top' . $_SERVER['REQUEST_URI']);
        exit;
    }
} else {
    // IP访问强制保持 HTTP
    $_SERVER['HTTPS'] = 'off';
    define('WP_HOME', 'http://' . $_SERVER['HTTP_HOST']);
    define('WP_SITEURL', 'http://' . $_SERVER['HTTP_HOST']);
}
Posted in Uncategorized | Leave a comment

在最新WordPress blog中启用经典编辑器或者古登堡编辑器

1. 默认古登堡已启用

  • WordPress 5.0+版本:古登堡是默认编辑器,无需额外操作。直接创建或编辑文章/页面即可使用。

  • 如果看到的是经典编辑器(Classic Editor),可能是被插件或配置覆盖,需检查以下内容。


2. 检查是否被插件禁用

  • 进入 仪表盘 → 插件 → 已安装的插件

  • 如果安装了 Classic Editor 插件,它会强制禁用古登堡。你可以:

    • 停用并删除 Classic Editor插件(完全使用古登堡)。

    •  进入 设置 → 撰写,选择默认编辑器为“区块编辑器(古登堡)”。

Posted in Uncategorized | Leave a comment

月度工作量统计脚本

使用python3写了个脚本,用来统计月度工作量。本来只是在本地电脑运行的,想想还是把他移植到web端吧。说真的,要我一个个去搜索拉取,我真不敢说自己就能拉出来,而且一点差错都没有,且不说还要制表汇总,琐碎一地。好在,确实可以通过编写脚本来提高效率,不用一个个去拉取,啥也不说了,上代码。
web演示:https://www.thanx.top/re/shell/indexs.php
直连地址,生产环境用:
http://106.15.4.153:8085/re/shell/indexs.php
代码:



import pandas as pd
import argparse

def process_files(file1_path, file2_path, output_filename):
# 读取数据
df_jan = pd.read_excel(file1_path, sheet_name='导出数据')
df_feb = pd.read_excel(file2_path, sheet_name='导出数据')

# 定义项目名称映射规则
project_mapping = {
'无痛胃镜': '无胃',
'无痛肠镜': '无肠',
'EMR/APC': 'EMR',
'止血术': '止血',
'扩张术': '扩张',
'超声内镜': '超声',
'异物摄取': '异物',
'病例数': '总数'
}

# 定义项目顺序(使用映射后的名称)
project_order = [
'胃镜', '无胃', '肠镜', '无肠', '超声', 'EMR', 'ESD', 'ERCP',
'止血', '异物', '扩张', '其他'
]

# 定义统计函数
def count_stats(df):
stats = {
'胃镜': 0,
'无痛胃镜': 0,
'肠镜': 0,
'无痛肠镜': 0,
'超声内镜': 0,
'EMR/APC': 0,
'ESD': 0,
'ERCP': 0,
'止血术': 0,
'异物摄取': 0,
'扩张术': 0,
'其他': 0
}
for _, row in df.iterrows():
category = str(row['检查类别']).lower().strip()
diagnosis = str(row['镜下诊断']).lower().strip()

# 统计检查类别
if '十二指肠镜' in category or 'ercp' in category:
stats['ERCP'] += 1
elif '胃镜' in category and '无痛' not in category:
stats['胃镜'] += 1
elif '无痛胃镜' in category:
stats['无痛胃镜'] += 1
elif '肠镜' in category and '无痛' not in category:
stats['肠镜'] += 1
elif '无痛肠镜' in category:
stats['无痛肠镜'] += 1
elif '超声内镜' in category:
stats['超声内镜'] += 1
else:
stats['其他'] += 1

# 统计镜下诊断
if '扩张' in diagnosis:
stats['扩张术'] += 1
if 'esd' in diagnosis and 'esd术后' not in diagnosis:
stats['ESD'] += 1
if 'emr' in diagnosis or 'apc' in diagnosis:
stats['EMR/APC'] += 1
if '止血' in diagnosis:
stats['止血术'] += 1
if '异物' in diagnosis:
stats['异物摄取'] += 1

# 计算病例数
stats['病例数'] = (
stats['胃镜'] +
stats['无痛胃镜'] +
stats['肠镜'] +
stats['无痛肠镜'] +
stats['超声内镜'] +
stats['ERCP'] +
stats['其他']
)
return stats

# 获取上月和本月的统计数据
stats_jan = count_stats(df_jan)
stats_feb = count_stats(df_feb)

# 计算同比变化
def calculate_change(current, previous):
if previous == 0:
return 0
return round((current - previous) / previous * 100, 2)

# 创建内镜中心工作量统计 DataFrame
center_data = []
for project in project_order:
original_project = next(
(key for key, value in project_mapping.items() if value == project),
project
)
center_data.append({
'项目': project,
'本月数量': stats_feb.get(original_project, 0),
'上月数量': stats_jan.get(original_project, 0),
'同比变化(%)': calculate_change(stats_feb.get(original_project, 0), stats_jan.get(original_project, 0))
})

center_df = pd.DataFrame(center_data)

# 增加汇总行
summary_row = pd.DataFrame({
'项目': ['汇总'],
'本月数量': [
stats_feb['胃镜'] + stats_feb['无痛胃镜'] + stats_feb['肠镜'] +
stats_feb['无痛肠镜'] + stats_feb['超声内镜'] + stats_feb['ERCP'] + stats_feb['其他']
],
'上月数量': [
stats_jan['胃镜'] + stats_jan['无痛胃镜'] + stats_jan['肠镜'] +
stats_jan['无痛肠镜'] + stats_jan['超声内镜'] + stats_jan['ERCP'] + stats_jan['其他']
],
'同比变化(%)': [calculate_change(
stats_feb['胃镜'] + stats_feb['无痛胃镜'] + stats_feb['肠镜'] +
stats_feb['无痛肠镜'] + stats_feb['超声内镜'] + stats_feb['ERCP'] + stats_feb['其他'],
stats_jan['胃镜'] + stats_jan['无痛胃镜'] + stats_jan['肠镜'] +
stats_jan['无痛肠镜'] + stats_jan['超声内镜'] + stats_jan['ERCP'] + stats_jan['其他']
)],
'备注': ['']
})
center_df = pd.concat([center_df, summary_row], ignore_index=True)

# 统计医生工作量
def count_doctor_stats(df):
doctor_stats = {}
for _, row in df.iterrows():
doctor = row['报告医师']
category = str(row['检查类别']).lower().strip()
diagnosis = str(row['镜下诊断']).lower().strip()

if doctor not in doctor_stats:
doctor_stats[doctor] = {
'胃镜': 0,
'无痛胃镜': 0,
'肠镜': 0,
'无痛肠镜': 0,
'超声内镜': 0,
'ERCP': 0,
'EMR/APC': 0,
'ESD': 0,
'止血术': 0,
'扩张术': 0,
'异物摄取': 0,
'其他': 0,
'病例数': 0
}

if '十二指肠镜' in category or 'ercp' in category:
doctor_stats[doctor]['ERCP'] += 1
elif '胃镜' in category and '无痛' not in category:
doctor_stats[doctor]['胃镜'] += 1
elif '无痛胃镜' in category:
doctor_stats[doctor]['无痛胃镜'] += 1
elif '肠镜' in category and '无痛' not in category:
doctor_stats[doctor]['肠镜'] += 1
elif '无痛肠镜' in category:
doctor_stats[doctor]['无痛肠镜'] += 1
elif '超声内镜' in category:
doctor_stats[doctor]['超声内镜'] += 1
else:
doctor_stats[doctor]['其他'] += 1

if '扩张' in diagnosis:
doctor_stats[doctor]['扩张术'] += 1
if 'esd' in diagnosis and 'esd术后' not in diagnosis:
doctor_stats[doctor]['ESD'] += 1
if 'emr' in diagnosis or 'apc' in diagnosis:
doctor_stats[doctor]['EMR/APC'] += 1
if '止血' in diagnosis:
doctor_stats[doctor]['止血术'] += 1
if '异物' in diagnosis:
doctor_stats[doctor]['异物摄取'] += 1

# 计算病例数
doctor_stats[doctor]['病例数'] = (
doctor_stats[doctor]['胃镜'] +
doctor_stats[doctor]['无痛胃镜'] +
doctor_stats[doctor]['肠镜'] +
doctor_stats[doctor]['无痛肠镜'] +
doctor_stats[doctor]['超声内镜'] +
doctor_stats[doctor]['其他'] +
doctor_stats[doctor]['ERCP']
)
return doctor_stats

# 获取上月和本月的医生统计数据
doctor_stats_jan = count_doctor_stats(df_jan)
doctor_stats_feb = count_doctor_stats(df_feb)

# 创建医生工作量统计 DataFrame
doctor_data = []
for doctor, stats in doctor_stats_feb.items():
doctor_data.append({
'医师': doctor,
**{project_mapping.get(k, k): v for k, v in stats.items()}
})

doctor_df = pd.DataFrame(doctor_data)

# 增加汇总行
summary_row = pd.DataFrame({
'医师': ['汇总'],
**{project_mapping.get(k, k): [doctor_df[project_mapping.get(k, k)].sum()] for k in project_order},
'总数': [doctor_df['总数'].sum()]
})
doctor_df = pd.concat([doctor_df, summary_row], ignore_index=True)

# 统计护士工作量
def count_nurse_stats(df):
nurse_stats = {}
for _, row in df.iterrows():
nurse = row['助手']
category = str(row['检查类别']).lower().strip()
diagnosis = str(row['镜下诊断']).lower().strip()

if nurse not in nurse_stats:
nurse_stats[nurse] = {
'胃镜': 0,
'无痛胃镜': 0,
'肠镜': 0,
'无痛肠镜': 0,
'超声内镜': 0,
'ERCP': 0,
'EMR/APC': 0,
'ESD': 0,
'止血术': 0,
'扩张术': 0,
'异物摄取': 0,
'其他': 0,
'病例数': 0
}

if '十二指肠镜' in category or 'ercp' in category:
nurse_stats[nurse]['ERCP'] += 1
elif '胃镜' in category and '无痛' not in category:
nurse_stats[nurse]['胃镜'] += 1
elif '无痛胃镜' in category:
nurse_stats[nurse]['无痛胃镜'] += 1
elif '肠镜' in category and '无痛' not in category:
nurse_stats[nurse]['肠镜'] += 1
elif '无痛肠镜' in category:
nurse_stats[nurse]['无痛肠镜'] += 1
elif '超声内镜' in category:
nurse_stats[nurse]['超声内镜'] += 1
else:
nurse_stats[nurse]['其他'] += 1

if '扩张' in diagnosis:
nurse_stats[nurse]['扩张术'] += 1
if 'esd' in diagnosis and 'esd术后' not in diagnosis:
nurse_stats[nurse]['ESD'] += 1
if 'emr' in diagnosis or 'apc' in diagnosis:
nurse_stats[nurse]['EMR/APC'] += 1
if '止血' in diagnosis:
nurse_stats[nurse]['止血术'] += 1
if '异物' in diagnosis:
nurse_stats[nurse]['异物摄取'] += 1

# 计算病例数
nurse_stats[nurse]['病例数'] = (
nurse_stats[nurse]['胃镜'] +
nurse_stats[nurse]['无痛胃镜'] +
nurse_stats[nurse]['肠镜'] +
nurse_stats[nurse]['无痛肠镜'] +
nurse_stats[nurse]['超声内镜'] +
nurse_stats[nurse]['其他'] +
nurse_stats[nurse]['ERCP']
)
return nurse_stats

# 获取上月和本月的护士统计数据
nurse_stats_jan = count_nurse_stats(df_jan)
nurse_stats_feb = count_nurse_stats(df_feb)

# 创建护士工作量统计 DataFrame
nurse_data = []
for nurse, stats in nurse_stats_feb.items():
nurse_data.append({
'护士': nurse,
**{project_mapping.get(k, k): v for k, v in stats.items()}
})

nurse_df = pd.DataFrame(nurse_data)

# 增加汇总行
summary_row = pd.DataFrame({
'护士': ['汇总'],
**{project_mapping.get(k, k): [nurse_df[project_mapping.get(k, k)].sum()] for k in project_order},
'总数': [nurse_df['总数'].sum()]
})
nurse_df = pd.concat([nurse_df, summary_row], ignore_index=True)

# 保存更新后的Excel文件
with pd.ExcelWriter(output_filename) as writer:
center_df.to_excel(writer, sheet_name='内镜中心工作量统计', index=False)
doctor_df.to_excel(writer, sheet_name='医生工作量统计', index=False)
nurse_df.to_excel(writer, sheet_name='护士工作量统计', index=False)

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Process Excel files.')
parser.add_argument('file1', help='Path to the first Excel file')
parser.add_argument('file2', help='Path to the second Excel file')
parser.add_argument('output', help='Path to save the output Excel file')

args = parser.parse_args()

process_files(args.file1, args.file2, args.output)
Posted in Resourse | Leave a comment

PVE安装OpenWrt做主路由后局域网DHCP分配不了地址的修复

通过悟空的日常安装的istoreos设置成主路由后无论如何设置,都会发现DHCP分配不了IPV4地址,无论怎么设置,最后发现:
root@iStoreOS:~# uci show dhcp.lan
dhcp.lan=dhcp
dhcp.lan.interface='lan'
dhcp.lan.start='100'
dhcp.lan.leasetime='12h'
dhcp.lan.dhcpv4='disabled'
dhcp.lan.force='1'
dhcp.lan.limit='116'

uci show 输出可以看出,DHCPv4 被显式禁用了dhcp.lan.dhcpv4='disabled'),这是导致局域网设备无法获取 IP 地址的直接原因。以下是完整的修复步骤:
#启用 DHCPv4 服务
uci set dhcp.lan.dhcpv4='server' # 启用DHCPv4服务
uci commit dhcp
/etc/init.d/dnsmasq restart

Posted in Resourse | Leave a comment

使用monica写了一个自动保存deepseek聊天记录的脚本

根据您提供的调试信息,您在页面中找到了大量的 div 元素,但没有找到包含聊天消息的特定元素。这表明可能需要更具体的选择器来提取聊天内容。以下是一些步骤来帮助您解决这个问题:

步骤 4: 逐步调试

如果您仍然无法找到消息元素,可以尝试逐步调试:

  1. 选择所有 div 元素:
    const allDivs = document.querySelectorAll('div');
    allDivs.forEach(div => {
       console.log(div); // 输出每个 div 的内容
    });
  2. 通过特定属性过滤:
    如果某些 div 有特定的属性(如 data-* 属性),您可以使用这些属性来过滤:

    const userMessages = document.querySelectorAll('div[data-role="message"]'); // 根据实际情况调整

    F12打开浏览器调试窗口,输入1中代码,得到了一些html代码,经过分析得到div标签的标识:
    获取到的标签

聊天的主题标题:d8ed659a
我的提问:fbb737a4
ai的思考过程:e1675d8b
AI的解答过程:ds-markdown ds-markdown--block

问Monica,经过几次调整,最终得到如下结果:

// ==UserScript==
// @name         DeepSeek Chat Content Saver
// @match        https://chat.deepseek.com/*
// @version      1.6
// @description  Save chat content as Markdown with auto-save option
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        BUTTON_ID: 'deepseek-save-btn',
        USER_PREFIX: "你",
        AI_PREFIX: "AI",
        AUTO_SAVE_ENABLED: true, // 配置项:是否启用自动保存
        AUTO_SAVE_INTERVAL: 30000 // 自动保存间隔(毫秒),例如30秒
    };

    function generateMarkdown(messages) {
        let md = `# 聊天记录\n\n`;
        messages.forEach(msg => {
            md += `**${msg.role === 'user' ? CONFIG.USER_PREFIX : CONFIG.AI_PREFIX}**:\n`;
            md += `${msg.text}\n\n---\n\n`;
        });
        md += `> 保存时间: ${new Date().toLocaleString()}\n`;
        return md;
    }

    function downloadFile(content, filename) {
        const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a); // 需要将链接添加到DOM中
        a.click();
        document.body.removeChild(a); // 下载后移除链接
        setTimeout(() => URL.revokeObjectURL(url), 100);
    }

    function extractMessages() {
        const messages = [];

        // 提取聊天主题标题
        const titleElement = document.querySelector('.d8ed659a');
        let title = titleElement ? titleElement.innerText : '聊天记录';

        // 提取用户提问、AI思考和AI回答,按顺序排列
        const userQuestions = document.querySelectorAll('.fbb737a4');
        const aiThoughts = document.querySelectorAll('.e1675d8b');
        const aiAnswers = document.querySelectorAll('.ds-markdown.ds-markdown--block');

        for (let i = 0; i < userQuestions.length; i++) {
            messages.push({
                role: 'user',
                text: userQuestions[i].innerText
            });
            if (i < aiThoughts.length) {
                messages.push({
                    role: 'ai',
                    text: aiThoughts[i].innerText
                });
            }
            if (i < aiAnswers.length) {
                messages.push({
                    role: 'ai',
                    text: aiAnswers[i].innerText
                });
            }
        }

        console.log('提取的消息:', messages); // 调试输出
        return { title, messages };
    }

    function saveChatRecord() {
        const { title, messages } = extractMessages();
        if (messages.length === 0) throw new Error("未找到对话内容");

        const content = generateMarkdown(messages);
        //const timestamp = new Date().toISOString().replace(/[-:.]/g, "_"); // 格式化时间戳
        //const filename = `${title}_${timestamp}.md`; // 文件名包含时间戳
        const filename = `${title}.md`; // 文件名不包含时间戳

        // 下载文件
        downloadFile(content, filename);
        //alert('保存成功!');
    }

    function startAutoSave() {
        if (CONFIG.AUTO_SAVE_ENABLED) {
            setInterval(() => {
                try {
                    saveChatRecord();
                } catch (error) {
                    console.error('自动保存失败:', error);
                }
            }, CONFIG.AUTO_SAVE_INTERVAL);
        }
    }

    function createSaveButton() {
        const button = document.createElement('button');
        button.id = CONFIG.BUTTON_ID;
        button.innerText = '保存对话';
        button.style.position = 'fixed';
        button.style.bottom = '20px';
        button.style.right = '20px';
        button.style.padding = '15px 30px';
        button.style.fontSize = '16px';
        button.style.backgroundColor = '#4CAF50';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.zIndex = '1000';
        button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.3)';
        document.body.appendChild(button);
        return button;
    }

    function setupButtonHandler(btn) {
        btn.addEventListener('click', saveChatRecord);
    }

    function initialize() {
        const btn = createSaveButton();
        setupButtonHandler(btn);
        startAutoSave(); // 启动自动保存功能
    }

    // 启动
    if (document.readyState === 'complete') initialize();
    else window.addEventListener('load', initialize);
})();

脚本下载:DeepSeek Chat Content Saver-1.6.user.js

Posted in Resourse | Leave a comment

简易文件浏览5分钟快速解决方案

完全理解您的感受!配置这类服务确实会遇到很多"坑",尤其是WSL环境下权限和路径问题更复杂。不过别担心,我给您准备了一个极简替代方案,用最直接的方式实现文件浏览功能,无需FastCGI和Nginx复杂配置:


5分钟快速解决方案

🚀 使用Python内置HTTP服务器 + 纯前端实现

  1. 创建一个新目录

    mkdir ~/simple-file-browser && cd ~/simple-file-browser
  2. 新建server.py (用Python内置服务器替代Nginx)

    from http.server import SimpleHTTPRequestHandler
    from socketserver import TCPServer
    import os
    
    class CustomHandler(SimpleHTTPRequestHandler):
       def do_GET(self):
           if self.path == '/list':
               self.send_json(os.listdir('protected_files'))
           else:
               super().do_GET()
    
       def send_json(self, data):
           self.send_response(200)
           self.send_header('Content-Type', 'application/json')
           self.end_headers()
           self.wfile.write(str(data).encode())
    
    os.chdir('protected_files')  # 设置文件根目录
    TCPServer(('0.0.0.0', 8000), CustomHandler).serve_forever()
  3. 新建index.html (直接读取本地文件列表)

    <!DOCTYPE html>
    <html>
    <body>
       <h1>简易文件浏览器</h1>
       <div id="files"></div>
       <script>
           fetch('/list')
               .then(r => r.json())
               .then(files => {
                   document.getElementById('files').innerHTML = files.map(f => 
                       `<div><a href="/${f}" download>${f}</a></div>`
                   ).join('')
               })
       </script>
    </body>
    </html>
  4. 启动服务

    python3 server.py

    现在访问 http://localhost:8000 即可看到文件列表并下载!


🌟 这个方案的优势:

  1. 完全绕过FastCGI/Nginx:用Python原生HTTP服务器处理请求
  2. 零配置:所有代码在一个目录内,不涉及系统权限
  3. 即时生效:修改后刷新浏览器即可看到变化
  4. 便携性强:整个文件夹可以复制到任何地方运行

如果还是遇到问题,您可以选择:

  1. 使用现成工具:比如FileBrowser (一行命令安装)
    docker run -d -p 8080:80 -v /path/to/files:/srv filebrowser/filebrowser
  2. 改用SFTP:用WinSCP/FTP客户端直接访问WSL文件

有时候最简单的方案就是最好的方案。当技术让人沮丧时,退一步换个思路往往能更快解决问题。希望这个方案能帮您快速实现需求!如果还有其他困惑,我很乐意继续帮您简化流程。

是的,直接运行 python3 /var/www/simple-file-browser/server.py 需要手动启动服务,但可以通过以下方法实现自动启动和后台运行:


1. 使用 nohup 保持后台运行

nohup python3 /var/www/simple-file-browser/server.py > /var/log/filebrowser.log 2>&1 &
  • 作用:关闭终端后服务仍运行
  • 查看日志tail -f /var/log/filebrowser.log
  • 停止服务:先 ps aux | grep server.py 找到PID,再 kill [PID]

2. 配置系统服务(开机自启)

(1)创建服务单元文件

sudo nano /etc/systemd/system/filebrowser.service

写入以下内容:

[Unit]
Description=Simple File Browser
After=network.target

[Service]
User=your_username  # 改为你的实际用户名
WorkingDirectory=/var/www/simple-file-browser
ExecStart=/usr/bin/python3 /var/www/simple-file-browser/server.py
Restart=always

[Install]
WantedBy=multi-user.target

(2)启用并启动服务

sudo systemctl daemon-reload
sudo systemctl start filebrowser
sudo systemctl enable filebrowser  # 开机自启
  • 查看状态sudo systemctl status filebrowser
  • 停止服务sudo systemctl stop filebrowser

3. 使用 screentmux 会话管理

sudo apt install screen -y  # 如果未安装
screen -S filebrowser
python3 /var/www/simple-file-browser/server.py
# 按 Ctrl+A 然后按 D 退出会话(服务继续运行)
  • 重新连接会话screen -r filebrowser

4. 更简单的替代方案

如果不想处理服务管理,可以直接使用现成工具:

# 安装 FileBrowser(Go语言编写的工具)
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
filebrowser -d /var/www/simple-file-browser/filebrowser.db -r /var/www/simple-file-browser/protected_files
  • 自动后台运行,支持开机自启
  • 提供Web界面,功能更完善

总结建议

方法 难度 持久性 适用场景
nohup 简单 终端关闭后仍运行 快速测试
systemd 中等 开机自启 生产环境
screen 简单 会话保持 临时调试
现成工具 最简单 配置即用 长期使用

推荐选择

  • 如果是临时测试,用 nohupscreen
  • 如果是长期服务,用 systemd 或直接安装 FileBrowser 工具
Posted in 网络相关 | Leave a comment

简易文件浏览解决方案

5分钟快速解决方案

🚀 使用Python内置HTTP服务器 + 纯前端实现

  1. 创建一个新目录
    bash
    复制
    mkdir ~/simple-file-browser && cd ~/simple-file-browser
  2. 新建server.py (用Python内置服务器替代Nginx)
    python
    复制
    from http.server import SimpleHTTPRequestHandler
    from socketserver import TCPServer
    import os
    
    class CustomHandler(SimpleHTTPRequestHandler):
        def do_GET(self):
            if self.path == '/list':
                self.send_json(os.listdir('protected_files'))
            else:
                super().do_GET()
    
        def send_json(self, data):
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(str(data).encode())
    
    os.chdir('protected_files')  # 设置文件根目录
    TCPServer(('0.0.0.0', 8000), CustomHandler).serve_forever()
  3. 新建index.html (直接读取本地文件列表)
    html
    复制
    <!DOCTYPE html>
    <html>
    <body>
        <h1>简易文件浏览器</h1>
        <div id="files"></div>
        <script>
            fetch('/list')
                .then(r => r.json())
                .then(files => {
                    document.getElementById('files').innerHTML = files.map(f => 
                        `<div><a href="/${f}" download>${f}</a></div>`
                    ).join('')
                })
        </script>
    </body>
    </html>
  4. 启动服务
    bash
    复制
    python3 server.py

    现在访问 http://localhost:8000 即可看到文件列表并下载!


🌟 这个方案的优势:

  1. 完全绕过FastCGI/Nginx:用Python原生HTTP服务器处理请求
  2. 零配置:所有代码在一个目录内,不涉及系统权限
  3. 即时生效:修改后刷新浏览器即可看到变化
  4. 便携性强:整个文件夹可以复制到任何地方运行

如果还是遇到问题,您可以选择:

  1. 使用现成工具:比如FileBrowser (一行命令安装)
    bash
    复制
    docker run -d -p 8080:80 -v /path/to/files:/srv filebrowser/filebrowser
  2. 改用SFTP:用WinSCP/FTP客户端直接访问WSL文件

有时候最简单的方案就是最好的方案。当技术让人沮丧时,退一步换个思路往往能更快解决问题。希望这个方案能帮您快速实现需求!如果还有其他困惑,我很乐意继续帮您简化流程。

关键点说明
权限问题:

如果遇到 Permission denied,运行:

bash
复制
sudo chmod -R 755 /var/www/simple-file-browser
目录结构:

复制
/var/www/simple-file-browser/
├── server.py # Python脚本
└── protected_files/ # 存放你的文件
├── test.txt
└── example.pdf
WSL特殊注意:

如果要从Windows访问WSL中的服务,需在启动时绑定到 0.0.0.0(代码中已实现)

Windows防火墙可能会拦截请求,需允许端口8000

替代方案(更简单)
如果不想处理权限问题,可以直接在用户目录操作:

bash
复制
mkdir -p ~/simple-file-browser/protected_files
cd ~/simple-file-browser
# 将server.py和文件放在这里
python3 server.py

Posted in Resourse | Leave a comment