2026-07-19 04:48:21 +08:00
export type MemosMemo = { name : string ; content : string ; visibility : string ; creator? : string ; createTime? : string ; updateTime? : string ; tags? : string []; attachments ?: { type ?: string }[]; resources ?: { type ?: string }[] };
2026-07-19 05:27:46 +08:00
export type MemosIdentity = { name : string ; username? : string ; nickname? : string ; displayName? : string ; avatarUrl? : string ; avatar? : string };
2026-07-19 04:48:21 +08:00
export type MemosSyncRules = { creator? : string | null ; tags? : string []; from ?: string | null ; to? : string | null ; attachmentMode ?: "all" | "images" | "none" };
2026-07-19 00:29:30 +08:00
const base = ( url : string ) => url . replace ( /\/+$/ , "" ) + "/api/v1" ;
async function request ( url : string , token : string , init? : RequestInit ) {
const res = await fetch ( url , { ... init , headers : { Authorization : `Bearer ${ token } ` , "Content-Type" : "application/json" , ...( init ? . headers || {}) }, cache : "no-store" });
if ( ! res . ok ) throw new Error ( `Memos API ${ res . status } : ${ await res . text () } ` ); return res ;
}
2026-07-19 05:14:01 +08:00
export async function createUserWebhook ( baseUrl : string , token : string , userName : string , webhook : { url : string ; displayName : string ; signingSecret : string }) {
const user = userName . split ( "/" ). at ( - 1 ); if ( ! user ) throw new Error ( "Invalid Memos user" );
return ( await request ( ` ${ base ( baseUrl ) } /users/ ${ encodeURIComponent ( user ) } /webhooks` , token , { method : "POST" , body : JSON.stringify ( webhook ) })). json () as Promise < { name : string } > ;
}
2026-07-19 00:29:30 +08:00
export async function verifyMemos ( baseUrl : string , token : string ) { await request ( ` ${ base ( baseUrl ) } /memos?pageSize=1` , token ); }
export async function getMemosIdentity ( baseUrl : string , token : string ) {
2026-07-19 05:27:46 +08:00
let user : MemosIdentity ;
try { user = await ( await request ( ` ${ base ( baseUrl ) } /auth/me` , token )). json () as MemosIdentity ; if (( user as any ). user ) user = ( user as any ). user as MemosIdentity ; }
catch ( error ) { if ( ! ( error instanceof Error ) || ! error . message . startsWith ( "Memos API 404" )) throw error ; user = await ( await request ( ` ${ base ( baseUrl ) } /auth/status` , token , { method : "POST" , body : "{}" })). json () as MemosIdentity ; }
2026-07-19 00:29:30 +08:00
if ( ! user . name ) throw new Error ( "Memos did not return an account identity" );
return user ;
}
2026-07-19 03:30:19 +08:00
export function memoUrl ( baseUrl : string , memoName : string ) { const id = memoName . split ( "/" ). at ( - 1 ); return id ? ` ${ baseUrl . replace ( /\/$/ , "" ) } /m/ ${ encodeURIComponent ( id ) } ` : null ; }
2026-07-19 05:43:03 +08:00
export type MemosPage = { memos : MemosMemo []; nextPageToken : string };
export async function listMemos ( baseUrl : string , token : string , rules : MemosSyncRules = {}, page : { pageToken? : string ; pageSize? : number } = {}) : Promise < MemosPage > {
let pageToken = page . pageToken || "" ;
2026-07-19 03:30:19 +08:00
const tags = rules . tags ? . filter ( Boolean ) || []; const mode = rules . attachmentMode || "all" ;
2026-07-19 05:08:55 +08:00
const quote = ( value : string ) => JSON . stringify ( value ); const filters = [ rules . creator && `creator == ${ quote ( rules . creator ) } ` , `visibility == "PUBLIC"` , ... tags . map (( tag ) => `tags.exists(t, t == ${ quote ( tag ) } )` )]. filter ( Boolean ). join ( " && " );
2026-07-19 05:09:49 +08:00
let useServerFilter = Boolean ( filters );
2026-07-19 05:43:03 +08:00
do { const params = new URLSearchParams ({ pageSize : String ( Math . max ( 10 , Math . min ( 100 , page . pageSize || 100 ))) }); if ( pageToken ) params . set ( "pageToken" , pageToken ); if ( useServerFilter && filters ) params . set ( "filter" , filters ); let data : any ;
2026-07-19 05:09:49 +08:00
try { data = await ( await request ( ` ${ base ( baseUrl ) } /memos? ${ params } ` , token )). json (); }
catch ( error ) { if ( ! pageToken && useServerFilter && error instanceof Error && error . message . startsWith ( "Memos API 400" )) { useServerFilter = false ; continue ; } throw error ; }
2026-07-19 05:43:03 +08:00
const memos = ( data . memos || []). filter (( memo : MemosMemo ) => {
2026-07-19 03:30:19 +08:00
if ( memo . visibility !== "PUBLIC" ) return false ;
2026-07-19 04:48:21 +08:00
if ( rules . creator && memo . creator !== rules . creator ) return false ;
2026-07-19 03:30:19 +08:00
if ( tags . length && ! tags . every (( tag ) => memo . tags ? . includes ( tag ))) return false ;
const created = memo . createTime ? . slice ( 0 , 10 ); if ( rules . from && ( ! created || created < rules . from )) return false ; if ( rules . to && ( ! created || created > rules . to )) return false ;
return true ;
2026-07-19 05:43:03 +08:00
}). map (( memo : MemosMemo ) => {
2026-07-19 03:30:19 +08:00
if ( mode === "all" ) return memo ;
const onlyImages = < T extends { type ?: string }>( items : T [] | undefined ) => mode === "none" ? [] : ( items || []). filter (( item ) => item . type ? . startsWith ( "image/" ));
return { ... memo , attachments : onlyImages ( memo . attachments ), resources : onlyImages ( memo . resources ) };
2026-07-19 05:43:03 +08:00
});
return { memos , nextPageToken : data.nextPageToken || "" };
} while ( true );
2026-07-19 00:29:30 +08:00
}
export async function createMemo ( baseUrl : string , token : string , memo : Pick < MemosMemo , "content" | "visibility" > & { attachments? : unknown []; resources? : unknown [] }) {
return ( await request ( ` ${ base ( baseUrl ) } /memos` , token , { method : "POST" , body : JSON.stringify ({ state : "NORMAL" , ... memo }) })). json () as Promise < MemosMemo >;
}
export async function createAttachment ( baseUrl : string , token : string , attachment : { filename : string ; content : string ; type : string }) {
return ( await request ( ` ${ base ( baseUrl ) } /attachments` , token , { method : "POST" , body : JSON.stringify ( attachment ) })). json () as Promise < { name : string ; filename : string ; type : string } > ;
}
export async function setMemoAttachments ( baseUrl : string , token : string , memoName : string , attachments : unknown []) {
const memoId = memoName . split ( "/" ). at ( - 1 );
if ( ! memoId ) throw new Error ( "Invalid Memos memo name" );
await request ( ` ${ base ( baseUrl ) } /memos/ ${ encodeURIComponent ( memoId ) } /attachments` , token , { method : "PATCH" , body : JSON.stringify ({ name : memoName , attachments }) });
}
export async function createResource ( baseUrl : string , token : string , resource : { filename : string ; content : string ; type : string ; size : string }) {
return ( await request ( ` ${ base ( baseUrl ) } /resources` , token , { method : "POST" , body : JSON.stringify ( resource ) })). json () as Promise < { name : string ; filename : string ; type : string ; size : string } > ;
}
export async function createRemoteFile ( baseUrl : string , token : string , file : { filename : string ; content : string ; type : string ; size : string }) {
try { return { kind : "attachment" as const , value : await createAttachment ( baseUrl , token , file ) }; }
catch ( error ) {
if ( ! ( error instanceof Error ) || ! error . message . startsWith ( "Memos API 404" )) throw error ;
return { kind : "resource" as const , value : await createResource ( baseUrl , token , file ) };
}
}