0%

近期翻了一下自己的blog, 看到好几年前的手搓代码做的有意思的东西(例 https://misumiio.github.io/2023/06/06/%E8%AE%A1%E7%AE%97%E5%B0%BA%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%8E%E5%88%B6%E4%BD%9C/ )并且回忆起当时快乐的手搓代码时光,感叹那样的时光不会再来了啊。不禁开始臆想如果我出生的时间,对应的时代是互联网的黄金时代,而非AI的黄金时代,会是怎样的一个场景呢?我读了随便一个什么大学,并不一定是名校,由兴趣进入计算机专业,上班就手搓代码,看看机器的运行状况,并没有什么复杂的东西,不一定要沾什么艰难的算法,就干个大专计算机也能干的活,赚了钱就回家休息,也不用考虑商业逻辑与人际关系,也没有网络小鬼跳脸,那是何等纯真自然而理想的生活方式啊,如同“风乎舞雩,浴乎沂,咏而归”一样。

相比纯正的技术宅,我写的东西并没有那么多,但是还是由兴趣写了一些。我最后一次在课程外手搓代码,最晚可以追溯到2025-02, 那时候我为linux开发了一个PDF双面打印与小册子打印的工具。(注:此时已经使用AI写UI了)遇到好像是pdf拼接的问题没法解决,上网抄代码,最后是在不知道什么地方找到了一个日本人写的代码,解决了这个问题。那时候还很有意思的是,好像还得把用的库锁版本,如果更新了就没法用了,说是有个函数弃用了,有新的写法,但是新的写法也没法复现原来的效果,感觉好像是新的库出bug了。

处于古法与现代写代码之间的,是AI时代与AI Agent时代之间。一年前的程设H,开发东西的搭档用gemini跑了好多东西(我没有谷歌账号,我跑不了),那时候就是在对话中复制代码,粘贴运行,复制报错信息并让AI修改;AI还可能把已经有的东西改坏掉……那时候他跑不通,然后我们就在青年之家,我用着他的电脑,调调改改给他搞通了跑起来了。感觉自己像一个老师傅,可能学习不过人家,编代码也竞争不过别人,但是遇到乱七八糟的问题我更有可能解决。现在想来那可能是我最后的价值体现,再后来就出现了Agent。

刚想写下自己这样的想法并不是拒绝AI带来的变化,因为我也享受到了AI带来的便利,真实地用AI开发了东西。随即发现不是的,我确实就是在惧怕AI的影响、在人的价值被AI替代之际迷茫,这样来说,可能我也可以算得最后一批前Ai时代的老东西了吧。现在来说,就算对什么东西有些兴趣,也只会让AI代工,不太可能愿意自己手搓代码了。开发些什么东西将来或许会成为我的工作,也或许不会。当手搓代码的乐趣处于被转化为工作和被更方便的东西替代这两者的两面夹击中,乐趣可能就不复存在了。

意义之塔上有什么?我有时练琴,那是为了什么;顶着补四个物理课,转向计算机专业,学着一个正在滑落的专业的基础课程,是为了什么?(学那些数理基础既没有什么重大的意义,也不是我本身想学的,因此不在讨论之列。)人不能总是只考虑前途、赚钱和机会成本,经济学上的概念不能取代意义。一方面,由于人的寿命有限,所以对自身未来的投资的收益是有尽头的。另一方面,在什么东西都可能被取代的时候,意义本身需要一个答案。

USO管弦乐团谱务小组时有不需要转调、只需要从总谱中摘取对应乐器的谱行并进行拼贴的工作,为了简化流程制作这个工具:

图

图

工具代码仓库地址:https://github.com/misumiio/pdfscorecut

在线使用:https://misumiio.github.io/pdfscorecut/

操作方法是只需要用鼠标纵向拖动选中需要的谱行,即可生成分谱预览,并可以下载分谱PDF。行间距和页边距等参数可以调整。

编写项目的背景

众所周知,Linux 系统一直缺乏一个好用的刻录光盘的软件。Brasero 软件的功能比较简单,而 k3b 是比较好用的软件,但是他采用的后端既不支持使用 UDF 的蓝光光盘续刻,也不支持使用iso9660的蓝光光盘进行续刻。也就是说在Linux系统下, 无法对蓝光光盘进行续刻 (Muliti-sessioning)。也就是说蓝光光盘必须一次性先准备好所有的文件进行刻录,之后没办法再追加了。这是对光盘存储空间的极大浪费。

k3b 软件并没有明确标明不支持蓝光光盘的续刻,而是在实际上刻录完第一次之后想要续刻的时候,提示介质不支持续刻了(具体的提示我已经忘记)。这样的一个问题曾经困扰我很久,之后我在互联网上找到一篇英文的帖子 https://forums.linuxmint.com/viewtopic.php?t=428014 ,讨论了这个问题。下面的人提出:k3b使用的后端均不支持蓝光光盘的UDF续刻,然而好在iso9660实际上并没有2GB的文件大小限制,所以我们可以仍旧使用iso9660。xorriso软件默认就打开最大级别的iso9660文件大小限制,所以我们可以用xorriso来刻录多分段的光盘。例如这篇帖子中回答者提供的示例代码:

# To address your DVD or Blu-ray dive as target device do:
iso=/dev/sr1
# But before using slow optical media, try with an image file on fast disk.
# (Make sure this file does not exist yet.)
iso=/...some.large.filesystem.../test.iso

# Choose some large test files
file1=/.../some_large_file
file2=/.../some_other_large_file

# Write first session
xorriso -outdev "$iso" -map "$file1" /file1

# Write second session
xorriso -dev "$iso" -map "$file2" /file2

# Mount on a GNU/Linux system
mount_point=/mnt/iso
sudo mount "$iso" "$mount_point"
ls -l "$mount_point"

这个工具只有命令行程序。虽然有对话模式可以在里面进行编辑,但是这些命令并不是和外面的bash完全兼容的,敲起来也不太习惯,学习成本比较高。因此我为这个工具开发了一个图形界面。

界面的主要操作逻辑就是左边是硬盘的文件系统,右边是光盘的文件系统。左右拖拽可以进行提取、刻录等计划,点击执行会最终执行。

截图

项目地址:https://github.com/misumiio/xorriso-gui

算起来也是将近要有一年半的时间没有发博客了。可能是因为事情比较繁忙,或者是也没有什么东西好发的。到最近这段时间,有东西想要发了,然而旧的电脑长期关机懒得掏出来,博客的源代码在旧的电脑上,于是鸽了很久没发。现在掏出来要拿它当做MC的服务器了,于是顺便把博客的目录迁移到新电脑上。

受益于学校发的免费大模型api token, 很多事情都可以自动操作了。于是我制作了很多资料,还编写了项目。

这篇blog主要介绍一下USTC大一需要用到的一些我自制的资料。

历年转专业数据可视化

USTC 转专业比较自由不过还是需要竞争,不然的话就去个性化。

这个项目显示了除了管理学院以外的各学院,申请转入的人数以及拟接收人数、比例等数据的可视化,供转专业时候决策参考。

项目位置:https://github.com/misumiio/USTC_Change_Major_data
访问网页:https://misumiio.github.io/USTC_Change_Major_data/

《中国近现代史纲要》机考题库提纲

USTC《中国近现代史纲要》课程有上机考试,很多道选择题都需要背诵。为了能够在刷题之前先对内容有一个纵览,以及更好的复习,我用AI将这题库中的七百多道题目制作成一个复习资料,包含了七百多道题目的所有考点,并且不考的内容都没写进去。所以这是一个USTC专属的史纲复习资料。

项目位置:https://github.com/misumiio/USTC_History_Computer_Test

《大学物理 II》课程板书数字化整理及资料

《大学物理》系列课程是中科大开给管院同学的通识类物理课程,难度低于《力学》《热学》《电磁学》《光学》《原子物理》系列课程。涂涛老师的《大学物理 II》风评超好,为了期末复习我们将板书数字化了。

感谢ALXR提供的板书截图、Daniel用AI将其数字化。

项目位置:https://github.com/misumiio/USTC_university_phisics_II_Tu-Tao_blackboard

可以查看all.pdf。

USTC LLM API 校外访问

USTC为学生提供了每日100元(实测使用Deepseek v4 pro按照较高的缓存命中率一天大概能使用2亿token)的LLM api调用服务,包括deepseek v4 pro、qwen3.6、glm-5.2等模型。但是这个模型服务仅限校内访问,网络通的微批嗯服务仅向教师开放,网络信息中心刚购买的新的VPN服务不支持linux。而学校提供的vlab虚拟机可以在学校教育网以外的环境访问。因此我在vlab上面部署服务,使用vlab作为跳板机转发,使得暑假放假回家的同学也有机会使用学校提供的LLM服务,召唤AI干活。

项目位置:github.com/misumiio/apiproxy

如果你想使用我提供的服务,你可以先访问https://frp-six.com:50766/crt下载证书,然后安装到系统中,然后配置opencode使用这一个地址(无需修改原先的api key)

具体操作步骤可以查看项目中的README.md

项目地址:https://github.com/LittleWalnutX/Multiple_PDF_Printer

为了补足linux个人桌面在多样化的打印功能上的不足,写了这个项目,例如Windows可以使用Adobe Acrobat DC来打印小册子,而linux没有这样的软件;甚至大多数软件不能双面打印,需要手动选择先逆页序打印偶数页,再打印奇数页。

项目生成PDF文件然后再手动打印。以后可能加入直接打印的方式。

目前已经开发完成的功能有:

双面打印
小册子打印

下面介绍一下思路: 双面打印的思路比较简单,只需要先把偶数页逆序提取出来,然后再提取奇数页,最后手动进行打印就可以了

小册子打印的思路比较复杂,其中的难点主要在于拼合PDF。几乎是直接引用了一个日本网站上的代码,然后修改了一下。

小册子打印需要解决另外一个问题就是页码的排布问题。观察一本钉好的小册子,可以发现,以16页的小册子为例,它的页码排布分别是,16,1,2,15, 14, 3, 4, 13 …..。这也就是说,第一页是最后一页,然后从头取两页,从后面取两页。

然后我们把它分成奇数页面和偶数页面,可以得到它的规律。详细的规律见代码。

然后写一下图形界面工具。这是用AI写的,把代码搬过来,然后改一下bug,再把自己的功能填上去就可以了。

这个地方还有一个问题,就是用tkinter写的代码,在linux的X11上,存在一个极大概率触发的问题,就是文件拖拽上去之后会直接闪退,并且报如下的错误:

X Error of failed request:  BadWindow (invalid Window parameter)
  Major opcode of failed request:  20 (X_GetProperty)
  Resource id in failed request:  0x1417efd
  Serial number of failed request:  1225
  Current serial number in output stream:  1225

排查了很长时间,我觉得这个问题不是我的问题,可能是因为AI写的代码里面用了tkinterdnd2库的问题。

这个问题在wayland上不会出现。尽管如此,这个问题仍然难以接受,所以使用AI工具,把这个界面用pyside重写,然后就得到了pyside版的gui。也在项目里面。

希望这么个项目可以对linux的桌面应用有一点微薄的帮助。以后如果有时间的话,可能再考虑尝试一下开发预览功能,以及直接打印而不是生成PDF的功能。

KDE Plasma面板经常崩溃,然而killall plasmashell不管用。网上没有找到解决办法。

经实测,发现killall -9 plasmashell就行了。

这就类似windows explorer重启。

还有其他意想不到的效果:

本来KDE wayland和x11的鼠标指针大小不是同一个标准,如果电脑屏幕有150%缩放,则wayland的24号鼠标指针等于x11的36号。在x11里面修改鼠标指针大小只会对新打开的应用生效,然而桌面不会,很难受。现在我们知道了,包括桌面和panel全部都是plasmashell这个进程,我们把他杀掉他就会自动重启,鼠标指针大小正常了。

闲着没事的阿玮提出了这么个问题:在一副扑克牌中任抽4张,算24点,有解的概率是多少。

(注:不允许使用乘方、开根、求导、阶乘等计算。)

于是开始研究:

  1. 计算任意一组数一个的二十四点表达式的算法解决:

上bing搜索,copilot太热情了,直接给出了算法。这个算法的原理就是枚举四个数之间所有可能的数学四则运算表达式,然后一个个算。测试代码发现可用。既然可以使用,那么具体原理不深究了。代码如下(cal24.py):

from itertools import permutations, product

def calculate(a, b, op):
    if op == '+':
        return a + b
    elif op == '-':
        return a - b
    elif op == '*':
        return a * b
    elif op == '/':
        if b != 0:
            return a / b
        else:
            return None

def solve_24(nums):
    operators = ['+', '-', '*', '/']
    for num_perm in permutations(nums):
        for ops in product(operators, repeat=3):
            # Try different parenthesis combinations
            expressions = [
                f"(({num_perm[0]} {ops[0]} {num_perm[1]}) {ops[1]} {num_perm[2]}) {ops[2]} {num_perm[3]}",
                f"({num_perm[0]} {ops[0]} ({num_perm[1]} {ops[1]} {num_perm[2]})) {ops[2]} {num_perm[3]}",
                f"{num_perm[0]} {ops[0]} (({num_perm[1]} {ops[1]} {num_perm[2]}) {ops[2]} {num_perm[3]})",
                f"{num_perm[0]} {ops[0]} ({num_perm[1]} {ops[1]} ({num_perm[2]} {ops[2]} {num_perm[3]}))",
                f"(({num_perm[0]} {ops[0]} {num_perm[1]}) {ops[1]} ({num_perm[2]} {ops[2]} {num_perm[3]}))"
            ]
            for expr in expressions:
                try:
                    if abs(eval(expr) - 24) < 1e-6:
                        return expr
                except ZeroDivisionError:
                    continue
    return 0

# 示例使用
nums = [8, 12, 6, 6]
solution = solve_24(nums)
print(solution)
  1. 一副52张的扑克里面抽牌,抽到一个特定组合的概率是多少:

分析这个问题可以发现,所有的组合可以分类如下:

AAAA型, AAAB型, AABB型, AABC型, ABCD型。

没有其他可能的分布。

计算这5种组合中,特定一种组合出现的概率。

总抽法是comb(52, 4)。(注:comb是qalculate写法,表示组合数; perm是qalculate写法, 表示permutation, 排列数)

AAAA型:1 / comb(52, 4)

AAAB型:考虑花色。A牌是在四个花色中选了3个,B牌是在四个花色里面选了一个。全部考虑顺序。则概率为4/52 * 3/51 * 2/50 * 4/49 * perm(4, 4)。

AABB型:同理。4/52 * 3/51 * 4/50 * 3/49 * perm(4, 4)

AABC型:同理。4/52 * 3/51 * 4/50 * 4/49 * perm(4, 4)

ABCD型:同理。4/52 * 4/51 * 4/50 * 4/49 * perm(4, 4)

具体的数据见代码。

把所有情况列出,所有概率加和,发现略小于1。正常现象,因为python浮点数存在误差。

代码如下(stat.py):

import cal24
pa = 0
for f in range(1, 14):
    for i in range(1, f+1):
        for j in range(1, i+1):
            for k in range(1, j+1):
                if f != i and i != j and j != k:
                    # case1: abcd
                    # p = 2816/4165
                    p = 24*32/812175
                elif f == i and i != j and j != k or f != i and i == j and j != k or f != i and i != j and j == k:
                    # case2: aabc
                    # p = 2112/20825
                    p = 12*8/270725
                elif f == i and i == j and j != k or f != i and i == j and j == k or f == i and i != j and j == k:
                    # case3: aaab
                    # p = 96/20825
                    p = 4*4/270725
                elif f == i and i != j and j == k:
                    # case4: aabb
                    # p = 216/20825
                    p = 6*6/270725
                elif f == i and i == j and j == k:
                    # case5: aaaa
                    # p = 1/20825
                    p = 1/270725
                expr = cal24.solve_24([f, i, j, k])
                print(f, i, j, k, expr)
                if expr:
                    pa += p


print(pa)

执行python stat.py > 24.txt可以得到:

1 1 1 1 0
2 1 1 1 0
2 2 1 1 0
2 2 2 1 0
2 2 2 2 0
3 1 1 1 0
3 2 1 1 0
3 2 2 1 0
3 2 2 2 (3 * (2 + 2)) * 2
3 3 1 1 0
3 3 2 1 (3 * (3 + 1)) * 2
3 3 2 2 ((3 + 3) * (2 + 2))
3 3 3 1 ((3 + 3) * (3 + 1))
3 3 3 2 (3 + (3 * 3)) * 2
3 3 3 3 ((3 * 3) * 3) - 3
4 1 1 1 0
4 2 1 1 0
4 2 2 1 4 * (2 * (2 + 1))
4 2 2 2 ((4 + 2) * (2 + 2))
4 3 1 1 4 * (3 * (1 + 1))
4 3 2 1 4 * ((3 + 2) + 1)
4 3 2 2 ((4 + 2) + 2) * 3
4 3 3 1 (4 * (3 + 3)) * 1
4 3 3 2 0
4 3 3 3 ((4 + 3) * 3) + 3
4 4 1 1 4 * ((4 + 1) + 1)
4 4 2 1 ((4 + 4) * (2 + 1))
4 4 2 2 (4 + (4 * 2)) * 2

……

13 13 11 4 0
13 13 11 5 0
13 13 11 6 0
13 13 11 7 0
13 13 11 8 0
13 13 11 9 ((13 + 13) - 11) + 9
13 13 11 10 0
13 13 11 11 0
13 13 12 1 ((13 / 13) + 1) * 12
13 13 12 2 ((13 - 13) + 12) * 2
13 13 12 3 12 * (3 - (13 / 13))
13 13 12 4 0
13 13 12 5 0
13 13 12 6 13 + (13 - (12 / 6))
13 13 12 7 0
13 13 12 8 0
13 13 12 9 0
13 13 12 10 ((13 + 13) - 12) + 10
13 13 12 11 13 + ((13 - 12) * 11)
13 13 12 12 ((13 - 13) + 12) + 12
13 13 13 1 0
13 13 13 2 (13 - (13 / 13)) * 2
13 13 13 3 0
13 13 13 4 0
13 13 13 5 0
13 13 13 6 0
13 13 13 7 0
13 13 13 8 0
13 13 13 9 0
13 13 13 10 (13 + (13 / 13)) + 10
13 13 13 11 ((13 + 13) - 13) + 11
13 13 13 12 ((13 + 13) / 13) * 12
13 13 13 13 0
0.8012448056145496

可以看到,有解的概率大概是80%多点。

后来发现,可以参考知乎这篇回答
他计算的结果和我相近。

房里被装了监控,有一两年了,一直想想办法干扰,现在做了一个完美的解决方案,供参考。

本来希望通过DNS来阻断,但是DNS有缓存之类的东西,而且猾伪路由器篡改我的DNS,而且我在Adguard Home里面也没有找到有关小米API的DNS解析记录,所以放弃。没想到最后采用的方案效果比之前还要好。

买一个树莓派全天候运行,装好firefox, geckodriver, python的selenium, 使用screen -S ban -s ban后台运行ban脚本,把脚本放在PATH下面。

原理是自动模拟人登录路由器后台手动禁止联网,过一段时间模拟人手动允许他联网。
我一开始绝对不会想到这么离谱的想法最后会采用而且效果这么好。

ban.py


#!/bin/python3

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC  # 和下面WebDriverWait一起用的
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.firefox.service import Service
from time import sleep
from random import randint
from threading import Thread

def ban(driver, sec):
    url = "http://192.168.6.6"
    
    
    driver.get(url)

    wait = WebDriverWait(driver, 20)
    # wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'inputArea paddingleft_14')))
    wait.until(EC.presence_of_element_located((By.ID, 'userpassword_ctrl')))

    pw_input = driver.find_element(By.ID, 'userpassword_ctrl')
    login_btn = driver.find_element(By.ID, 'loginbtn')
    pw_input.send_keys("freedom")
    sleep(0.6)
    login_btn.click()
    #sleep(6)
    sleep(0.6)

    #term_mng = driver.find_element(By.CLASS_NAME, 'nav_title')
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'small_icon want_devicecontrol')))
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #term_mng.click()
    sleep(0.6)

    driver.get("http://192.168.6.6/html/index.html#/devicecontrol")
    actionchains = ActionChains(driver)

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__blackon')
    wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址: 不给你看']/../../../../div[2]/div[1]/div[1]")))
    btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址:不给你看']/../../../../div[2]/div[1]/div[1]")
    btn.click()

    sleep(sec)

    div = driver.find_element(By.ID, 'device_selectlist_parenselect')
    actionchains.click(div).perform()

    wait.until(EC.presence_of_element_located((By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')))
    disal = driver.find_element(By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')
    actionchains.click(disal).perform()

    # actionchains.move_to_element(div).click().perform()

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')))

    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')
    # btn.click()

    wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址:不给你看']/../../../../div[2]/div[1]/div[1]")))
    btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址: 不给你看']/../../../../div[2]/div[1]/div[1]")
    btn.click()

    driver.get("about:blank")
    driver.refresh()



    #print(driver.page_source)



if __name__ == '__main__':
    while True:
        print("drv init", flush=True)
        opt = webdriver.FirefoxOptions()
        # 设置无界面
        opt.add_argument("--headless")


        service = Service(log_path='/dev/null')

        # 禁用 gpu
        # opt.add_argument('--disable-gpu')

        driver = webdriver.Firefox(options=opt, service=service)
        for i in range(64):
            print("ban", s:=randint(8, 16), flush=True)
            ban(driver, s)
            print("wait", s:=randint(16, 48), flush=True)
            sleep(s)
        driver.quit()

踩到的坑稍微做一下总结:

service对象不能再driver对象退出以后重复使用,也就是service = S……这句不能写在while True外面。否则报错。很奇怪的坑,网上没有解决方案,最后是我自己改对的。

对于要点击的对象一定要wait.until等他加载出来。

路由器里面设备的序号是会变的,正如我注释掉的代码,所以最好的方式使用MAC地址来定位。通过XPATH寻找与定位MAC文本同框的按钮,非常稳定。

driver如果每次都新实例化比较慢和占CPU, 如果每次只用同一个driver会内存泄漏,所以折中,每64次ban,关掉浏览器重新实例化一个driver.

总结

每隔几十秒断网十几秒,实测监控不会显示断线,APP上面只能看到卡顿转圈圈,卡很久能进去,看了十几秒画面又不动了。

这绝不是阿Q式的或者扎纸小人式的自我安慰,而是我想要搞你 想要恶心一下你就有能力做到恶心一下你,你想偷窥我的生活就给你看,岂有此理,没那么容易!而且这个东西的应用也在客观上确实降低了他们看的频率,毕竟我实测感觉还是非常卡的。

另外这一次也算是点亮了一点浏览器自动控制的科技树,当初云原神每天只送15分钟的时候我都是手动登录,如果有这东西那么也许不会那么麻烦,不过可惜现在云原神免费了,没用了

更新

通过故障分析,情况判断,我的程序得到了一个很好的鲁棒性提升。

有的时候程序会找不到那个小米摄像机,那是因为等待的时间太短了,此时所有的2.4G联网设备全部离线,所以导致浏览器默认进入分类是5G设备,等再久也等不到摄像机重新连上。

还有别的概率bug不可复现。随他去吧

以下是更新之后的代码

#!/bin/python3

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC  # 和下面WebDriverWait一起用的
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.firefox.service import Service
from selenium.common.exceptions import TimeoutException
from time import sleep, strftime, localtime
from random import randint, choice



def ban(driver, sec):
    url = "http://192.168.6.6"
    
    driver.get(url)

    # wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'inputArea paddingleft_14')))
    wait.until(EC.presence_of_element_located((By.ID, 'userpassword_ctrl')))
    pw_input = driver.find_element(By.ID, 'userpassword_ctrl')
    wait.until(EC.presence_of_element_located((By.ID, 'loginbtn')))
    login_btn = driver.find_element(By.ID, 'loginbtn')
    pw_input.send_keys("freedom")
    sleep(0.06)
    login_btn.click()
    #sleep(6)
    #sleep(1)
    sleep(0.06)
    wait.until(EC.presence_of_element_located((By.ID, "devicecontrol")))

    #term_mng = driver.find_element(By.CLASS_NAME, 'nav_title')
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'small_icon want_devicecontrol')))
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #term_mng.click()
    #sleep(0.6)

    driver.get("http://192.168.6.6/html/index.html#/devicecontrol")
    actionchains = ActionChains(driver)

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__blackon')

    # check if 2.4G section
    while True:
        wait.until(EC.presence_of_element_located((By.ID, "device_selectlist_parenselect")))
        div = driver.find_element(By.ID, 'device_selectlist_parenselect')
        if div.text == "2.4G 联网设备":
            # print(div.text)
            break
        else:
            print("LOCATED" + div.text)
            sleep(1)
            driver.refresh()
            sleep(0.2)



    try:
        wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")))
        btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")
        btn.click()
    except TimeoutException:
        time_str = strftime("%Y-%m-%d--%H-%M-%S", localtime())
        print(time_str, "START BAN FAILED")
        with open(time_str + "start_failed.log", 'a') as f:
            f.write(time_str + '\n' + driver.page_source + "\nSTART BAN FAILED")


    sleep(sec - 1.6)

    wait.until(EC.presence_of_element_located((By.ID, 'device_selectlist_parenselect')))
    div = driver.find_element(By.ID, 'device_selectlist_parenselect')
    actionchains.click(div).perform()

    wait.until(EC.presence_of_element_located((By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')))
    disal = driver.find_element(By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')
    actionchains.click(disal).perform()

    # actionchains.move_to_element(div).click().perform()

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')))

    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')
    # btn.click()

    try:
        wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")))
        btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")
        btn.click()
    except TimeoutException:
        time_str = strftime("%Y-%m-%d--%H-%M-%S", localtime())
        print(time_str, "END BAN FAILED")
        with open(time_str + "end_failed.log", 'a') as f:
            f.write(time_str + '\n' + driver.page_source + "\nEND BAN FAILED")

    driver.get("about:blank")
    sleep(0.1)
    driver.refresh()



    #print(driver.page_source)



if __name__ == '__main__':
    while True:
        print("drv init", flush=True)
        opt = webdriver.FirefoxOptions()
        # 设置无界面
        opt.add_argument("--headless")
        # opt.page_load_strategy = 'none'

        # firefox_profile = webdriver.FirefoxProfile()
        # firefox_profile.set_preference('permissions.default.image', 2)#某些firefox只需要这个
        # firefox_profile.set_preference('permissions.default.stylesheet', 2)


        service = Service(log_path='/dev/null')

        # 禁用 gpu
        # opt.add_argument('--disable-gpu')

        driver = webdriver.Firefox(options=opt, service=service)
        wait = WebDriverWait(driver, 10)
        waittimelist = list(range(8, 10)) * 4 + list(range(8, 17))
        for i in range(50):
            time_str = strftime("%Y-%m-%d--%H-%M-%S", localtime())
            print(time_str)
            print(i, "ban", s:=randint(9, 16), flush=True)
            ban(driver, s)
            print(i, "wait", s:=choice(waittimelist), flush=True)
            sleep(s)
        driver.quit()

使用flask和简单的html写了一个简单的信息课聊天室

from flask import Flask, request

app = Flask(__name__)

msglist = []

@app.route("/", methods=["POST", "GET"])
def root():
    global msglist
    if request.method == "GET":
        ip = request.remote_addr
        reqnum = ip[ip.rfind(".")+1:]
        user = request.values.get("user")
        message = request.values.get("message")
        print(reqnum, user, message)
        if (user and message) and (not msglist or msglist[0] != (reqnum, user, message)):
            msglist.insert(0, (reqnum, user, message))
    

    with open("chat.html", 'r') as f:
        text = ""
        name = ""
        for fornum, user, message in msglist:
            text += "<p>%s,%s: %s</p>" % (fornum, user, message)
            if not name and reqnum == fornum:
                name = user
        # 防止老师直接偷看
        if ip == "10.10.50.49":
            return ""
        return f.read() % (name, text)

app.run(host="0.0.0.0", port=8080, debug=False)
<!DOCTYPE html>
<html>
    <title>
        聊天室
    </title>
    <body>
        <h2>聊天室!</h2>
        <p>输入完成之后点击send发送</p>
        <p>如果不点击页面上的刷新按钮是不能看到新的信息的</p>
        <p>不要手动使用浏览器的刷新</p>
        <div>
            <button type="button" onclick="javascript:location.replace('/')">刷新</button>
            <form action="/" method="get">
                <div>
                    <label for="name">name:</label>
                    <input type="text" name="user" id="user" value="%s" required /> 
                </div>
                <div>
                    <label for="message">message:</label>
                    <input type="text" name="message" id="message" required />
                </div>
                <div>
                    <input type="submit" value="send" />
                </div>
            </form>
        </div>
        <p></p><p></p>
        <div>
            %s
        </div>

    </body>
</html>

自制云原神

关于这个的想法我在很久之前就产生了,然而当时我下载的sunshine是在第三方渠道下载的,因为神秘力量导致我不能访问github。于是我多点触控的配置失败了。今天我重新下载安装sunshine,成功多点触控玩原神,在此记录。

需要软件、文件:

  1. sunshine windows >= 0.20

  2. usbmmidd_v2(虚拟显示器驱动)(如果使用HDMI转VGA的线空接来欺骗显卡,分辨率没有奇奇怪怪的选项)

  3. moonlight Android 阿西西修改版

安装所有软件。

配置sunshine输入显示屏路径。(\,\DISPLAY5)音频的配置非必须。

平板不做副屏而是玩游戏时,电脑音频改为Steam Streaming Speakers。

虚拟显示器驱动要

deviceinstaller64 install usbmmidd.inf usbmmidd
deviceinstaller64 enableidd 1

参见WIN版虚拟显示器usbmmidd_v2 - 小脚丫走天下 - 博客园

moonlight连接windows成功。

云原神启动参数 yuanshen.exe use_mobile_platform -is_cloud 1 -platform_type CLOUD_THIRD_PARTY_MOBILE -popupwindow -borderless

记得设置副屏为主显示器否则原神会启动在电脑屏幕,拖过去之后会导致纵横比失调。

愉快玩耍吧。