标签 memos 下的文章

根据上文

[article id="1668"]

使用docker-compose部署
执行

mkdir qq2memos
cd qq2memos
vim docker-compose.yaml

输入以下内容

services:
  napcat:
    container_name: napcat
    mac_address: 02:42:ac:11:00:91 #自己修改
    environment:
      - ACCOUNT=3319693101 #QQ机器人号码
      - WSR_ENABLE=true
      - WS_URLS=["ws://memos:8080/onebot/v11/ws"]
      - NAPCAT_UID=0
      - NAPCAT_GID=0
    ports:
      - 6099:6099
      - 3000:3000
    restart: always
    image: mlikiowa/napcat-docker:latest
    volumes:
      - "./QQ:/app/.config/QQ"
      - "./config:/app/napcat/config"
    networks: 
      - memos
  memos:
    container_name: memos
    environment:
      - MEMOS_API=https://memos.imsun.org/api/v1/memo ##自己修改
    image: jkjoy/qq2memos:latest  
    volumes:  
      - "./data:/app/data"  
    restart: always
    networks: 
      - memos
networks:
  memos:

执行

docker-compose up -d

登录napcat的webui
ip:6099/webui/login.html

填写反向WS地址为

ws://memos:8080/onebot/v11/ws

即可

在群晖部署可使用
2024-10-04T11:22:28.png
提前在/docker/qqbot路径下 创建 data QQ config三个文件夹以免权限不足构建失败


简介

介绍如何使用Docker快速部署一个QQ机器人并对接Nonebot实现Memos机器人的功能:

  • 绑定memos账号
  • 转发消息发送到memos

步骤

部署QQ机器人

这里使用的项目是基于QQNT的无头机器人方案,使用webui登录,相对于之前我部署的Go-cqhttp的方案的好处是不会被风控掉线.
稳定性很nice

使用的项目地址
使用的项目文档: https://llonebot.github.io/zh-CN/guide/getting-started

Windows系统

在windows下非常简单,下载QQNT版本的QQ,登录你的QQ机器人账号

https://github.com/super1207/install_llob/releases

下载 exe,双击运行即可,之后打开 QQ 的设置,看到了 LLOneBot 就代表安装成功了。

Linux系统

在linux下 我选择使用 的项目 NapCatQQ
地址 : https://github.com/NapNeko/NapCatQQ

使用Docker部署
docker-compose.yaml内容如下

services:
  napcat:
    environment:
      - ACCOUNT=153985848 #QQ机器人号码
      - WS_ENABLE=true
      - NAPCAT_UID=0
      - NAPCAT_GID=0
    ports:
      - 3001:3001 #上传端口
      - 6099:6099 #webui端口
      - 3000:3000 #http端口 
    restart: always
    image: mlikiowa/napcat-docker:latest
    volumes:
      - "./napcat/app/.config/QQ:/app/.config/QQ"
      - "./napcat/app/napcat/config:/app/napcat/config"
    network_mode: host #使用host的原因是为了方便对接宿主机的nonebot框架

启动

docker-compose up -d

访问 http://ip:6099/webui/login.html

注意 : 登录所使用的 token 在docker-compose.yaml 所在目录下的
/napcat/app/napcat/config中的webui.json
QQ20240912-083331.png

扫码登录

在设置页面中添加反向 WS 地址,地址为 ws://127.0.0.1:8080/onebot/v11/ws, 这里的 8080 是 NoneBot 输出的端口号,/onebot/v11/ws 是 NoneBot onebot 适配器默认的路径
2024-09-12T00:36:18.png

部署nonebot

要求环境​Python 版本 >= 3.9

按照文档操作 https://llonebot.github.io/zh-CN/guide/nonebot2

Memos转发机器人的实现

在nonebot 项目中

新建 bot.py 内容为

import nonebot
from nonebot.adapters.onebot.v11 import Adapter as ONEBOT_V11Adapter

# 初始化 NoneBot
nonebot.init()

# 注册适配器
driver = nonebot.get_driver()
driver.register_adapter(ONEBOT_V11Adapter)

# 在这里加载插件
nonebot.load_builtin_plugins("echo")  # 加载内置插件
nonebot.load_from_toml("pyproject.toml")  # 从 toml 文件加载插件

# 如果有额外的插件目录,可以这样加载
# nonebot.load_plugins("src/plugins")

if __name__ == "__main__":
    nonebot.run()

新建 memos/plugins 文件夹 , 在其下创建 qq_to_memos.py 内容为

from nonebot import on_command, on_message, get_driver
from nonebot.rule import to_me
from nonebot.adapters.onebot.v11 import Bot, Event, Message
from nonebot.params import CommandArg
import json
import os
import httpx
from typing import Dict, Any
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='memos_bot.log',
    filemode='a'
)
logger = logging.getLogger(__name__)

# 文件路径
JSON_FILE = "users_data.json"

# 读取 JSON 数据
def read_json() -> Dict[str, Any]:
    if os.path.exists(JSON_FILE):
        with open(JSON_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {}

# 写入 JSON 数据
def write_json(data: Dict[str, Any]):
    with open(JSON_FILE, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=4)

# 初始化函数
async def init():
    if not os.path.exists(JSON_FILE):
        write_json({})
        logger.info(f"Created new JSON file: {JSON_FILE}")

# 注册命令
start = on_command("start", rule=to_me(), priority=5)

@start.handle()
async def handle_start(bot: Bot, event: Event, args: Message = CommandArg()):
    user_id = event.get_user_id()
    token = args.extract_plain_text().strip()
    if not token:
        await start.finish(" 请提供 Token,格式:/start <token>")
        logger.warning(f"User {user_id} failed to start due to missing token")
        return

    users_data = read_json()
    users_data[user_id] = {"token": token}
    write_json(users_data)

    logger.info(f"User {user_id} started successfully")
    await start.finish(" 绑定成功!现在您可以直接发送消息,我会将其保存到 Memos。")

# 处理所有消息
memo = on_message(priority=5)

@memo.handle()
async def handle_memo(bot: Bot, event: Event):
    user_id = event.get_user_id()
    message = event.get_message()

    users_data = read_json()
    user_info = users_data.get(user_id)

    if not user_info:
        await memo.finish(" 您还未绑定,请先使用 /start <token> 命令绑定。")
        logger.warning(f"Unstarted user {user_id} attempted to send a memo")
        return

    token = user_info["token"]

    text_content = message.extract_plain_text()

    # 如果消息为空,不处理
    if not text_content.strip():
        return

    # 发送到 Memos
    async with httpx.AsyncClient() as client:
        try:
            payload = {
                "content": text_content.strip(),
                "visibility": "PUBLIC"
            }
            response = await client.post(
                "https://memos.ee/api/v1/memos",
                json=payload,
                headers={"Authorization": f"Bearer {token}"}
            )
            response.raise_for_status()
            logger.info(f"Memo sent successfully for user {user_id}")

        except httpx.HTTPStatusError as e:
            logger.error(f"HTTP error occurred for user {user_id}: {e}")
            logger.error(f"Response content: {e.response.text}")
            await memo.finish(f" 发送失败,错误代码:{e.response.status_code},请检查您的 Token 和网络连接。")
            return
        except Exception as e:
            logger.error(f"Unexpected error occurred for user {user_id}: {e}")
            await memo.finish(" 发送过程中发生意外错误,请稍后重试。")
            return

    await memo.finish(" 已成功发送到 Memos!")

# 获取驱动器并注册启动事件
driver = get_driver()
driver.on_startup(init)

logger.info("Memos bot plugin initialized")

API 端点按自己需求更改即可.

在 pyproject.toml 中加入

plugins = []
plugin_dirs = ["memos/plugins"]

运行机器人

nb run

使用机器人

在聊天界面 使用命令

/start token

token 为 Memos 后台获取的对应的token
绑定成功后如下图效果
2024-09-12T00:44:49.png

然后直接发送消息

此时发送消息即可转发到 memos. 且默认为公开的 memos.

如需默认为其他状态 需修改 qq_to_memos.py 中 "visibility" 的 值 .

结尾

如果你想尝试一下此功能可以添加 QQ 机器人

153985848
需添加为好友且 在 https://memos.ee 注册获取 token 便可以使用.


前言

为了更加方便的同步内容,可以作备份之用.

声明

代码和功能实现来自于@大大的蜗牛 原理是使用webhook. 在发布内容时,触发脚本运行.

步骤

同步脚本

脚本中API_HOST为memos的API AUTHORIZATION为memos中Token CONTENT_URL111363033003475492为mastodon的用户的ID 获得用户ID的方法可以参见

[article id="1469"]

#!/bin/sh

# API 和 Token
API_HOST="https://memos.ee/api/v1/memo"
AUTHORIZATION="Bearer eyJhbGciOiJIUzI1NiIsImtpZCI6InYxIiwidHlwIjoiSldUIn0.eyJuYW1lIjoiamtqb3kiLCJpc3MiOiJtZW1vcyIsInN1YiI6IjEiLCJhdWQiOlsidXNlci5hY2Nlc3MtdG9rZW4iXSwiaWF0IjoxNjk3ODc0NTk2fQ.jNGMDE1YVX4Qj6hNhmrxb63WlRM5kGX10k_qRXH6ID4"

# 原始内容
CONTENT_URL="https://09j.cn/api/v1/accounts/111363033003475492/statuses?limit=1"
CONTENT=$(curl --connect-timeout 60 -s $CONTENT_URL | jq -r &#039;.[0]&#039;)
# mastodon
MASTODON_URL=$(echo $CONTENT | grep -oP &#039;https:\/\/09j\.cn\/@[^\/]+\/\d+&#039;)
DUDU_CONTENT="[自动转发自我的Mastodon]($MASTODON_URL)"

MENTIONS=$(echo $CONTENT | jq -r &#039;.mentions[]&#039;)
if [ ! -z "$MENTIONS" ]; then
  echo "Skipping status mention! $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")"
  echo ======================================================
  exit 0
fi

MEDIA=$(echo $CONTENT | jq -r &#039;.media_attachments&#039;)
# 判断 Media 的内容
if [ "$MEDIA" != "null" ]; then
  MEDIAS=$(echo $CONTENT | jq -r &#039;.media_attachments[] | select(.type=="image") | .url&#039;)
  # 拼接图片
  images=""
  for url in $MEDIAS; do
    images="$images![image]($url)\n"
  done
  TEXT=$(echo "$CONTENT" | jq -r &#039;.content&#039; | sed &#039;s/ +/ /g&#039; | lynx -dump -stdin -nonumbers -nolist | tr -d &#039;\n&#039; | sed &#039;/^$/N;s/\n\n/\n/g&#039; | sed &#039;s/^[[:space:]]*//;s/[[:space:]]*$//&#039;)
  TEXT="$TEXT\n$DUDU_CONTENT"
  TEXT="$TEXT\n$images"

else
   # 普通内容
  TEXT=$(echo "$CONTENT" | jq -r &#039;.content&#039; | sed &#039;s/ +/ /g&#039; | lynx -dump -stdin -nonumbers -nolist | tr -d &#039;\n&#039; | sed &#039;/^$/N;s/\n\n/\n/g&#039; | sed &#039;s/^[[:space:]]*//;s/[[:space:]]*$//&#039;)
  TEXT="${TEXT}\n$DUDU_CONTENT"
fi

curl -X POST \
  -H "Accept: application/json" \
  -H "Authorization: $AUTHORIZATION" \
  -d "{ \"content\": \"$TEXT\" }" \
  $API_HOST

echo Sync Mastodon to Memos Successful! $(TZ=UTC-8 date +"%Y-%m-%d"" ""%T")
echo ======================================================

我稍微做了一点修改.在发布到memos的同时贴上原本mastodon的原文链接. 由于我不会写规则,就随意写了匹配规则 我的实例为09j.cn 不需要则删除以下

MASTODON_URL=$(echo $CONTENT | grep -oP &#039;https:\/\/09j\.cn\/@[^\/]+\/\d+&#039;)
DUDU_CONTENT="[自动转发自我的Mastodon]($MASTODON_URL)" 
TEXT="$TEXT\n$DUDU_CONTENT"

即可

部署webhook

Docker镜像是根据官方dockerfile增加了中文支持,

推荐使用docker-compose部署 编辑docker-compose.yaml内容为

services:
  webhook:
    image: jkjoy/webhook
    container_name: webhook
    command: -verbose -hooks=hooks.yml -hotreload
    environment:
      - TZ=Asia/Chongqing #中国时区
      - LANG=C.UTF-8  #中文支持
    volumes:
      - ./config:/config:ro
    ports:
      - 9000:9000
    restart: always

在根目录下创建config目录,并在config下创建hooks.yml文件并编辑内容为

- id: memos
  execute-command: "/config/memos.sh"
  command-working-directory: "/"

把脚本内容保存为memos.sh保存在config目录下

然后在docker-compose.yaml所在的根目录下 运行docker compose up -d即可

使用Webhook

hooks.yaml为webhook的配置

其中的execute-command为可执行脚本

webhook的访问地址格式为

服务器 ip:端口/hooks/ID

以127.0.0.1为例 访问http://127.0.0.1:9000/hooks/memos

设置mastodon

在管理员后台中管理-webhooks-新增对端 对端URL填入http://127.0.0.1:9000/hooks/memos 已启用事件选择status.created 点击新增即可在发布新的嘟嘟时同步内容到memos了.

其他

同理也可以使用webhook在发布memos时候同步到其他拥有API的服务中了.


通过memos的自定义样式更改字体为霞鹜文楷

由于cdn.staticfile.org出现了跨域的问题导致很多图标和字体无法正常显示.

加上ucloud买了一年100G的CDN流量.

自己反正也很少用,就自建一个霞鹜文楷的CDN

把以下代码复制粘贴进memos的自定义样式中保存即可.

@import '//cdn.09j.cn/lxgw-wenkai-webfont/style.css';
body {
font-family: "LXGW WenKai", sans-serif;
}