FeiSync

unlisted

by Gao-Qian-Long

Sync your notes to Feishu (Lark) Drive.

Updated 1mo agoMIT
View on GitHub

FeiSync

Sync your Obsidian notes to Feishu (Lark) Drive.

中文说明


Features

  • One-way sync: Upload Obsidian notes to Feishu Drive with local files as the source of truth
  • Incremental sync: Skip unchanged files based on SHA-256 content hash to save bandwidth
  • Multi-folder sync: Configure multiple local folders mapped to different Feishu folders independently
  • Manual sync: Trigger sync via command palette or ribbon icon
  • Auto sync (optional): Watch for file changes and upload automatically with debouncing
  • Scheduled sync (optional): Auto-sync at configurable intervals (1–1440 minutes)
  • Download from Feishu: Pull files from cloud to local, with conflict detection via hash comparison
  • Delete sync (optional): Remove cloud files when local files are deleted or renamed
  • Ignore rules: .feisync-ignore.md file with gitignore-compatible syntax
  • File tree browser: Browse Feishu folders and view complete recursive file trees with metadata
  • Sync log viewer: Built-in log modal showing upload, skip, delete, download, and error events
  • User OAuth: Personal cloud space access without IP whitelist
  • Proxy support (optional): Reverse proxy for restricted networks
  • Chunked upload: Large files uploaded in 4MB chunks, no size limit
  • Rate limiting & retry: Built-in 5 QPS rate limiter with configurable retry attempts
  • Concurrency control: Configurable max concurrent uploads (1–10)

Important: This plugin performs one-way sync (Obsidian → Feishu). If you modify files directly in Feishu and then sync from Obsidian, the cloud changes will be overwritten by the local version. Use "Download from Feishu" to pull cloud changes to local first.


Installation

Requirements

  • Obsidian 0.15.0+ (Desktop only)
  • A Feishu enterprise account

Steps

  1. Copy the plugin folder to .obsidian/plugins/ in your vault
  2. Restart Obsidian
  3. Enable "FeiSync" in Community Plugins settings

Quick Start

1. Create a Feishu App

  1. Go to Feishu Open Platform and log in
  2. Click "Create Enterprise App"
  3. Note down App ID and App Secret from "Credentials & Basic Info"

2. Configure App Permissions

Add these permissions in "Permission Management" and then publish a new version:

PermissionIdentifierPurpose
Cloud Spacedrive:driveFile and folder CRUD operations
Export Documents (Readonly)drive:export:readonlyExport/download online documents
Download Filedrive:file:downloadDownload cloud file content
Online Documentdocx:documentAccess online documents
Import Documentdocs:document:importImport Markdown as Feishu docs
Export Documentdocs:document:exportExport Feishu docs to other formats

3. Configure Web App (OAuth)

  1. Go to app → "App Features" → "Web App"
  2. Add a web app with:
    • Desktop Homepage: https://localhost
    • Redirect URL: http://localhost:9527/callback

4. Configure Plugin

  1. Open Settings → FeiSync
  2. Enter App ID and App Secret
  3. Add folder mappings:
    • Auto mode: Automatically create/manage folders under a root Feishu folder
    • Custom mode: Specify an exact Feishu folder token
  4. Click "Start Authorization" and complete OAuth in your browser
  5. (Optional) Enable auto sync, scheduled sync, or delete sync

5. Sync

  • Click the cloud-upload ribbon icon for a menu with sync/download/settings
  • Use the Command Palette (Ctrl+P / Cmd+P):
    • FeiSync: Sync now
    • FeiSync: Download from feishu
    • FeiSync: View sync log

Folder Mapping Modes

ModeBehaviorUse Case
AutoPlugin automatically creates a subfolder under the configured Feishu root folder. The folder token is managed internally.Simple setup, one-click sync
CustomYou specify the exact Feishu folder token. Use the built-in Browse Feishu Folder button to navigate and select.Precise control over cloud location

Ignore Rules

Create .feisync-ignore.md in your vault root. Syntax is compatible with .gitignore:

# Ignore directories
attachments/
node_modules/

# Ignore by extension
*.log
*.tmp

# Ignore anywhere in the tree
**/.DS_Store

# Un-ignore (exception)
!important.md

Changes to this file are picked up automatically on the next sync.


Plugin Settings

SettingDefaultDescription
App IDFeishu app identifier
App secretFeishu app secret
Sync folder mappingsOne or more local→remote folder pairs
Auto sync on changeOffWatch local files and sync after a debounce
Debounce interval5sDelay after file change before auto-sync
Scheduled syncOffAuto-sync at fixed time intervals
Sync interval30minInterval for scheduled sync
Delete syncOnRemove cloud files when local files are deleted
Max concurrent uploads3Parallel upload limit (1–10)
Max retry attempts3API call retry attempts on failure
Proxy URLOptional reverse proxy for open.feishu.cn

Proxy Server (Optional)

Only needed if you cannot directly access open.feishu.cn.

Nginx Config

server {
    listen 8080;
    server_name _;

    add_header 'Access-Control-Allow-Origin' '*' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;

    location / {
        if ($request_method = 'OPTIONS') { return 204; }

        resolver 8.8.8.8 ipv6=off valid=300s;
        resolver_timeout 5s;

        proxy_pass https://open.feishu.cn/;
        proxy_http_version 1.1;
        proxy_set_header Host open.feishu.cn;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_buffering off;
        proxy_ssl_server_name on;
        proxy_connect_timeout 30s;
    }
}

Architecture

feisync/
├── main.ts                 # Plugin entry, lifecycle, commands, coordination
├── settings.ts             # Settings UI, config management, sync log modal
├── feishuAuth.ts           # OAuth & token management (tenant + user tokens)
├── feishuApi.ts            # Feishu Drive API wrapper
│                           #   - Folder/file CRUD
│                           #   - Upload (full + chunked)
│                           #   - Download, export, import
│                           #   - Rate limiting (5 QPS)
│                           #   - Retry logic with exponential backoff
├── syncEngine.ts           # Sync engine
│                           #   - Incremental sync (SHA-256 hash-based)
│                           #   - Concurrent upload pool
│                           #   - Delete sync & rename handling
│                           #   - Multi-folder support
│                           #   - Download from Feishu with conflict check
├── fileWatcher.ts          # Local file monitoring
│                           #   - Create/modify/delete/rename events
│                           #   - Debounced sync trigger
├── feishuFolderBrowser.ts  # Interactive folder browser + recursive file tree modal
├── syncFolderConfig.ts     # Multi-folder config model & validation
├── ignoreFilter.ts         # .feisync-ignore.md parser (gitignore-compatible)
├── fileTypeUtils.ts        # File type detection for Feishu API
├── logger.ts               # Unified logging with namespace support
├── manifest.json           # Plugin metadata
├── styles.css              # Plugin UI styles
├── esbuild.config.js       # Build configuration
└── package.json

Feishu APIs Used

Authentication

APIMethodPurpose
/open-apis/auth/v3/tenant_access_token/internalPOSTApp-level access token
/open-apis/auth/v3/app_access_token/internalPOSTApp token for OAuth
/open-apis/authen/v1/authorizeGETOAuth authorization page
/open-apis/authen/v2/oauth/tokenPOSTExchange code for user token
/open-apis/authen/v1/user_infoGETGet authorized user info
/open-apis/authen/v1/oidc/access_tokenPOSTRefresh user access token

File & Folder Operations

APIMethodPurpose
/open-apis/drive/v1/filesGETList folder contents
/open-apis/drive/v1/files/create_folderPOSTCreate folder
/open-apis/drive/v1/files/{token}DELETEDelete file/folder
/open-apis/drive/v1/files/upload_allPOSTUpload file (≤20MB)
/open-apis/drive/v1/files/upload_preparePOSTChunked upload initialization
/open-apis/drive/v1/files/upload_blockPOSTUpload chunk (4MB)
/open-apis/drive/v1/files/upload_finishPOSTComplete chunked upload
/open-apis/drive/v1/files/{token}/downloadGETDownload cloud file
/open-apis/drive/v1/export_tasksPOSTCreate export task
/open-apis/drive/v1/export_tasks/{token}GETQuery export task result
/open-apis/drive/v1/import_tasksPOSTCreate import task
/open-apis/drive/v1/import_tasks/{token}GETQuery import task result
/open-apis/drive/v1/media/batch_get_tmp_download_urlPOSTGet batch download URLs
/open-apis/drive/v1/metas/batch_queryPOSTBatch query file metadata

Commands

CommandIDAction
Sync nowfeisync:syncTrigger one-way upload sync
Download from feishufeisync:downloadPull cloud files to local
View sync logfeisync:logOpen settings and view sync history

Ribbon icon (cloud-upload) provides a quick menu with the same actions plus Open settings.


Data Safety Notes

  • One-way sync overwrite risk: If a file is modified in Feishu and then you run "Sync now" from Obsidian, the cloud version will be deleted and replaced with the local (older) version. Always use "Download from feishu" first if you edited files in the cloud.
  • Delete sync: When enabled, deleting a local file will also delete its cloud counterpart. This can be disabled in settings.
  • Hash-based detection: The plugin uses SHA-256 hashes to detect changes. Files with identical content will be skipped even if their modification times differ.

Development

Setup

npm install
npm run build      # Production build (generates main.js)
npm run dev        # Watch mode — rebuilds on file changes

Code Quality Review (ESLint)

This plugin uses eslint-plugin-obsidianmd to enforce Obsidian-specific best practices and catch common mistakes.

Installation

Run this once per project to install the required development dependencies:

npm install --save-dev eslint eslint-plugin-obsidianmd @typescript-eslint/parser

Running the Linter

npm run lint        # Check for issues
npx eslint . --fix  # Automatically fix fixable issues

Configuration

The rules are configured in eslint.config.mjs at the project root. The config is already set up — you just need to install the packages above. Key settings:

  • TypeScript parsing: @typescript-eslint/parser is used with tsconfig.json to enable typed rule checks
  • Ignores: node_modules/, main.js, dist/, eslint.config.mjs, esbuild.config.js, and all *.json files are excluded
  • Recommended rules: Uses obsidianmd recommended rule set

Key ESLint Rules Explained

RuleWhat it catchesFix
prefer-active-window-timerssetTimeout instead of activeWindow.setTimeout — breaks on mobile--fix auto-fixes
no-deprecatedUses a deprecated Obsidian APIManual — switch to replacement API
no-unsupported-apiUses an API not available in ObsidianManual — remove or replace
ui/sentence-caseUI text not in sentence case (e.g., "APP ID" → "app ID")Manual — rewrite text
no-static-styles-assignmentSets .style.borderTop = ... directly in JSMove styles to CSS classes
prefer-instanceofinstanceof Array instead of Array.isArray()--fix auto-fixes
no-explicit-anyExplicit any type used — use unknown insteadManual
no-unsafe-assignmentAssigning an any value to a typed variableAdd type assertion
no-unsafe-returnReturning an any value from a typed functionAdd type assertion
no-unnecessary-type-assertionAssertion doesn't change type (e.g., x as T where x is already T)Remove assertion or use as unknown as T

Tip: If --fix removes a type assertion you need, convert it to as unknown as T (double assertion). This pattern is not removed by --fix.

Re-checking After Manual Edits

If you manually edit files after --fix, always run lint again to check for regressions:

npm run lint

License

MIT

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.