Monday, 8 November 2021

知乎 - "我们检测到您此次登录异常,请您进行安全验证" 的 bug

近两天知乎的每新打开一页都会出现 "我们检测到您此次登录异常,请您进行安全验证" 对话框。(截图注: www2 只是测试, www 一样):


输入手机验证,对话框关闭,但打开新一页或点击 "查看全部 xxx 答案" 都会重复出现该验证对话框。不验证直接 X 关闭对话框也行,不过重复出现很烦。

后来察觉专栏 zhuanlan.zhihu.com 没对话框,就想到换其它 subdomain 可能可以。

google 递减法 找到 https://www-quic.zhihu.com/ (开头的 400 页面可以 Ctrl+F5 刷新) 是可以 bypass 对话框且浏览主页的。

即 `site:zhihu.com -"www.zhihu.com" -"zhuanlan.zhihu.com" -"www2.zhihu.com" -"s.zhihu.com" -"daily.zhihu.com"`:


反而 infoscan 扫描到很多却没 www-quic。

写了一个简单的火狐 add-on 重定向 www 去 www-quic:

`web-ext sign` 把 add-on 给自己私人用有问题 (不懂是不是没 web-ext build 先):



(吐槽一下 web-ext 每次 timeout 错误后,以为失败却出现在 Firefox Developers Hub 的 " My Submissions" list ,有点辣眼睛, 又无法删除的) :


所以就干脆 submit 真的 add-on:

然后继续研究。我之前为了研究一个东西而有了新手机号码,就测试一下来比较 Cookie 有什么特别区别。发现无法注册,这么多 bug 是什么鬼。也不是只有我一个有这个问题:




不过我还有 gmail (后来才察觉错误的账号是 gmail + 手机号码, 另一个是手机号码而已) 是可以登入的,用 gmail vs 手机号码 Cookie 也可以。

可是我想到一件事, gmail 登入有问题也是要手机验证什么的(知乎一路来很注重手机号码),岂不是两个账号都同一个手机号码,会不会因此 sot 了?

看设置果然如此,都是绑定同一个手机号码。我把有问题的账号绑定新手机号码,错误对话框真的消失了,重登入也是没问题。



这个 bug 估计是因为 gmail 账号 A 错误信息请求手机验证时, 我输入后却 act as 同一个手机号码的另一个账号 B 验证, 因此永远都会有验证对话框 囧。

如同你用同一个手机号码, 登入后的账号只能选一个, 不可能同时登入两个账号。

至于当初我是怎么弄到绑定同一个手机号码,忘了。

Thursday, 25 February 2021

中文和英文双字幕观看 YouTube 视频

我英文差, 但是又不能只看中文(讲话的人是英文,失去原味),所以打算双字幕观看。英文字幕在 mpv 播放器下方,看到不懂的字可以望上面的中文字幕。

虽然此视频  IlU-zDU6aQ0 网页版有英文字幕以及自动翻译的中文字幕,可是旧版本的 youtube-dl 和随便两个网站 (https://downsub.comhttps://savesubs.com) 都只能拿到英文字幕。



虽然最新版本的  youtube-dl 已经 fix 了,无论如何,要手动完成此任务,也行的:

1. 下载英文字幕 (.vtt): youtube --skip-download --write-sub --sub-lang en https://www.youtube.com/watch?v=IlU-zDU6aQ0 。

2. 在 Youtube 播放器, 变改语言边 inspect network, 得到 `api/timedtext?` 链接。

用 curl 跑链接获得 .json 格式的字幕。

3.  转换 .json 去 .srt 不需要重造轮子,浏览 https://zhuanlan.zhihu.com/p/337934938 下载某人写的 node.js 代码, `parse_subtitle.js`, 加上这三条 line:

    let sourcePath = process.argv[2]
    let resultPath = process.argv[3]
    let isTime = true

4. 然后把这代码: `s.segs[0].utf8` 改成 `s.segs[0].utf8.match(/.{1,21}/g).join('\n')` ,

以便能在 mpv 播放器, 每 21 个中文字 wrap,避免一行长过播放器宽度,因为 mpvlibass 不支持没空白的中文字 wrap。

5. 运行 `node parse_subtitle.js IlU-zDU6aQ0.sub.zh-Hans.json IlU-zDU6aQ0.sub.zh-Hans.srt` ,

把 .json 转换去 .srt

6. 运行 `mpv --sub-file="IlU-zDU6aQ0.sub.zh-Hans.srt" --sub-file="Marty Lobdell - Study Less Study Smart-20110722-IlU-zDU6aQ0.en.vtt" --sid=2 --secondary-sid=1 'Marty Lobdell - Study Less Study Smart-20110722-IlU-zDU6aQ0.mp4` ,

播放 .srt 中文字幕在上方, 以及 .vtt 英文字幕在下方。



youtube-dl fix 了不表示这 web API 就没用,其实两 API 的翻译是有出入的。

web API 是 53 个中文字翻译(我切为三行,但翻译是一起的),而 youtube-dl 是 27 个中文字翻译。

理论上 53 一行肯定比 27 一行能翻译比较准确, 不过两者都没考虑上下文的句号来断句翻译, 所以翻译可能怪怪的。

当然,句号翻译的前提是原文是有句号的, 否则没句号却强行变成同一行翻译也会有问题。

youtube-dl 原字幕(上图), ,web API 自动中文翻译(左下), youtube-dl 自动中文翻译(右下):





我也曾尝试 Android 11 的 Live Caption (即时字幕) 功能以达到类似效果。
可是 Live caption当前只有英文:



,且 player 的该视频只有英文选项。



只要其一有中文即可(不过如果只能选 player 自动翻译的中文,意味着两个都是机器翻译,准确性也大打折扣),偏偏两个都是英文,任务失败。

Android Live caption + 视频字幕效果图, Live caption (移去上方), 英文原字幕 (下方):


不过桌面版的 Chrome 也有  Live Caption 啊!

只不过必须安装 Chrome Canary(否则就算启动设置也是没用的)

,可是 Linux 却没有 Chrome Canary(除非... 用 wine/虚拟机... 囧我不奉陪了)。



算了,试下打开封尘不懂几年的 Windows。

安装好 Chrome Canary 后,去 `chrome://flags/#enable-accessibility-live-captions`(没有出现 "Live Caption" 就搜 "Live" 关键字) 设置 "Enabled" 它。
(Android 的 Chrome Canary 没有该设置... Android 再次任务失败)




Enabled 后下面提示你 "Relaunch" Chrome。

重启 Chrome 后,然后去 settings -> accessibility 启动 Live Caption

,抑或去视频页面的右上方 "" 图标会能给你实时启动/关闭 Live Caption。



第一次启动 Live Caption 后会花一些时间下载语言分析包, 不过当前只有英文所以不会下载太久。

然后就完成啦。视频播放器字幕没办法永久移动, 所以只能移动 Live caption 字幕去上方。

由于 Live caption 只有英文,那么中文的就必须是播放器字幕,就形成了英文必须在上方的情况
(必须全屏,否则头被完全盖着囧
那个占位的箭头有点鸡肋,展开又太多行眼花缭乱,不展开又嫌少行, 也不能自定义多少行):



无论如何,Live Caption 用 speech 翻译的准确性肯定比原文翻译低。

如上图, "Ontogeny" 变成 "On to me"。

当然,播放器的自动翻译也无法保证百分百,但起码能比 speech 翻译准确。

除非本来就没字幕,就可能打平, 但实时翻译真的能比得上预先翻译?

Live caption 还有说了该段话才显示的局限性
(无法一次性理解整体, 当然你也可以说有利弊,可以了解当前讲的话是哪一个词)

, 些许延迟(我只观察一下)。

最后,避免日后知乎删除 .json 转 .srt 的代码文章,所以备份在这 (//orig 注释表示原版, //hole 表示我改的):

let sourcePath = process.argv[2] //'IlU-zDU6aQ0.sub.zh-Hans.json'
let resultPath = process.argv[3] //'IlU-zDU6aQ0.sub.zh-Hans.srt'
let isTime = true

//orig: https://zhuanlan.zhihu.com/p/337934938

var fs = require('fs')

//2. 处理 json

 fs.readFile(sourcePath, 'utf8', function (err, data) {
        if (err) console.log(err);
        var test1 = JSON.parse(data);//读取的值
        // console.log(test1.events)
        subtitle = test1.events
    
        if (fs.existsSync(resultPath)) {
            fs.writeFileSync(resultPath, '')
        }
        for (let i = 0; i < subtitle.length; i++) {
            const s = subtitle[i];
            // console.log(s.segs[0].utf8) //字幕内容
            // i :字幕的位置
            // console.log(s.tStartMs) 起始时间
            // console.log(s.tStartMs + s.dDurationMs) 结束时间
    
            // 字幕格式
            // 1
            // 00:00:09,779 --> 00:00:15,899
            // 内容
            // 空行
    
            if(isTime){
                fs.appendFileSync(resultPath, i + '\n' + convertTime(s.tStartMs) + ' --> ' + convertTime(s.tStartMs + s.dDurationMs)
                //+ '\n' + s.segs[0].utf8 + '\n\n',err => {     //orig
                + '\n' + s.segs[0].utf8.match(/.{1,21}/g).join('\n') + '\n\n',err => {  //hole
                    if(err){
                        console.log(err)
                    }
                })
            }else{
                fs.appendFileSync(resultPath, s.segs[0].utf8 + '\n\n',err => {  //orig
                    if(err){
                        console.log(err)
                    }
                })
            }
               
        }
    
    });

//3. 处理时间的方法

  function convertTime(time) {
    var s, m, h
    h = Math.floor(time / 1000 / 60 / 60)
    console.log('h:' + h)
    m = Math.floor(((time / 1000 / 60 / 60) % 1) * 60)
    // console.log('m:' + m)


    s = ((((time / 1000 / 60 / 60) % 1) * 60) % 1) * 60// 通过与 1 取余获取小数部分
    s = s.toFixed(3)
    s = s.toString().replace('.', ',').substring(0, 6)
    console.log('s:' + s)
    if (h < 10) {
        h = '0' + h
    }
    if (m < 10) {
        m = '0' + m
    }
    var timeString = h + ':' + m + ':' + s
    // console.log(timeString)
    return timeString
}





Saturday, 6 February 2021

Trace python program from log perspective

回答了 -vvv debug pip:


Just a reminder to whom google this error and come here.

Let's say I get this error:

$ python3 example.py
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    import aalib
ModuleNotFoundError: No module named 'aalib'

Since it mentions aalib, I was thought to try aalib:

$ python3.8 -m pip install aalib
ERROR: Could not find a version that satisfies the requirement aalib (from versions: none)
ERROR: No matching distribution found for aalib

But it actually wrong package name, ensure pip search(service disabled at the time of writing), or google, or search on pypi site to get the accurate package name:

enter image description here

Then install successfully:

$ python3.8 -m pip install python-aalib
Collecting python-aalib
  Downloading python-aalib-0.3.2.tar.gz (14 kB)
...

As pip --help stated:

$ python3.8 -m pip --help
...
  -v, --verbose               Give more output. Option is additive, and can be used up to 3 times.

To have a systematic way to figure out the root causes instead of rely on luck, you can append -vvv option of pip command to see details, e.g.:

$ python3.8 -u -m pip install aalib -vvv
User install by explicit request
Created temporary directory: /tmp/pip-ephem-wheel-cache-b3ghm9eb
Created temporary directory: /tmp/pip-req-tracker-ygwnj94r
Initialized build tracking at /tmp/pip-req-tracker-ygwnj94r
Created build tracker: /tmp/pip-req-tracker-ygwnj94r
Entered build tracker: /tmp/pip-req-tracker-ygwnj94r
Created temporary directory: /tmp/pip-install-jfurrdbb
1 location(s) to search for versions of aalib:
* https://pypi.org/simple/aalib/
Fetching project page and analyzing links: https://pypi.org/simple/aalib/
Getting page https://pypi.org/simple/aalib/
Found index url https://pypi.org/simple
Getting credentials from keyring for https://pypi.org/simple
Getting credentials from keyring for pypi.org
Looking up "https://pypi.org/simple/aalib/" in the cache
Request header has "max_age" as 0, cache bypassed
Starting new HTTPS connection (1): pypi.org:443
https://pypi.org:443 "GET /simple/aalib/ HTTP/1.1" 404 13
[hole] Status code 404 not in (200, 203, 300, 301)
Could not fetch URL https://pypi.org/simple/aalib/: 404 Client Error: Not Found for url: https://pypi.org/simple/aalib/ - skipping
Given no hashes to check 0 links for project 'aalib': discarding no candidates
ERROR: Could not find a version that satisfies the requirement aalib (from versions: none)
Cleaning up...
Removed build tracker: '/tmp/pip-req-tracker-ygwnj94r'
ERROR: No matching distribution found for aalib
Exception information:
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/pip/_internal/cli/base_command.py", line 186, in _main
    status = self.run(options, args)
  File "/usr/lib/python3/dist-packages/pip/_internal/commands/install.py", line 357, in run
    resolver.resolve(requirement_set)
  File "/usr/lib/python3/dist-packages/pip/_internal/legacy_resolve.py", line 177, in resolve
    discovered_reqs.extend(self._resolve_one(requirement_set, req))
  File "/usr/lib/python3/dist-packages/pip/_internal/legacy_resolve.py", line 333, in _resolve_one
    abstract_dist = self._get_abstract_dist_for(req_to_install)
  File "/usr/lib/python3/dist-packages/pip/_internal/legacy_resolve.py", line 281, in _get_abstract_dist_for
    req.populate_link(self.finder, upgrade_allowed, require_hashes)
  File "/usr/lib/python3/dist-packages/pip/_internal/req/req_install.py", line 249, in populate_link
    self.link = finder.find_requirement(self, upgrade)
  File "/usr/lib/python3/dist-packages/pip/_internal/index/package_finder.py", line 926, in find_requirement
    raise DistributionNotFound(
pip._internal.exceptions.DistributionNotFound: No matching distribution found for aalib

From above log, there is pretty obvious the URL https://pypi.org/simple/aalib/ 404 not found. Then you can guess the possible reasons which cause that 404, i.e. wrong package name. Another thing is I can modify relevant python files of pip modules to further debug with above log. To edit .whl file, you can use wheel command to unpack and pack.



由于答案篇幅有点长且可以多种 debug 方式,我就没说更进一阶的方式。

由于 pip 使用 logging 来 print 的,更改 logging module 的输出函数可以更直接知道这个 log 是谁 print 的, 从而轻松修改相应的 python 脚本。

  


即:

    print(''.join(['\n\x1b[7;36m[holeL] name: ', repr(name), ' lvl: ', repr(level), ' fn: \x1b[0m\x1b[K\x1b[17;36m', repr(fn), '\x1b[0m\x1b[K\x1b[7;36m lno: ', repr(lno), ' msg: ', repr(msg), ' args: ', repr(args), ' exc_info: ', repr(exc_info), ' func: ', repr(func), ' sinfo: ', repr(sinfo), '\x1b[0m\x1b[K' ]))

比起 trace 模块方便输出大量的 log 要去想排除哪一个又怕排除错,轻松得多:






之后想到修改 trace 模块以包含 print 和 write,  之后想到有 stack 才能清楚 log 与程序的上下关系。

不过为了想到避免重复的 stacks, 尝试了各种复杂的代码, 之后想到 Prev 重复的 Prev 重复很难 refer。

最后想到用 [NEW] 和 [EQU] 不同 tags 标记新部分和重复部分,一目了然且不会看起来重复冗长。

 `NEW` 和 `EQU` 同样三个字的宽度才容易区分上下同一个文件。




... 省略



字体颜色也并非随着个人喜好而随便选的。

绿表示新/diff 的 +(显示时间都是新的,所以也是绿),和平深蓝表示打平/相等/重复。

白底黑字代码比较一目了然阅读(原先的水绿背景虽然美但太难阅读了),且与一般黑底白字的 log 相反,容易区分。

至于文件名字和行数都是同一个水绿色(不会太明显而扰乱代码的阅读,也不会太深很难看见)。

行数连着主打背景白色的源代码,
所以水绿用在背景色

不过不同终端的颜色可能有点不同。gnome-terminal 的标记和时间会闪, konsole 不会, 之前在 konsole 做的时候没发现不同。

由于冗长的代码需要长时间的阅读,我倾向于不闪, 不过就给用户两种选择吧。




源代码: https://github.com/limkokhole/pylogtrace



Friday, 22 January 2021

How to play Facebook Quiz



第一种方法在重复 submit 能发出多个 messenger notification 给该朋友我玩了你的 game,而第二种方法没 notification。不过两种方法都有在 game 内置的 notification。

第一种方法其实很简单,我玩第二次的时候就成功了。

不过那个是朋友的 /posts/ 链接,所以还要研究直接进特定 game 的直接链接, 否则朋友不分享就弄不到了(能弄到,去 friend list 然后一个个找到天黑,但手动不是程序员干的事)。

后来在玩 game 后的 messenger 信息找到 payload route,再稍微修改就能进到该特定朋友的特定 game 页面。

如果多个账号玩了再 hide score 呢?朋友还是能从 notification 找到不存在的朋友玩了这个 game, 会露马脚。且仍然不够直接链接+单个账号轻松。

其实这是两个月前的研究,能了解一下 Facebook 的 game 代码构造。两个月后发现 Firefox 很小几率能重复链接打开,大多 load 到最后仍然是黑屏。不过之后发现 chrome 能。