diff --git a/manifest.json b/manifest.json
index 86df8e0..645157d 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,9 +1,9 @@
{
- "id": "znote-sync",
- "name": "Znote Sync",
+ "id": "sonicnote-sync",
+ "name": "SonicNote Sync",
"version": "1.0.0",
"minAppVersion": "0.15.0",
- "description": "Sync recordings from Znote (SonicNote) to Obsidian as Markdown files",
- "author": "EasyLink",
+ "description": "Sync recordings from SonicNote to Obsidian as Markdown files",
+ "author": "EasyLinkIn",
"isDesktopOnly": false
}
diff --git a/package.json b/package.json
index d1346ca..e33087a 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,13 @@
{
- "name": "znote-sync",
+ "name": "sonicnote-sync",
"version": "1.0.0",
- "description": "Sync recordings from Znote (SonicNote) to Obsidian as Markdown files",
+ "description": "Sync recordings from SonicNote to Obsidian as Markdown files",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
},
- "keywords": ["obsidian", "znote", "recording", "sync"],
+ "keywords": ["obsidian", "sonicnote", "recording", "sync"],
"author": "EasyLink",
"license": "MIT",
"devDependencies": {
diff --git a/release/obsidian-plugin.html b/release/obsidian-plugin.html
new file mode 100644
index 0000000..86db441
--- /dev/null
+++ b/release/obsidian-plugin.html
@@ -0,0 +1,252 @@
+
+
+
+
+
+SonicNote Sync — Obsidian 插件
+
+
+
+
+
+
+
+
+
+
前置要求
+
+ - Obsidian 桌面版 v0.15.0 及以上(下载地址)
+ - SonicNote 妙记账号
+
+
+
+
+
安装
+
+ - 点击上方按钮下载 sonicnote-sync.zip
+ - 解压 zip 文件,得到以下文件:
+
+ - main.js
+ - manifest.json
+ - styles.css
+
+
+ - 打开 Obsidian Vault 所在文件夹,进入插件目录:
+
+
+
+ - 在 plugins 目录下新建文件夹 sonicnote-sync,将解压的 3 个文件放进去:
+
+
.obsidian/plugins/sonicnote-sync/
+├── main.js
+├── manifest.json
+└── styles.css
+
+ - 重启 Obsidian,进入 设置 → 第三方/社区插件,在列表中找到 SonicNote Sync 并启用
+
+
+
+
+
使用方法
+
登录
+
+ - 进入 设置 → SonicNote Sync
+ - 点击"登录"按钮
+ - 输入 SonicNote 妙记的手机号和密码(使用 APP 验证码或者申领长期密码)
+
+
+
同步录音
+
登录成功后,通过以下任一方式触发同步:
+
+ - 点击左侧功能区的 耳机图标
+ - 命令面板(Ctrl/Cmd + P)输入 "SonicNote: 同步录音"
+
+
同步完成后,Vault 中会生成 SonicNoteSync/ 文件夹,每条录音对应一个 Markdown 文件。
+
+
+
+
同步内容
+
每个录音文件包含:
+
+ | 内容 | 说明 |
+
+ | Frontmatter | 录音元数据(ID、时间、时长、设备名、标签等) |
+ | 笔记 | 你在 App 中手写的笔记内容 |
+ | 转录内容 | AI 转写文本(含说话人、时间戳) |
+ | AI 总结 | 智能总结内容(要点、行动项等) |
+
+
+
+
+
+
设置项
+
+ | 设置 | 默认值 | 说明 |
+
+ | 同步文件夹 | SonicNoteSync | Markdown 文件存放目录 |
+
+
+
+
+
+
卸载
+
+ - 进入 设置 → 社区插件,找到 SonicNote Sync,点击禁用并卸载
+ - 手动删除 .obsidian/plugins/sonicnote-sync/ 文件夹
+
+
+
+
+
+
+
+
+
+
+
diff --git a/release/sonicnote-sync.zip b/release/sonicnote-sync.zip
new file mode 100644
index 0000000..903b12b
Binary files /dev/null and b/release/sonicnote-sync.zip differ
diff --git a/src/api.ts b/src/api.ts
index 8fad6f5..d346902 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -1,8 +1,8 @@
import { requestUrl, RequestUrlParam } from 'obsidian';
-import { BackendResponse, Recording, TranscriptSegment, SummaryData, ZnotePluginSettings } from './types';
+import { BackendResponse, Recording, TranscriptSegment, SummaryData, SonicNotePluginSettings } from './types';
-export class ZnoteApiClient {
- constructor(private getSettings: () => ZnotePluginSettings) {}
+export class SonicNoteApiClient {
+ constructor(private getSettings: () => SonicNotePluginSettings) {}
isAuthenticated(): boolean {
return this.getSettings().token !== '';
@@ -31,19 +31,25 @@ export class ZnoteApiClient {
headers['Authorization'] = `Bearer ${this.token}`;
}
- const params: RequestUrlParam = {
- url,
- method,
- headers,
- };
-
if (method === 'POST' && options?.body) {
headers['Content-Type'] = 'application/json';
- params.body = JSON.stringify(options.body);
}
- const response = await requestUrl(params);
- return response.json as BackendResponse;
+ try {
+ const response = await requestUrl({
+ url,
+ method,
+ headers,
+ body: method === 'POST' && options?.body ? JSON.stringify(options.body) : undefined,
+ });
+ return response.json as BackendResponse;
+ } catch (e: any) {
+ // requestUrl throws on non-2xx status codes; try to extract the body
+ if (e?.json) {
+ return e.json as BackendResponse;
+ }
+ throw new Error(e?.message || `请求失败: ${method} ${path}`);
+ }
}
async login(phone: string, code: string): Promise<{ token: string; userId: string }> {
@@ -53,9 +59,14 @@ export class ZnoteApiClient {
if (res.code !== 200) {
throw new Error(res.msg || '登录失败');
}
+ const data = res.data;
+ const token = typeof data === 'string' ? data : data?.token;
+ if (!token) {
+ throw new Error('登录响应中缺少 token');
+ }
return {
- token: res.data.token || res.data,
- userId: res.data.userId || '',
+ token,
+ userId: data?.user?.userId || data?.userId || '',
};
}
diff --git a/src/formatter.ts b/src/formatter.ts
index d07dab4..8101b8a 100644
--- a/src/formatter.ts
+++ b/src/formatter.ts
@@ -50,7 +50,7 @@ export function generateFrontmatter(recording: Recording, syncTime: string): str
}
lines.push('tags:');
- lines.push(' - znote');
+ lines.push(' - sonicnote');
const recordTypeLabel = recording.recordType === '00' ? '通话' : '录音';
lines.push(` - ${recordTypeLabel}`);
diff --git a/src/main.ts b/src/main.ts
index 39b58e8..58af37a 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,12 +1,12 @@
import { Notice, Plugin } from 'obsidian';
-import { ZnoteApiClient } from './api';
+import { SonicNoteApiClient } from './api';
import { SyncService } from './sync';
-import { ZnoteSettingTab } from './settings';
-import { DEFAULT_SETTINGS, ZnotePluginSettings } from './types';
+import { SonicNoteSettingTab } from './settings';
+import { DEFAULT_SETTINGS, SonicNotePluginSettings } from './types';
-export default class ZnoteSyncPlugin extends Plugin {
- settings: ZnotePluginSettings = DEFAULT_SETTINGS;
- private api!: ZnoteApiClient;
+export default class SonicNoteSyncPlugin extends Plugin {
+ settings: SonicNotePluginSettings = DEFAULT_SETTINGS;
+ private api!: SonicNoteApiClient;
private syncService!: SyncService;
private statusBarEl!: HTMLElement;
@@ -14,7 +14,7 @@ export default class ZnoteSyncPlugin extends Plugin {
await this.loadSettings();
// Initialize API client and sync service
- this.api = new ZnoteApiClient(() => this.settings);
+ this.api = new SonicNoteApiClient(() => this.settings);
this.syncService = new SyncService(
this.app,
this.api,
@@ -27,42 +27,42 @@ export default class ZnoteSyncPlugin extends Plugin {
this.updateStatusBar();
// Ribbon icon
- this.addRibbonIcon('headphones', 'Znote Sync: 同步录音', () => {
+ this.addRibbonIcon('headphones', 'SonicNote Sync: 同步录音', () => {
this.triggerSync();
});
// Commands
this.addCommand({
- id: 'znote-sync:sync',
+ id: 'sonicnote-sync:sync',
name: '同步录音',
callback: () => this.triggerSync(),
});
this.addCommand({
- id: 'znote-sync:login',
+ id: 'sonicnote-sync:login',
name: '登录',
callback: () => {
// @ts-ignore - internal API to open settings
this.app.setting.open();
// @ts-ignore
- this.app.setting.openTabById('znote-sync');
+ this.app.setting.openTabById('sonicnote-sync');
},
});
this.addCommand({
- id: 'znote-sync:logout',
+ id: 'sonicnote-sync:logout',
name: '登出',
callback: async () => {
this.settings.token = '';
this.settings.phoneNumber = '';
await this.saveSettings();
- new Notice('已登出 Znote');
+ new Notice('已登出 SonicNote');
this.updateStatusBar();
},
});
// Settings tab
- this.addSettingTab(new ZnoteSettingTab(
+ this.addSettingTab(new SonicNoteSettingTab(
this.app,
this,
this.api,
@@ -70,11 +70,11 @@ export default class ZnoteSyncPlugin extends Plugin {
() => this.saveSettings()
));
- console.log('Znote Sync plugin loaded');
+ console.log('SonicNote Sync plugin loaded');
}
onunload() {
- console.log('Znote Sync plugin unloaded');
+ console.log('SonicNote Sync plugin unloaded');
}
async loadSettings() {
@@ -87,15 +87,15 @@ export default class ZnoteSyncPlugin extends Plugin {
private async triggerSync() {
if (!this.api.isAuthenticated()) {
- new Notice('请先登录 Znote(设置 → Znote Sync)');
+ new Notice('请先登录 SonicNote(设置 → SonicNote Sync)');
return;
}
- this.statusBarEl.setText('Znote: 同步中...');
+ this.statusBarEl.setText('SonicNote: 同步中...');
try {
const result = await this.syncService.syncAll((msg) => {
- this.statusBarEl.setText(`Znote: ${msg}`);
+ this.statusBarEl.setText(`SonicNote: ${msg}`);
});
let message = `同步完成: ${result.synced} 条新/更新`;
@@ -115,9 +115,9 @@ export default class ZnoteSyncPlugin extends Plugin {
const lastSync = this.settings.lastSyncTime
? `上次同步: ${this.settings.lastSyncTime.substring(0, 16).replace('T', ' ')}`
: '未同步';
- this.statusBarEl.setText(`Znote: ${lastSync}`);
+ this.statusBarEl.setText(`SonicNote: ${lastSync}`);
} else {
- this.statusBarEl.setText('Znote: 未登录');
+ this.statusBarEl.setText('SonicNote: 未登录');
}
}
}
diff --git a/src/settings.ts b/src/settings.ts
index ceac65f..d625f74 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -1,17 +1,17 @@
import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
-import { ZnoteApiClient } from './api';
-import { ZnotePluginSettings, DEFAULT_SETTINGS } from './types';
+import { SonicNoteApiClient } from './api';
+import { SonicNotePluginSettings, DEFAULT_SETTINGS } from './types';
-export class ZnoteSettingTab extends PluginSettingTab {
- private api: ZnoteApiClient;
- private getSettings: () => ZnotePluginSettings;
+export class SonicNoteSettingTab extends PluginSettingTab {
+ private api: SonicNoteApiClient;
+ private getSettings: () => SonicNotePluginSettings;
private saveSettings: () => Promise;
constructor(
app: App,
plugin: Plugin,
- api: ZnoteApiClient,
- getSettings: () => ZnotePluginSettings,
+ api: SonicNoteApiClient,
+ getSettings: () => SonicNotePluginSettings,
saveSettings: () => Promise
) {
super(app, plugin);
@@ -25,26 +25,14 @@ export class ZnoteSettingTab extends PluginSettingTab {
const settings = this.getSettings();
containerEl.empty();
- containerEl.createEl('h2', { text: 'Znote Sync 设置' });
-
- // Server URL
- new Setting(containerEl)
- .setName('服务器地址')
- .setDesc('智音笔记后端服务地址')
- .addText(text => text
- .setPlaceholder('http://localhost:8048')
- .setValue(settings.serverUrl)
- .onChange(async (value) => {
- settings.serverUrl = value;
- await this.saveSettings();
- }));
+ containerEl.createEl('h2', { text: 'SonicNote Sync 设置' });
// Sync folder
new Setting(containerEl)
.setName('同步文件夹')
.setDesc('录音 Markdown 文件存放的文件夹(相对于 Vault 根目录)')
.addText(text => text
- .setPlaceholder('ZnoteSync')
+ .setPlaceholder('SonicNoteSync')
.setValue(settings.syncFolder)
.onChange(async (value) => {
settings.syncFolder = value;
@@ -83,15 +71,15 @@ export class ZnoteSettingTab extends PluginSettingTab {
}
class LoginModal extends Modal {
- private api: ZnoteApiClient;
- private getSettings: () => ZnotePluginSettings;
+ private api: SonicNoteApiClient;
+ private getSettings: () => SonicNotePluginSettings;
private saveSettings: () => Promise;
private onCloseCallback: () => void;
constructor(
app: App,
- api: ZnoteApiClient,
- getSettings: () => ZnotePluginSettings,
+ api: SonicNoteApiClient,
+ getSettings: () => SonicNotePluginSettings,
saveSettings: () => Promise,
onCloseCallback: () => void
) {
@@ -106,7 +94,7 @@ class LoginModal extends Modal {
const { contentEl } = this;
contentEl.empty();
- contentEl.createEl('h2', { text: '登录智音笔记' });
+ contentEl.createEl('h2', { text: '登录 SonicNote' });
let phone = '';
let code = '';
@@ -118,9 +106,10 @@ class LoginModal extends Modal {
.onChange((value) => { phone = value; }));
new Setting(contentEl)
- .setName('验证码')
+ .setName('密码')
+ .setDesc('使用 APP 验证码或者申领长期密码')
.addText(text => text
- .setPlaceholder('请输入验证码')
+ .setPlaceholder('请输入密码')
.onChange((value) => { code = value; }));
new Setting(contentEl)
@@ -129,7 +118,7 @@ class LoginModal extends Modal {
.setCta()
.onClick(async () => {
if (!phone || !code) {
- new Notice('请输入手机号和验证码');
+ new Notice('请输入手机号和密码');
return;
}
try {
diff --git a/src/sync.ts b/src/sync.ts
index b568a7a..5c9356b 100644
--- a/src/sync.ts
+++ b/src/sync.ts
@@ -1,18 +1,18 @@
import { App, Notice, TFile, TFolder } from 'obsidian';
-import { ZnoteApiClient } from './api';
-import { ZnotePluginSettings, LocalFileInfo, Recording, SyncResult, TranscriptSegment, SummaryData } from './types';
+import { SonicNoteApiClient } from './api';
+import { SonicNotePluginSettings, LocalFileInfo, Recording, SyncResult, TranscriptSegment, SummaryData } from './types';
import { formatFileName, toMarkdown } from './formatter';
export class SyncService {
- private api: ZnoteApiClient;
+ private api: SonicNoteApiClient;
private app: App;
- private getSettings: () => ZnotePluginSettings;
+ private getSettings: () => SonicNotePluginSettings;
private saveSettings: () => Promise;
constructor(
app: App,
- api: ZnoteApiClient,
- getSettings: () => ZnotePluginSettings,
+ api: SonicNoteApiClient,
+ getSettings: () => SonicNotePluginSettings,
saveSettings: () => Promise
) {
this.app = app;
@@ -113,7 +113,7 @@ export class SyncService {
return match ? match[1] : null;
}
- private async processRecording(recording: Recording, localIndex: Map, settings: ZnotePluginSettings): Promise {
+ private async processRecording(recording: Recording, localIndex: Map, settings: SonicNotePluginSettings): Promise {
const syncTime = new Date().toISOString();
// Check if we need to update
@@ -150,7 +150,7 @@ export class SyncService {
});
}
- private async writeRecordingToNewFile(recording: Recording, syncTime: string, settings: ZnotePluginSettings, conflictWithPath?: string): Promise {
+ private async writeRecordingToNewFile(recording: Recording, syncTime: string, settings: SonicNotePluginSettings, conflictWithPath?: string): Promise {
const content = await this.buildRecordingContent(recording, syncTime);
let fileName = formatFileName(recording);
diff --git a/src/types.ts b/src/types.ts
index 485d5fc..bd4e727 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,6 +1,6 @@
// ===== 插件设置 =====
-export interface ZnotePluginSettings {
+export interface SonicNotePluginSettings {
serverUrl: string;
syncFolder: string;
pageSize: number;
@@ -9,9 +9,9 @@ export interface ZnotePluginSettings {
lastSyncTime: string;
}
-export const DEFAULT_SETTINGS: ZnotePluginSettings = {
- serverUrl: 'http://ainote.easylinkin.com:8048',
- syncFolder: 'ZnoteSync',
+export const DEFAULT_SETTINGS: SonicNotePluginSettings = {
+ serverUrl: 'https://ainote.easylinkin.com:18048/prod-api',
+ syncFolder: 'SonicNoteSync',
pageSize: 50,
token: '',
phoneNumber: '',
diff --git a/styles.css b/styles.css
index 457130d..37c422d 100644
--- a/styles.css
+++ b/styles.css
@@ -1,10 +1,10 @@
-/* Znote Sync Plugin Styles */
+/* SonicNote Sync Plugin Styles */
/* Login modal */
-.znote-login-modal .setting-item {
+.sonicnote-login-modal .setting-item {
padding: 8px 0;
}
-.znote-login-modal .setting-item-control input {
+.sonicnote-login-modal .setting-item-control input {
width: 200px;
}