浪子 浪子

折腾/memos

想到了如何通过CF Workers 让新版Memos 兼容广场,以下代码AI生成

新建一个workers

const NEW_ORIGIN = "https://memos.ee";

const KV = MEMOS_MAP; // KV 绑定名

const CORS_HEADERS = {

 "Access-Control-Allow-Origin": "*",

 "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",

 "Access-Control-Allow-Headers": "Content-Type, Authorization"

};



/* ---------- 工具:原子自增数字 id ---------- */

async function allocNumericId() {

 const val = await KV.get("next_id");

 const next = val ? Number(val) + 1 : 10000;

 await KV.put("next_id", String(next));

 return next;

}



/* ---------- 工具:建立 UID↔数字 双向映射 ---------- */

async function getNumericId(uid) {

 let num = await KV.get(`uid:${uid}`);

 if (!num) {

 num = await allocNumericId();

 await KV.put(`uid:${uid}`, String(num));

 await KV.put(`num:${num}`, uid);

 }

 return Number(num);

}



/* ---------- 用户信息内存缓存 ---------- */

const userCache = new Map();

async function fetchUser(uid) {

 if (userCache.has(uid)) return userCache.get(uid);

 const r = await fetch(`${NEW_ORIGIN}/api/v1/users/${uid}`);

 if (!r.ok) return null;

 const data = await r.json();

 userCache.set(uid, data);

 return data;

}



addEventListener("fetch", (event) => {

 event.respondWith(handleRequest(event.request));

});



async function handleRequest(req) {

 const url = new URL(req.url);



 if (req.method === "OPTIONS") {

 return new Response(null, { status: 204, headers: CORS_HEADERS });

 }



 /bin /config /dev /etc /home /lib /media /memos /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var ---------- 旧 /api/v1/memo 兼容 ---------- */

 if (url.pathname === "/api/v1/memo") {

 const creatorId = url.searchParams.get("creatorId") || "1";

 const rowStatus = url.searchParams.get("rowStatus") || "NORMAL";

 const limit = Number(url.searchParams.get("limit") || "10");

 const offset = Number(url.searchParams.get("offset") || "0");



 const need = offset + limit;

 const up = new URL("/api/v1/memos", NEW_ORIGIN);

 up.searchParams.set("parent", `users/${creatorId}`);

 up.searchParams.set("state", rowStatus);

 up.searchParams.set("pageSize", Math.min(need, 1000));



 const r = await fetch(up, {

 headers: { Authorization: req.headers.get("Authorization") || "" }

 });

 if (!r.ok) return new Response(await r.text(), { status: r.status });



 const { memos = [] } = await r.json();

 const slice = memos.slice(offset, offset + limit);



 /bin /config /dev /etc /home /lib /media /memos /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var 给每条记录分配数字 id 并缓存 */

 for (const m of slice) {

 const uid = m.name.split("/")[1];

 await getNumericId(uid);

 }



 /bin /config /dev /etc /home /lib /media /memos /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var 读 KV 拿到数字 id */

 const uidSet = new Set(slice.map(m => Number(m.creator.split("/")[1])));

 await Promise.all([...uidSet].map(uid => fetchUser(uid)));



 const legacy = await Promise.all(

 slice.map(async m => {

 const uid = Number(m.creator.split("/")[1]);

 const user = userCache.get(uid);

 const num = await getNumericId(m.name.split("/")[1]);

 return {

 id: num, // ← 数字 id

 rowStatus: m.state,

 creatorId: uid,

 createdTs: Math.floor(new Date(m.createTime).getTime() / 1000),

 updatedTs: Math.floor(new Date(m.updateTime).getTime() / 1000),

 displayTs: Math.floor(new Date(m.displayTime).getTime() / 1000),

 content: m.content,

 visibility: m.visibility.toUpperCase(),

 pinned: m.pinned,

 parent: null,

 creatorName: user?.displayName || "",

 creatorUsername: user?.username || "",

 resourceList: m.attachments || [],

 relationList: m.relations || []

 };

 })

 );



 return new Response(JSON.stringify(legacy), {

 headers: { "content-type": "application/json", ...CORS_HEADERS }

 });

 }



 /bin /config /dev /etc /home /lib /media /memos /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var ---------- /m/ 双向 301 ---------- */

 const match = url.pathname.match(/^\/m\/([^/]+)$/);

 if (match) {

 const slug = match[1];



 // 1) 纯数字 → 查 UID

 if (/^\d+$/.test(slug)) {

 const uid = await KV.get(`num:${slug}`);

 if (uid) return Response.redirect(`${NEW_ORIGIN}/memos/${uid}`, 301);

 else return new Response("Not Found", { status: 404 });

 }



 // 2) UID → 查数字并写入 KV

 const num = await getNumericId(slug);

 return Response.redirect(`${NEW_ORIGIN}/m/${num}`, 301);

 }



 /bin /config /dev /etc /home /lib /media /memos /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var ---------- 其余全部 301 ---------- */

 return Response.redirect(NEW_ORIGIN + url.pathname + url.search, 301);

}

https://memos.ee更改为自己的memos地址 把代码粘贴进去 点击部署

在KV中新建一个数据库命名空间随便填写

在workers中绑定命名空间 变量名称MEMOS_MAPKV 命名空间选择上面创建好的

最好给workers绑定好域名例如 https://old.memos.ee

使用memobbs 广场演示 https://memobbs.memos.ee

评论
浪子 浪子

折腾 Metube群晖部署,想要下载youtube

需要设置环境变量,使用v2rayA来进行代理

services:

 metube:

 image: ghcr.io/alexta69/metube

 container_name: metube

 restart: unless-stopped

 environment:

 YTDL_OPTIONS: '{"proxy":"http://127.0.0.1:20171"}'

 network_mode: "host" # 使用主机模式

 volumes:

 - ./download:/downloads

评论
浪子 浪子

折腾 blinko的确比memos搜索要快.

评论
浪子 浪子

折腾/memos Memos 0.23.0 又改API,通过API无法查询到附件了

评论
浪子 浪子

折腾/memos

memos 的粘贴上传图片有点蠢啊,相同的文件名不能自动重命名吗?竟然能直接覆盖了...

评论
浪子 浪子

折腾 现在都在搞live 照片.我想到了一个办法,使用memos的API来获取live的 jpg静态图和mov 视频,然后合成一个live

评论
浪子 浪子

一个多月没看moments有人给我留言我竟然没有看到,

所以给moments增加了一个评论通知的功能,使用环境变量读取,没有更改数据库,原版可以无痛转移.

由于原版舍弃了邮件通知的功能,评论框也没有填入邮箱的选项,所以,没有考虑邮件通知

docker-compose.yaml配置文件如下

services:

 moments:

 image: jkjoy/moments:latest

 environment:

 JWT_KEY: "BbYS93dHHfIC1cQR8rI6"

 WEBHOOK_URL: "https://open.feishu.cn/open-apis/bot/v2/hook/*" #飞书webhook 

 SITE_URL: "https://www.moments.cn" #访问地址

 QQ_WEBHOOK_URL: "https://http.asbid.cn/send_private_msg" #QQ机器人的API

 QQ_USER_ID: "123456" #接收消息的QQ号码

 ports:

 - "3000:3000"

 volumes:

 - ./data:/app/data

 - /etc/localtime:/etc/localtime:ro

 - /etc/timezone:/etc/timezone:ro

QQ机器人 折腾

评论
浪子 浪子

折腾/memos

昨天想给memos换个数据库,首先想到的是PostgreSQL,因为官方文档给了一个迁移的脚本.

结果毫无意外的失败了,在重新安装一个新的memos时,就失败了.然后在issue 提出了为何会失败,收到的回复是 使用neosmemo/memos:stable无法使用PostgreSQL,使用ghcr.io/usememos/memos:latest 则不会有这个问题.

简直无语.稳定版不稳定这不是笑话么

花了半天的功夫手动从 sqlite 转到了 mysql 数据库,虽然不知道有什么用.

评论
浪子 浪子

折腾/memos 测试webhook

瞬间图片
瞬间图片
评论
浪子 浪子

折腾/部署

zeabur.com部署memos并自动保存数据到S3储存,若意外删除数据,可自动恢复数据

fork本项目

https://github.com/jkjoy/memos-litestream

zeabur.com中导入项目并自动构建

需在环境变量中设置以下S3存储的相关参数

LITESTREAM_ACCESS_KEY_ID=000000001a2b3c40000000001

LITESTREAM_SECRET_ACCESS_KEY=K000ABCDEFGHiJkLmNoPqRsTuVwXyZ0

LITESTREAM_REPLICA_BUCKET=xxxxxxxxx

LITESTREAM_REPLICA_ENDPOINT=s3.us-west-000.backblazeb2.com

LITESTREAM_REPLICA_PATH=memos_prod.db 本项保持默认

然后重新部署

评论