見出し画像

AutoHotkeyで外部からObsidianにメモを投げる

先日、Obsidianの導入プラグイン一覧の記事を書き、追加更新分に「FleetingNotes」というスマホ同期プラグインについて触れました。
スマホのアプリを通じて、メモを自分のObsidianに直接投げ込めるものです。

Twitter経由のこの方の記事にて知ったわけですが、実際に使用したところ私のスイートスポットから微妙にズレた物でして。ボール半個分というか。
でもスマホからポンと走り書きを放り込める点はありがたいので、スマホのTwitterアイコンの横に陣取るぐらいのインパクトはありました。
また、ChromeとFirefoxからもメモを送り込める拡張機能もあります。

スマホからはFleetingNotesを利用するとして、メインのデスクトップPC上からはブラウザと言わず、Obsidian外であればどこからでもメモを投げ込める機能をAutoHotkeyで作れないかと試行錯誤してみました。

私の理想は、Obsidian上の「QuickAdd : CaptureMemo」にメモを投げ込みたい。
ぷーおんさんの記事に触れてから、私のObsidian上ではQuickAdd主体に機能化が進められてます。


求められる機能

  • Obsidian以外のデスクトップ上で、ショートカットを発動できる。

  • 発動するとメモ入力欄をマウス付近に出現させる。

  • 内容は入力欄と送信ボタンのみ。Tabキーで交互に選択できる。

  • 送信するとObsidian上でQuickAddのCaptureMemoマクロを実行する。

  • Obsidian上では同ショートカットは発動しない(というかQuickAddと同じショートカットなので、そちらを発動させる)。

  • ブラウザ上でメモを取る場合は、閲覧しているWeb情報を付加する。

  • Obsidianが起動していない場合は、メモをmdファイルとしてObsidian所定ディレクトリに保存する。Obsidian起動時に読み込めるものとする。

  • メモを送信した後に、元のアプリをアクティブ状態にする。

可能ならObsidianが最小化されてる状態でも、そのままメモが送り込めれば最高だったのですが、調べた範囲では難しく、結果としてメモ投入時にObsidianをアクティブ化し「自動でショートカット実行→メモをペースト→OKボタン押す」という流れになりました。

外部からの操作はObsidianプラグイン「Advanced URI」も検討しましたが、QuickAdd開発者が示したサンプル内容でもObsidianがアクティブ状態のものでした。
またAutoHotkey側で非アクティブ状態でもコントロールできる方法も模索しましたが上手くいってません。

完成、か?

Chrome上で実行した様子

Obsidian上でメモが追加された様子

とりあえずできたもの公開

なんだかんだで、できたものを公開してみることにします。

私は自称「アミバ式プログラミング」と呼ぶ、いわゆる「当てずっぽ」「適当」「総当たり」な手法で、プログラムと呼ぶのもおこがましい代物をたまに自作しています。
従って以下のサンプルも見苦しい点あると思いますが、スルーしてください。

ちなみに AutoHotkey Ver.1 用です。

  • 2023/10/9 同じURL上でメモ連投の場合、2つ目以降のメモにはURL情報を付加しないよう変更

  • 2023/10/10 ESC処理の修正を行いました。

/*  ------------------------------------------------------------
; AHK Capture Memo for Obsidian  Ver.1.0.2
    - QuickAdd: Capture Memo「Alt+Q」をObsidian外で実現
    - マウス先に入力欄を表示。保存したらObsidian側に渡す
    - 入力欄表示時に再度「Alt+Q」「ESC」で窓消去
    - ブラウザの表示URLを自動的に補足する機能付き
    - Obsidian未起動時は所定フォルダにmdファイルを生成
    - 下記(1)(2)(3)(4)を個別設定
    - 2023/10/10 ESC処理の修正
*/
#Persistent
#SingleInstance, Force
#NoEnv
#HotkeyInterval, 2000
#MaxHotkeysPerInterval, 200
DetectHiddenWindows, On

SetTitleMatchMode,RegEx
CoordMode, Mouse, Screen
FileEncoding, UTF-8

; 設定ここから -------------------------------------------------

; (1) Obsidian未起動時に、mdファイル生成先 ※要設定
dirObsidian := "C:\<your-vault>\<your-favorite-folder>"

; (2) メモ欄のフォントサイズ、1行の文字数、メモ欄の行数目安
iFont := 14
iChar := 11
iFrmR := 5

; (3) AHKファイル直下にアイコンあれば読み込む
passIco := A_ScriptDir . "\ico\ico_ob_capturememo_256.ico"

; (4) 処理が早すぎて動かない場合はsleep時間を増やす(10001秒)
iSleep := 50

; 設定ここまで -------------------------------------------------

If ( !FileExist(dirObsidian) )
{
    Msgbox, Error: dirObsidian is not Exist!`n%dirObsidian%
    ExitApp
}

vAppname := "AHK Capture Memo for Obsidian"
Menu, Tray, Tip, %vAppname%
If ( FileExist(passIco) )
    Menu, Tray, Icon, %passIco%

Return

/* ------------------------------------------------------------
; ▼以下「AHK Capture Memo」アクティブ時
*/
#IfWinActive, AHK Capture Memo

; ESC:入力欄を終了
Esc::
	Gui, 99:Destroy
Exit

/* ------------------------------------------------------------
; ▼以下「Obsidian」非アクティブ時
; Alt+Q:メモ入力窓表示
*/
#IfWinNotActive, Obsidian v[\d\.]+$

!q::
    ; 初期化
    flgApp := 0
    vURL   :=
    vMemo  :=
    mTitle :=
    mID    :=
    mPID   :=

    ; すでに窓あれば終了して何もしない
    IfWinActive, %vAppname%
    {
        Gui, 99:Destroy
        Exit
    }

    MouseGetPos, mX, mY, mID
    WinGetTitle, mTitle, ahk_id %mID%
    WinGet, mPID, PID, ahk_id %mID%

    ClipSaved := ClipboardAll
    clipboard =

    ; - Microsoft​ Edge / ahk_class Chrome_WidgetWin_1
    ; - Google Chrome / ahk_class Chrome_WidgetWin_1
    ; — Mozilla Firefox / ahk_class MozillaWindowClass
    IfWinActive, Edge$
        flgApp := -23

    IfWinActive, Google Chrome$
        flgApp := -16

    IfWinActive, Mozilla Firefox$
        flgApp := -18

    If ( flgApp < 0 )
    {
        mTitle := SubStr(mTitle, 1, flgApp)
        Send, ^l ; Ctrl+L ブラウザ共通 URL欄選択
        sleep, %iSleep%
        Send, ^c
        ClipWait, 2
        If ( mTitle = "" )
            vURL := "(Web) [" . clipboard . "](" . clipboard . ")"
        Else
            vURL := "(Web) [" . mTitle . "](" . clipboard . ")"
    }

    iFrmW := Ceil( iFont * 1.6 * iChar )
    iFrmH := Ceil( iFont * 1.5 * iFrmR )
    iBtnY := iFrmH + 10
    iBtnH := Ceil( iFont * 1.5 ) + 4
    iGuiW := iFrmW + 10
    iGuiH := iFrmH + iBtnH + 15
    iGuiX := mX
    iGuiY := mY
    iRate := A_ScreenDPI / 96.0

    SysGet, iMcnt, MonitorCount
    Loop, %iMcnt%
    {
      SysGet, iM, Monitor, %A_Index%
      SysGet, iMwa, MonitorWorkArea, %A_Index%

        If ( mX - iMLeft >= 0 && mX - iMRight <= 0 && mY - iMTop >= 0 && mY - iMBottom <= 0 )
        {
            If ( mX + Ceil( iGuiW * iRate ) + 20 - iMwaRight >= 0 )
                iGuiX := iMwaRight - Ceil( iGuiW * iRate ) - 20

            If ( mY + Ceil( iGuiH * iRate ) + 100 - iMwaBottom >= 0 )
                iGuiY := iMwaBottom - Ceil( iGuiH * iRate ) - 100
        }
    }

    Gui, 99:Destroy
    Gui, 99:-MaximizeBox -MinimizeBox -Resize
    Gui, 99:Font, S%iFont% Norm c000000
    Gui, 99:Add, Edit, vvMemo x5 y5 w%iFrmW% h%iFrmH% , 
    Gui, 99:Add, Button, x5 y%iBtnY% w%iFrmW% h%iBtnH% Default gSubmitMemo , Capture Memo
    Gui, 99:Show, x%iGuiX% y%iGuiY% w%iGuiW% h%iGuiH%, %vAppname%

    Clipboard := ClipSaved
    ClipSaved = 
Return

/* ------------------------------------------------------------
; submitMemo メモ登録
*/
SubmitMemo:
    Gui, Submit

    ; ブラウザURLもなし&空欄の場合は処理なく終了
    If ( vMemo == "" && vURL == "" )
    {
        Gui, 99:Destroy
        Exit
    }

    ClipSaved := ClipboardAll
    clipboard =

    ; 前回分メモのURLと同じであればURL付加しない
    If ( vURL != vURLpre )
    {
        If ( vURL )
            clipboard := vURL

        If ( vURL && vMemo )
            clipboard .= "<br>"
    }

    If ( vMemo )
    {
        vMemo := RegExReplace(vMemo, "<", "\<")
        vMemo := RegExReplace(vMemo, ">", "\>")
        vMemo := RegExReplace(vMemo, "\n", "<br>")
        clipboard .= vMemo
        ClipWait, 2
    }

    IfWinExist, Obsidian v[\d\.]+$
    {
        WinActivate, - Obsidian v[\d\.]+$
        WinWaitActive, - Obsidian v[\d\.]+$
        sleep, %iSleep%
        Send, !q
        sleep, %iSleep%
        Send, ^v
        sleep, %iSleep%
        Send, {Tab}
        sleep, %iSleep%
        Send, {Enter}
        sleep, %iSleep%
    } 
    Else
    {
        vNote  :=

        If ( vMemo )
        {
            vNote  := vMemo
            vNote := RegExReplace(vNote, "<br>", "")
            vNote := RegExReplace(vNote, "\s", "")
            vNote := RegExReplace(vNote, " ", "")
            vNote := RegExReplace(vNote, "[\,\.\\/:;\*\?!<>\|\n\r]", "")
            vNote := Substr(vNote, 1, 20)
        }
        Else
            FormatTime, vNote, A_Now, yyyy-MM-dd_HH-mm-ss

        FormatTime, vStamp, A_Now, yyyy-MM-dd HH:mm

        ; Obsidian未起動時はmdファイルを生成
        FileAppend,
(
---
title: "%vNote%"
date created: "%vStamp%"
---
%clipboard%

), %dirObsidian%\%vNote%.md
        sleep, 1000
    }

    WinActivate, ahk_id %mID%

    vURLpre := vURL ; 前回分のURL情報を引き継ぎ

    flgApp := 0
    vURL   :=
    vMemo  :=
    mTitle :=
    vNote  :=

    Clipboard := ClipSaved
    ClipSaved = 

Return

; ScriptEnd

細かい補足

  • AHKスクリプト内の(1)~(4)の設定をまずご確認ください。

  • 私はQuickAdd: CaptureMemoをショートカット「Alt+Q」に設定しています。

  • このAHKについても同じショートカットです。
    これを変更したい場合は58行目と185行目辺りにある「!q」を変更してください。

  • Ctrlキーなら「^」、Shiftキー「+」、Altキー「!」、Winキー「#」です。
    「Ctrl+Shift+Q」なら「^+q」と記載します。

  • メモ欄で行う改行は、Obsidian側には「<br>」に変換されて改行されます。

  • メモ欄に含まれる「<」「>」はエスケープ処理されて、そのまま記録されます。

  • それ以外のエスケープ処理は行ってません。


この記事が気に入ったらサポートをしてみませんか?