Cursorからnoteに記事を投稿する方法

コラム

昨日は、執筆ツールとしてCursorが使えるのかどうか、という観点から、文字数のカウントを表示させる方法について書きました。
本日も、Cursorを執筆に使うために必要な設定についてお話しします。
お題は、Cursorからnoteに記事を投稿する方法です。

わたしは今年の年初から1日も欠かさずにnoteの投稿をしています。わたしの執筆活動というと、小説だけではなく、noteの投稿も欠くことのできないものになっています。
そんななかで、執筆ツールをObsidianからCursorに集約させるためには、note執筆の効率性も上げていかなければなりません。Cursorから直接noteに投稿できるかどうかは、その観点からも重要と言えます。

ご存じの通り、noteは直接記事を投稿するためのAPIを公開していません。WordPressですとAPIが公開されていますので、直接投稿することができますが、noteではできないのです。
そこで、Cursorからnoteに間接的に記事を投稿する方法を考えてみました

難しい話ではありません。
まず、Cursorのテキストをクリップボードにコピーします。
その上で、noteのサイトを開きます。
これだけです。但し、これをいちいち手動でやるのは面倒なので、スクリプトでやってみました。
noteのサイトが開けば、手動で新規に記事を作成してテキストを貼り付けるだけです。

スクリプトはJavaScriptで作りました。次の通りです(このスクリプトを実行するためにはNode.jsというツールが必要です。わたしは公式サイトからダウンロードしたものを使用しました。この記事の一番最後に、インストール方法について記載しておきます)。


// note-publisher.js
const fs = require('fs').promises;
const path = require('path');
const { exec } = require('child_process');
const yargs = require('yargs');

// コマンドライン引数の処理
const argv = yargs
  .option('file', {
    alias: 'f',
    describe: '投稿するファイルのパス',
    type: 'string',
    demandOption: true
  })
  .option('clipboard', {
    alias: 'c',
    describe: 'クリップボードにコピーするかどうか',
    type: 'boolean',
    default: true
  })
  .option('open', {
    alias: 'o',
    describe: 'noteを自動で開くかどうか',
    type: 'boolean',
    default: true
  })
  .option('debug', {
    alias: 'd',
    describe: 'デバッグモード(クリップボード内容を表示)',
    type: 'boolean',
    default: false
  })
  .option('verify', {
    alias: 'v',
    describe: 'クリップボードの内容を検証',
    type: 'boolean',
    default: false
  })
  .help()
  .argv;

// クリップボードにコピーする関数(改善版)
async function copyToClipboard(text) {
  return new Promise((resolve, reject) => {
    const platform = process.platform;
    
    if (platform === 'darwin') {
      // macOS - より確実な方法
      const fs = require('fs');
      const tempFile = '/tmp/cursor-note-temp.txt';
      
      try {
        // 一時ファイルに書き込み
        fs.writeFileSync(tempFile, text, 'utf8');
        
        // pbcopyで読み込み
        exec(`pbcopy < "${tempFile}"`, (error) => {
          // 一時ファイルを削除
          try {
            fs.unlinkSync(tempFile);
          } catch (e) {
            // 削除エラーは無視
          }
          
          if (error) {
            reject(error);
          } else {
            resolve();
          }
        });
      } catch (error) {
        reject(error);
      }
    } else if (platform === 'win32') {
      // Windows
      const tempFile = require('os').tmpdir() + '\\cursor-note-temp.txt';
      const fs = require('fs');
      
      try {
        fs.writeFileSync(tempFile, text, 'utf8');
        exec(`type "${tempFile}" | clip`, (error) => {
          try {
            fs.unlinkSync(tempFile);
          } catch (e) {}
          
          if (error) {
            reject(error);
          } else {
            resolve();
          }
        });
      } catch (error) {
        reject(error);
      }
    } else {
      // Linux
      exec(`echo "${text.replace(/"/g, '\\"')}" | xclip -selection clipboard`, (error) => {
        if (error) {
          reject(error);
        } else {
          resolve();
        }
      });
    }
  });
}

// ブラウザでnoteを開く(改善版)
function openNote() {
  const platform = process.platform;
  let command;
  
  if (platform === 'win32') {
    command = 'start';
  } else if (platform === 'darwin') {
    command = 'open';
  } else {
    command = 'xdg-open';
  }
  
  console.log('🌐 note.comを開いています...');
  exec(`${command} "https://note.com/?popover=postings"`);
  
  // ページ読み込み待機のメッセージ
  console.log('⏰ ページの読み込みを待ってからペーストしてください(約3-5秒後)');
}

// Obsidianリンクやマークアップをクリーンアップ
function cleanContent(content) {
  return content
    // Obsidianリンク [[link]] や [[link|display]] を変換
    .replace(/!?\[\[([^|\]]+)(?:\|[^\]]+)?\]\]/g, "$1")
    // コメント削除
    .replace(//gs, "")
    // 余分な改行を整理
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

// Markdownファイルを処理
async function processMarkdownFile(filePath) {
  try {
    // ファイル存在チェック
    await fs.access(filePath);
    
    // ファイル内容を読み込み
    const content = await fs.readFile(filePath, 'utf8');
    
    // ファイル名からタイトルを取得(拡張子を除く)
    const title = path.basename(filePath, '.md');
    
    // コンテンツをクリーンアップ
    const cleanedContent = cleanContent(content);
    
    // タイトルと内容を結合
    const fullText = `${title}\n\n${cleanedContent}`;
    
    return {
      title,
      content: cleanedContent,
      fullText,
      success: true
    };
    
  } catch (error) {
    return {
      success: false,
      error: error.message
    };
  }
}

// メイン処理
async function main() {
  console.log('📝 note.com投稿準備...');
  
  // ファイルパスの取得
  let filePath = argv.file;
  
  // 相対パスを絶対パスに変換
  if (!path.isAbsolute(filePath)) {
    filePath = path.resolve(process.cwd(), filePath);
  }
  
  console.log(`📖 ファイル処理中: ${filePath}`);
  
  // ファイルを処理
  const result = await processMarkdownFile(filePath);
  
  if (!result.success) {
    console.error(`❌ エラー: ${result.error}`);
    process.exit(1);
  }
  
  console.log(`✅ 処理完了:`);
  console.log(`   タイトル: ${result.title}`);
  console.log(`   文字数: ${result.fullText.length}文字`);
  
  // クリップボードにコピー
  if (argv.clipboard) {
    try {
      await copyToClipboard(result.fullText);
      console.log('📋 クリップボードにコピーしました');
      
      // デバッグモード
      if (argv.debug) {
        console.log('=== デバッグ: コピーされた内容(最初の200文字) ===');
        console.log(result.fullText.substring(0, 200) + '...');
        console.log('=== デバッグ: コピーされた内容(最後の200文字) ===');
        console.log('...' + result.fullText.substring(result.fullText.length - 200));
      }
      
      // クリップボード検証
      if (argv.verify) {
        setTimeout(() => {
          exec('pbpaste', (error, stdout) => {
            if (!error) {
              const clipboardContent = stdout;
              if (clipboardContent === result.fullText) {
                console.log('✅ クリップボード検証: 正常にコピーされています');
              } else {
                console.log('❌ クリップボード検証: 内容が一致しません');
                console.log(`期待値の長さ: ${result.fullText.length}`);
                console.log(`実際の長さ: ${clipboardContent.length}`);
              }
            }
          });
        }, 500);
      }
      
    } catch (error) {
      console.warn('⚠️  クリップボードコピー失敗:', error.message);
      console.log('--- コピー用テキスト(手動コピー用) ---');
      console.log(result.fullText);
      console.log('--- ここまで ---');
    }
  }
  
  // noteを開く
  if (argv.open) {
    console.log('🌐 note.comを開いています...');
    openNote();
  }
  
  console.log('🎉 完了!noteで投稿してください');
}

// エラーハンドリング
process.on('uncaughtException', (error) => {
  console.error('予期しないエラー:', error.message);
  process.exit(1);
});

// スクリプト実行
if (require.main === module) {
  main().catch(console.error);
}

package.jsonはこちら。


{
  "scripts": {
    "note": "node note-publisher.js",
    "note-debug": "node note-publisher.js --debug --verify",
    "note-verify": "node note-publisher.js --verify"
  }
}

task.jsonはこちら。


{
    "label": "note.com投稿(検証付き)",
    "type": "shell",
    "command": "node",
    "args": [
        "/Users/ユーザー名/wordpress-publisher/note-publisher.js",
        "-f",
        "${file}",
        "--verify"
    ],
    "options": {
        "cwd": "/Users/ユーザー名/wordpress-publisher"
    }
}

keybindings.jsonはこちら。


{
    "key": "ctrl+shift+n",
    "command": "workbench.action.tasks.runTask",
    "args": "note.com投稿",
    "when": "editorTextFocus && resourceExtname == '.md'"
}

これで、

  1. Cursorで執筆
  2. 記事ファイルを選択
  3. キーボードショートカット実行
  • cmd+Shift+N → note.com投稿
  1. ObsidianはClaudeと連携するための原稿保管庫
  2. バックアップはObsidian SyncとGitHub

というフローができあがりました。
物書きとしての創作ワークフローが大幅に効率化されたと思います。これで、執筆に集中でき、投稿作業は瞬時に完了です。

Node.jpインストール方法

Windows

公式インストーラー

  1. 公式サイトからダウンロード
  • nodejs.org にアクセス
  • 「LTS」版をダウンロード(Long Term Support版、安定版)
  • Windows Installer (.msi) をダウンロード
  1. インストール実行
  • ダウンロードした .msi ファイルを実行
  • インストールウィザードに従って進行
  • 「Add to PATH」にチェックが入っていることを確認
  • 「Automatically install the necessary tools」にチェック

macOS

公式インストーラー

  1. nodejs.org から macOS Installer (.pkg) をダウンロード
  2. ダウンロードしたファイルを実行してインストール

package.jsonはこちら。

{
    "scripts": {
    "note": "node note-publisher.js",
    "note-debug": "node note-publisher.js --debug --verify",
    "note-verify": "node note-publisher.js --verify"
  }
}

task.jsonはこちら。

{
    "label": "note.com投稿(検証付き)",
    "type": "shell",
    "command": "node",
    "args": [
        "/Users/ユーザー名/wordpress-publisher/note-publisher.js",
        "-f",
        "${file}",
        "--verify"
    ],
    "options": {
        "cwd": "/Users/ユーザー名/wordpress-publisher"
    }
}

keybindings.jsonはこちら。

{
    "key": "ctrl+shift+n",
    "command": "workbench.action.tasks.runTask",
    "args": "note.com投稿",
    "when": "editorTextFocus && resourceExtname == '.md'"
}

これで、

  1. Cursorで執筆
  2. 記事ファイルを選択
  3. キーボードショートカット実行
  • cmd+Shift+N → note.com投稿
  1. ObsidianはClaudeと連携するための原稿保管庫
  2. バックアップはObsidian SyncとGitHub

というフローができあがりました。
物書きとしての創作ワークフローが大幅に効率化されたと思います。これで、執筆に集中でき、投稿作業は瞬時に完了です。

Node.jpインストール方法

Windows

公式インストーラー

  1. 公式サイトからダウンロード
  • nodejs.org にアクセス
  • 「LTS」版をダウンロード(Long Term Support版、安定版)
  • Windows Installer (.msi) をダウンロード
  1. インストール実行
  • ダウンロードした .msi ファイルを実行
  • インストールウィザードに従って進行
  • 「Add to PATH」にチェックが入っていることを確認
  • 「Automatically install the necessary tools」にチェック

macOS

公式インストーラー

  1. nodejs.org から macOS Installer (.pkg) をダウンロード
  2. ダウンロードしたファイルを実行してインストール

コメント

タイトルとURLをコピーしました