Clipyをnode.jsで触るライブラリを作りました

@econa77さんが2015年リリースしたクリップボード拡張Macアプリ[「Clipy」](https://clipy-app.com/)はこの２年間世界中の人気を集まりました。昨年、[とあるIssue](https://github.com/Clipy/Clipy/issues/240)と格闘している間、Clipyの中身を少しのぞきました。そこで得られた知見でこの **[Clipy Mate](https://github.com/jerrywdlee/clipy-mate-core)** プロジェクトをはじめました。

# GitHubレポジトリ
https://github.com/jerrywdlee/clipy-mate-core

# システム要項
- MacOS 10.10 and over
- Clipy v1.2.0 and over
- Node.js v8.9.0 ~ v10 ([Realm](https://www.npmjs.com/package/realm)はnode v12には対応していません)

# API
## インストール
```sh
npm i clipy-mate-core
```

## Import
### ES6
```js
const ClipyMate = require('clipy-mate-core');
const clipy = new ClipyMate();
```

### TypeScript
```ts
import ClipyMate from 'clipy-mate-core'
const clipy: ClipyMate = new ClipyMate();
```

## 全スニペットの取得

```js
// Read all Folders and Snippets
clipy.readSnippets().then((folders) => {
  console.log(folders);
  console.log(folders[0].snippets);
});
```

## ClipyがエクスポートしたXMLファイルをParse

```js
// Parse `snippet.xml`
const fs = require('fs');
clipy.parseXml(fs.readFileSync('./snippet.xml')).then((folders) => {
  console.log(folders);
  console.log(folders[0].snippets);
});
```

## フォルダとスニペットの作成

### フォルダの作成

```js
// Create or Update a folder
// it will update a folder has same `identifier` field
// or create a new folder if `identifier` is blank or not found
clipy.upsertFolder({ title: 'test folder' }).then(folder => {
  console.log(folder.identifier);
});
```

### スニペットの作成(フォルダ指定要)
```js
// Create or Update a snippet inside a folder
// it will update a snippet has same `identifier` field
// or create a new snippet if `identifier` is blank or not found
const folder = clipy.CPYFolder[0];
const folderId = folder.identifier;
clipy.upsertSnippet({ title: 'test snippet', content: 'test' }, folderId).then(snippet => {
  console.log(snippet.identifier);
});
```

## 削除
### フォルダ(と配下のスニペット)の削除
```js
// Destroy a specific folder (All snippets in this folder will also be destroyed)
const folder = clipy.CPYFolder[0];
const folderId = folder.identifier;
clipy.destroyFolder(snippetId).then(folder => {
  console.log(folder);
});
```

### スニペットの削除

```js
// Destroy a specific snippet
const snippet = clipy.CPYSnippet[0];
const snippetId = snippet.identifier;
clipy.destroySnippet(snippetId).then(snippet => {
  console.log(snippet);
});
```

### 全てを削除

```js
// [Danger!] Destroy all folders and snippets
clipy.clearAllSnippets().then();
```

## Realm Connectionの解除

```js
// After all
clipy.disconnect();
```

# ロードマップ
* コアライブラリ: **[clipy-mate-core](https://github.com/jerrywdlee/clipy-mate-core)** [v1.0.3]
* コンソルアプリ: **[clipy-mate-cli](https://github.com/jerrywdlee/clipy-mate-clihttps://github.com/jerrywdlee/clipy-mate-cli)** [v0.2.1]
* VS Code プラグイン: **clipy-mate-vscode** [WIP]

# 原理
Clipyは[Realm](https://realm.io/)というデータベースを使ってスニペットを管理しています。
該当データベースは下記の場所にストックされていることもわかりました。

```
$HOME/Library/Application Support/com.clipy-app.Clipy/default.realm
```

[Realm Browser](https://apps.apple.com/jp/app/realm-browser/id1007457278?mt=12)でこの `default.realm` を開くと、データ構造が確認できます。

<img width="812" alt="default.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/c0b6a407-8c5c-b433-de0f-7a5c3462ac1a.png">


# 既知のBug等

- Clipy v1.1.2以下を使う場合、Clipyが起動している間、`default.realm`の操作はできないため、必ずClipyを終了してから操作を行います。Clipy v1.2.0以上であれば、そんな問題が存在しません。
- スニペットを更新した場合、Clipyを再起動しないと「スニペットを編集」で反映されない問題があります。
- Clipyのデータを読み取るためのライブラリ[Realm](https://www.npmjs.com/package/realm)は、nodejs v12に対応していないため、node v10系がおすすめです。[参考](https://github.com/realm/realm-js/issues/2418)

# 参考
[クリップボード拡張Macアプリ「Clipy」を公開しました](https://qiita.com/econa77/items/1848bf3fdfb7127ca9b8)