[Obcsapi v2] 微信到 Obsidian 2.0

注意
本文最后更新于 2024-04-21,文中内容可能已过时。

微信到 Obsidian 2.0,以前出过一个1.0版本。最近发现了一个 Obsidian 的 Memos 插件,跟 flomo 界面非常像,或者说一样。不过数据来源用的是 Obsidian 。我使用了多端同步插件 remotely save插件,选择腾讯云COS同步。我想,如果能够在微信测试号中,信息发送过去,然后存储到COS中。在 Obsidian 中一刷新就能看到了。

1.0 版本 微信使用 Remotely Save S3 兼容 发送到 Obsidian 1.0版本。不太好用

!!! 注意 新项目已发布 Obsidian 从本地到云端 obcsapi v3.0
下面文章属于 2.0 版本,新项目是 3.0 版本。请读者根据自身实际情况酌情选择!!!

因此我写下了2.0版本。实现了以下功能:

  • 支持图片和文字
  • 图片下载到存储本地,而非链接(微信发送的图片,会给咱们的服务器返回图片URL)
  • 对用户的判断,仅限特定用户存储笔记。(根据 OpenID 判断)
  • 检索文字中含有 “todo” ,则生成勾选框。如 - [ ] 13:11 somethingtodo
  • 正常生成 - 13:11 something
  • 内容能在 Memos 中正常显示
  • 支持收藏链接(部分支持),位置,语音(转文字存储)。(2.1 新增)
  • 返回可点击的链接,可以在微信内置浏览器中使用 Memos (2.1 新增,需要kkbt/obweb支持,参考页面,支持查看三天,修改一天)

BUG:

  • 不推荐批量传图片,推荐显示已保存后依次上传。
  • 不推荐一秒内上传多个文件,图片命名精确到1S。1S内多图片会覆盖。
  • 不要使用微信自带的表情符号,请使用输入法表情。
  • 如果微信输入框换行或分段,只会在这一条消息最开始有 - 13:11 。也就是说,第二行、第二段不会在 Memos 中显示。

说明: 不推荐批量传图片,程序对图片处理非常粗糙。。例如1M带宽服务器,连续传五个图片时。造成费时间会超过五秒,触发微信重传机制。这个机制带来的问题有很多。比如会多上传几份重复的文件,并且在微信测试号显示服务故障。(其实解决方案有很多,如使用任务队列或者是异步的方式。先返回success,另外开线程上传文件。留着以后有机会解决吧)。

开源地址: https://gitee.com/kkbt/obsidian-csapi
微信发送到 Obsidian 是该项目的最初的功能,后进一步拓展出更多功能。

依赖

  • werobot
  • cos-python-sdk-v5

程序思路

程序思路运行一个测试号服务器,使用werobot。根据message的公共属性source,即微信用户的唯一openid,判断用户。是自己的ID,如果是文字消息,判断消息字符串是否包含todo,包含则在消息字符串增加勾选框markdown语法,否则使用无序列表。然后根据当日时间,判断当日日志是否存在。若存在,下载本日日志并添加处理好的字符串,然后上传。不存在则直接上传处理好的字符串。
如果是图片消息,从消息中获取图片url并下载。下载完成之后上传至COS中,图片命名为当前的时间精确到秒。图片名包装成md图片链接。处理好的字符串按照文字消息的处理方法保存。

注意:以下代码仅为参考,最新代码请带 https://gitee.com/kkbt/obsidian-csapi 获取。

主文件 wechat.py 和 get_add.py 需要放到同一级目录下。运行 python wechat.py

python

# -*- coding=utf-8

# README
# 需要安装的依赖
# werobot cos-python-sdk-v5
# 可用 Alist 或其他可挂载 S3 的开源网盘 实现web编辑.md

from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import werobot
import time
import get_add as cosga

# 相关信息

token = "token"  # 自定义
APP_ID = "wxxxxxxxxxxxxxxxxx"  # 微信公众号测试号APP_ID
APP_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # 微信公众号测试号APP_SECRET


# werobot 配置

robot = werobot.WeRoBot(token=token)
robot.config["APP_ID"] = APP_ID
robot.config["APP_SECRET"] = APP_SECRET

rob_client = robot.client
rob_client.delete_menu()
# 
# rob_client.create_menu({
#     "button":[{
#          "type": "click",
#          "name": "今日",
#          "key": "info"
#     }]
# })

# message.source 为 OpenID 。获取->测试号二维码->用户列表(最多100个)->微信号

@robot.key_click("info")
def menu_click(message):
    if (message.source != "o6_bmjrPTlm6_2sgVt7hMZOPfL2M"):
        return '你不是恐咖兵糖'
    return cosga.get_daily_today()

# 关注回复


@robot.subscribe
def subscribe(message):
    return "这是恐咖兵糖的测试公众号"


# 文字消息回复 put_object append_object
@robot.text
def text_reply(message):
    if (message.source != "o6_bmjrPTlm6_2sgVt7hMZOPfL2M"):
        return '你不是恐咖兵糖'
    str = cosga.add_memos_in_daily(message.content) # ETag
    return "📩 已保存"


# 图片消息
@robot.image
def image_reply(message):
    if (message.source != "o6_bmjrPTlm6_2sgVt7hMZOPfL2M"):
        return '你不是恐咖兵糖'
    now_key = cosga.save_asset(message.img)
    str = cosga.add_memos_in_daily("![]("+now_key+")") # ETag
    return  "📩 已保存"


@robot.error_page
def make_error_page(url):
    return "404"


@robot.handler
def error_message(message):
    return "不支持的消息类型"


robot.config["HOST"] = "0.0.0.0"
robot.config["PORT"] = "8008"
robot.run()

get_add.py

python

# -*- coding=utf-8

# 需要安装的依赖
# werobot cos-python-sdk-v5

from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import time
import requests

# 相关信息
secret_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # 腾讯云 secret_i
secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxx"  # 腾讯云 secret_key
region = "ap-nanjing"  # 腾讯云 COS
cos_token = None  # 腾讯云 COS token
scheme = "https"  # 腾讯云 COS 访问模式
bucket = "test-0123456789"  # 腾讯云 Bucket
config = CosConfig(
    Region=region,
    SecretId=secret_id,
    SecretKey=secret_key,
    Token=cos_token,
    Scheme=scheme,
)

client = CosS3Client(config)
   

# name = time.strftime("%Y%m%d%H%M%S", time.localtime())
def add_memos_in_daily(text):
    # 生成ObjectKey => 日志/2022-08-08.md
    daily_note_dir_path = "日志/"
    today_daily_file_key = daily_note_dir_path + time.strftime("%Y-%m-%d", time.localtime()) + ".md"
    todo = "todo"
    if todo in text:
        message = time.strftime("\n- [ ] %H:%M ", time.localtime()) + text
    else:
        message = time.strftime("\n- %H:%M ", time.localtime()) + text
    response3 = client.object_exists(
        Bucket=bucket,
        Key=today_daily_file_key
     )
    # 判断本日日志是否存在
    if(response3==True):
         # 存在 读取文件
        response2 = client.get_object(
            Bucket=bucket,
            Key=today_daily_file_key
        )
        str1=response2['Body'].get_raw_stream().read().decode('utf-8')
        btyes = bytes(str1+ message, encoding="utf8")
    else:
        btyes = bytes(message, encoding="utf8")


    #btyes = bytes(str1+"- 20:01 somgthing", encoding="utf8")	
    # name = time.strftime("%Y%m%d%H%M%S", time.localtime())
    response_sum = client.put_object(
            Bucket=bucket,
            Body=btyes,
            Key=today_daily_file_key
        )
    return response_sum["ETag"]

def save_asset(file):
    # 生成ObjectKey => 日志/附件/202208/20220808131104.jpg 
    file = requests.get(file).content # wechat file is url 为了防止过期,下载下来。
    # file = bytes("![]("+file+")", encoding = "utf8") # 直接使用腾讯微信链接显示图片
    daily_asset_dir_path = "日志/附件/" + time.strftime("%Y%m",time.localtime()) + "/" 
    today_daily_asset_file_key = daily_asset_dir_path + time.strftime("%Y%m%d%H%M%S", time.localtime()) + ".jpg"
    now_key = today_daily_asset_file_key
    response_file = client.put_object(
        Bucket=bucket,
        Body=file,
        Key=now_key
    )
    print(response_file["ETag"])
    return now_key

# 已弃用 原因: 超过一定长度后微信显示服务错误
def get_daily_today():
    # 生成ObjectKey => 日志/2022-08-08.md
    daily_note_dir_path = "日志/"
    today_daily_file_key = daily_note_dir_path + time.strftime("%Y-%m-%d", time.localtime()) + ".md"
    response3 = client.object_exists(
        Bucket=bucket,
        Key=today_daily_file_key
    )
    if(response3==True):
    # 存在 读取文件
        response2 = client.get_object(
            Bucket=bucket,
            Key=today_daily_file_key
        )
        str1=response2['Body'].get_raw_stream().read().decode('utf-8')
        return str1
    else:
        return '今日无日志'

微信测试号建议直接放在桌面

微信测试号建议直接放在桌面
微信测试号
Memos 效果
源码效果

以上效果图为早期版本效果图,新的版本已经返回已保存,和可点击的链接文字。即可在微信内置浏览器使用 Memeos

希望以后会有优秀的类似转发服务提供商,而且按量付费不太贵那种。能用很简单,好用很困难。Knuth 大佬(发明 KMP 算法的那位),说二分

Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky…

这软件也是如此,思路很简单,细节是魔鬼