前言

豆瓣刚出小组件的时候,我用过一段时间豆瓣电影日历,当时的体验还不错。后来在慕尼黑Hugendubel书店,有一个区域是专门卖各种日历的,有风景画,哈利·波特主题,和二次元等等。我是为了看推理小说才去的那家书店,所以萌生了做一款推理主题日历的想法。

 

我想先从最简单的功能开始,因为是在手机上观看,日历卡片设计成了竖屏,日历的条目包括推理主题的书籍和电影,最上面是一张作品的海报,下方是作品名称,类型等信息。

我先用Canvas设计了一个海报,然后每天手动更新图片和信息。结果第二天我就出了差错,只更新了日期,但没更新星期几。虽然Canvas的设计我觉得还挺好看的,但手动更新时间毕竟不是长久之计,我开始考虑用Python生成海报。

手动设计的海报,结果忘记更新星期几了

开发过程

因为最初的想法只是根据手动输入的网址生成一张海报,然后用脚本自动标记上时间,防止时间出现错误,所以我先用Pillow这个Python包写了一下海报生成的代码。我主要是设计了一下海报和文字的位置和颜色,其中颜色是根据条目的主题而变化的,每天都不一样。豆瓣评分的图标用的是GitHub上常见的设计。

我随便选了一个可以商用的字体,叫作「站酷文艺体」。可有一次我发现《和騎士度過的那一夜》这本推理短篇集的名字无法正确显示,才意识到字体需要包含简体和繁体,所以更换成了「霞鹜文楷」。

我试用了一下,因为不满足于手动输入图片网址的繁琐,又找了一下网上的豆瓣爬虫,自动抓取豆瓣条目的图片。不过由于豆瓣条目的网址还是需要手动输入,整个流程很费时间。我想到了使用一个json文件来存储这些网址,脚本只需要在json文件里读取最新输入的条目。等以后积累的条目多了,就可以随机选取一条来当作背景图片。

每天输入一条的话,不知道要等到猴年马月才能积累足够的条目。偶然间发现了一个GitHub Action,可以从豆瓣上爬取用户标记过的作品。一次性把全部条目下载下来,每个条目包含了名称,类型,上映/出版日期等信息,十分方便。

	{
		"comment": "《巧克力公寓谋杀案》",
		"rating": {
			"count": 1,
			"max": 5,
			"star_count": 4,
			"value": 4
		},
		"sharing_text": "我的评分:★★★★ 《巧克力公寓谋杀案》 https://book.douban.com/subject/36480672/ 来自@豆瓣App",
		"sharing_url": "https://www.douban.com/doubanapp/dispatch?uri=/subject/36480672/interest/4036595172",
		"tags": [],
		"charts": [],
		"platforms": [],
		"vote_count": 1,
		"create_time": "2024-03-15 20:05:51",
		"status": "done",
		"id": 4036595172,
		"is_private": false,
		"subject": {
			"rating": {
				"count": 2253,
				"max": 10,
				"star_count": 3.5,
				"value": 6.8
			},
			"controversy_reason": "",
			"pubdate": [
				"2024-1"
			],
			"pic": {
				"large": "https://img2.doubanio.com/view/subject/l/public/s34735111.jpg",
				"normal": "https://qnmob3.doubanio.com/view/subject/m/public/s34735111.jpg?imageView2/2/q/80/w/200/h/300/format/jpg/sharpen/1"
			},
			"honor_infos": [],
			"other_versions_count": 0,
			"is_show": false,
			"vendor_icons": [
				"https://img1.doubanio.com/f/frodo/161387ce451872cbf51fcb288cd5ffd1dcd89705/pics/vendors/logo_dedao@2x.png",
				"https://img1.doubanio.com/f/frodo/f6f620132e6d8a02d171f03114bbe2339aa8af97/pics/vendors/logo_doubanread@2x.png"
			],
			"card_subtitle": "紫金陈 / 2024 / 湖南文艺出版社",
			"book_subtitle": "",
			"id": "36480672",
			"author": [
				"紫金陈"
			],
			"is_released": true,
			"vendor_original_price": "",
			"color_scheme": {
				"is_dark": true,
				"primary_color_light": "a55634",
				"_base_color": [
					0.05000000000000001,
					0.6837606837606838,
					0.9176470588235294
				],
				"secondary_color": "f9f6f4",
				"_avg_color": [
					0.045662100456621,
					0.6666666666666666,
					0.8588235294117647
				],
				"primary_color_dark": "7f4228"
			},
			"type": "book",
			"cover_url": "https://dou.img.lithub.cc/book/36480672.jpg",
			"min_sale_price": null,
			"press": [
				"湖南文艺出版社"
			],
			"pages": [
				"424"
			],
			"sharing_url": "https://book.douban.com/subject/36480672/",
			"url": "https://book.douban.com/subject/36480672/",
			"title": "长夜难明 : 双星",
			"uri": "douban://douban.com/book/36480672",
			"subtype": "book",
			"intro": "闹市街头,女子坠楼身亡,腹部有刀伤。\n两天后,一段赵泽宇手持带血匕首逃离案发现场的视频,被人在网上公开。赵泽宇是江北市原市长赵忠悯的独生子,江北知名企业家,案件顿时引发全社会关注。赵泽宇被警方刑拘,可他坚称自己没有杀人,是被人陷害的,事发后他因害怕仓促逃离现场。死者实为跳楼自杀。\n警方当然不信这番狡辩,可通过一系列的技术手段还原案发经过,结果证实死者竟真的是自杀。\n现在警方面临的问题更加棘手了。从证据角度讲,死者是自杀的,赵泽宇无罪,警方应该放人;可视频在前,全网皆知,加上赵泽宇的家庭背景,如果警方对外通报死者是自杀的,谁会相信?人民群众会质疑警方徇私枉法。\n事到如今,各方都希望赵泽宇“有罪”,最好还是重罪,这样才能平息这场风波。",
			"buy_more_uri": "",
			"null_rating_reason": "",
			"article_intros": [
				{
					"useful_count": 1,
					"uri": "douban://douban.com/review/15811580",
					"comment_count": 1,
					"intro": "没有多么长夜难明,不如改名《巧克力公寓谋杀案》",
					"total": 1,
					"type": "review"
				}
			],
			"vendor_sale_price": "",
			"has_ebook": true
		}
	},

因为是「推理日历」,我在脚本中加了筛选条件,随机选取数据库中的条目,直到选到悬疑或者推理类的作品为止。然而并不是所有的条目都有类型标注,所以我筛选的方式是判断简介里面是否出现了「悬疑」,「推理」,「侦探」等关键词。

我能想到的发布方式有好几种。最简单省事儿的办法就是发布在社媒上,比如豆瓣相册,小红书,公众号之类的地方。头两天的海报我也确实是这么做的。之前用过Flask,可以在pythonanywhere上发布,优点是站点访问速度较快,但每三个月就要点一下更新,不然站点会过期。

更正式的发布方式,是做成类似「ONE·一个」那样的APP,但我没搞过移动端的开发,对网上看到其他人用的flutter之类的不熟。而且不付费注册的话,只能把APP发布在Test Flight里,不能上架应用商店。其实除了APP,ios还可以使用小组件发布一些轻量级的应用,我看到了两款日语单词学习组件,调用了词典网站或者APP的API,可以随机展示日语单词和翻译,用的就是Scriptable。

 

我决定使用的办法,就是用GitHub Pages发布日历并且设置成每天自动更新,然后移动端使用小组件来展示网页。日历的网址是https://code-cp.github.io/mystery-calendar/,小组件叫作Web Widget。这款小组件可以根据输入的网址显示网页快照。

 

不过我适用了一段时间后,发现Web Widget有时候无法更新网页。我发现了另一款相似的小组件Glimpse 2,网页的更新更加及时,试用了几天感觉很不错。

 

上文提到了GitHub Action,我用它来运行代码库中生成日历的脚本。在代码库中新建一个.github/workflows文件夹,然后创建yml格式的配置文件。GitHub会根据配置文件里设定的运行时间(cron: '0 1 * * *')来执行里面的步骤,比如可以每隔一个小时,每天某个时间运行等等。具体如何设置时间,可以参考这个网站

不过GitHub Action并不能完全按照yml文件里的时间来执行,这取决于GitHub的runner的承载能力。我看网上的讨论说,最好把分钟数(也就是第一位的0)改成非0的值,因为大多数的Action都是在0分的时候执行。

name: run-main

on:
  workflow_dispatch:
  schedule:
    - cron: '0 1 * * *' 

jobs:
  build:
    runs-on: ubuntu-latest
    steps:

      - name: checkout repo content
        uses: actions/checkout@v2 # checkout the repository content to github runner

      - name: setup python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10' # install the python version needed
          
      - name: install python packages
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          
      - name: execute py script # run main.py
        run: |
            cd src/ 
            python main.py
          
      - name: commit files
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add -A
          git diff-index --quiet HEAD || (git commit -a -m "updated logs" --allow-empty)
          
      - name: push changes
        uses: ad-m/github-push-action@v0.6.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: main 

但在调试的时候没时间等待它自动运行,往往需要手动执行workflow,可以在配置文件里加上workflow_dispatch:,就可以在Github网站上点击Run workflow来运行了。

这个脚本先执行checkout repo content,因为我要运行的是Python脚本,还要加上setup python,这些都是GitHub提供的。接下来会安装代码库的requirements.txt里的包,然后运行脚本。main.py会生成一个新的日历,保存在images文件夹内,所以需要commit files和push changes,把图片保存在代码库中,GitHub Pages就会在deploy的时候使用新的图片。

GitHub Actions自动运行并且commit, push到了代码库

结语

制作日历之前,我以为这只是一个很简单的工程,没想到有这么多细节需要处理,比如怎么处理不同格式的时间和字体。除了豆瓣评分,我还想要加上IMDb和烂番茄的评分,但暂时没时间去研究那些网站的API了。

手机上显示日历的效果展示

目前这个日历仅仅是一个demo,以后有机会,还是想把它做成「ONE·一个」那样的风格。其实我不仅仅想做一个日历,我还没找到任何一款专门做推理内容的APP,所以更想做一款包含推理资讯,论坛等内容丰富的推理APP。