Archlinuxでキーリング

i3をgnome-session 経由で動かす恩恵の一つ、 キーリングを使うことにしました。

これによって、SSH鍵やパスワードなどを何度も入力せずに済むようになります。

  1. gnome-keyringとseahorse(GUIツール)をインストールする
yaourt -S gnome-keyring seahorse
  1. seahorseを起動
  2. SSH鍵とSSH公開鍵を~/.ssh配下に置く
  3. seahorseのuser keyに出てくるのを確認する
  4. .zshenvに以下を書く if [ -n "$DESKTOP_SESSION" ];then eval $(gnome-keyring-daemon --start) export SSH_AUTH_SOCK fi

ためす

eval $(gnome-keyring-daemon --start)
ssh-add -l <-- これで鍵情報が出てくれば勝利

ついでにGITのHTTPS認証もいけるっぽいので試す

yaourt -S libgnome-keyring
cd /usr/share/git/credential/gnome-keyring
sudo make
git config --global credential.helper /usr/lib/git-core/git-credential-gnome-keyring

うまくいけた。

ちなみにバックアップは~/.local/share/keyrings配下をバックアップ、リストアは置くだけ

しかし、keepassxとgnome-keyringの共存方法を考えたいなぁ。 これができない場合gnome-keyringはSSH鍵の管理、WEB系のパスワード管理などはKeepassXを使うことになりそう。 まぁ、WindowsだろうとLinuxだろうと使えるKeepassXの方をメインにするのが正しいな。

ちなみに、僕は、keepassxのデータベースをencfsで暗号化して、googleドライブで全PCで共有している。

2017年の明けましておめでとうございます

あけましておめでとうございます

2017年がはじまりましたね。 viでブログを書く。という実験の為にはてなブログを立ち上げました。

遊んでたんじゃないもの。メモやログをサーバから外へ送信する仕組みを開発していたのですもの。 その途中の成果物ですもの。

しらない方の為に。

viとは

vi(ヴィーアイ)は、Emacsと共にUNIX環境で人気があるテキストエディタ。ビル・ジョイによって開発された。名の由来はVIsual editorないしVisual Interfaceとされる。後発のUnix系OSに搭載されているviは、上位互換のVimやnviであることが多い(viコマンドでvimやnviが起動する)。 by wikipedia

つまり、なにか。 よく、ハッキング映画とかにある。黒い画面に白か緑の文字。

WindowsやMacが隠しているOSの真の姿。 我が家に来た人ならわかりますね。きっと。 僕は常に、その画面でパソコンを操作しています。

その、マウスの居ない世界でテキスト編集するソフト、つまりエディタという種類のソフト。 それがVIと呼ばれています。

詳しく言えば、viではなく、nvimという少し進化したviを使っています。

使ったことがない人には世界一使い難いエディタ。 使い慣れた人にはこれがないとテキスト編集をしたくないと思わせるエディタ。 オープンソースの中でemacsと人気を2分するエディタだ。

なにが使いづらく、使いやすいか。 マウス操作はできず、一切の操作をキーボード・ショートカットで行う。 さらにショートカットすべてがカスタマイズ可能。 エディタに機能を追加、改造することも可能。

長所と短所が同じ説明になる。

で、僕はなにかというと、もうvimを使い始めて何年になるだろう。 たぶん、18年くらいかなぁ。真面目に使いはじめてからは14年くらい。 プログラムも、PDFも、なんでもこのエディタで処理してきた。

何度このエディタの設定を書き直しただろう。

おかげで、他のアプリで文章を書くのが苦痛でしょうがない。 だからブログサービスも避けてきた。(たぶん)

ところが!!

書けるじゃない。VIで書いて、保存時にhatenaのサーバに対して、送信する仕組みがあれば! 今回の調査内容プロ向け

というわけで、真っ黒な画面の中からお外の世界へ。

あけましておめでとう

今年もよろしくおねがいします。

追伸 死ぬほど忙しくしています。 もうしばらく引きこもります。

nvimを使ってブログを書く方法

概要

ブログをnvimから更新したいと思った。 対応しているのはhatebloだったのでアカウントを作成した。

依存しているパッケージは以下

  • mattn/webapi-vim
  • moznion/hateblo.vim
  • kana/vim-metarw

準備

dein.tomlに以下を追記する

[[plugins]]
repo = 'mattn/webapi-vim'
[[plugins]]
repo = 'moznion/hateblo.vim'
hook_add = '''
  let g:hateblo_vim = {
    \ 'user': '<ユーザ名>',
    \ 'api_key': '<APIキー>',
    \ 'api_endpoint': '<エントリーポイント>',
    \ 'WYSIWYG_mode': 0,
    \ 'always_yes': 1,
    \ 'edit_command': 'edit'
  \}
'''
[[plugins]]
repo = 'kana/vim-metarw'

編集フロー(1)

編集対象の記事を選ぶか、新規作成をする

:edit hateblo:

metarw content browser
hateblo:

New Entry <-- ここにカーソルを当てて[ENTER]を押すと新しいエントリを作成できる
nvimを使ってブログを書く方法 <-- ここで編集
マークダウン記法でブログを書く
はじまり
<< First Page

*:HatebloUpdate

現在の編集中のエントリをアップデートする

編集フロー(2)

:HatebloCreate

新規記事を作る

:HatebloList

編集対象の記事を選ぶ

*:HatebloUpdate

現在の編集中のエントリをアップデートする

はてなブログに自作VIMスクリプトで記事を書く

概要

VIMでメモをよく取る、このノリでブログを書きたい。 調べてみると、すでにはてなブログへ投稿するプラグインは存在する。

しかし、この仕組みは自作できるようになっていれば、他の開発でも役に立つはずと考え 自作してみることにした。ちなみに初vimscript。いちおうgithubで公開中hajimemat/hateblo.vim

行った調査は以下。

  1. vimとはてなブログはどうつながるか
    1. 通信仕様 AtomPub
    2. 認証仕様 WSSE
  2. vimでどう実現するか
    1. 通信ツール math/webapi-vim
  3. 参考にしたスクリプト
    1. 既存のツール moznion/hateblo.vim

わかったこと

webapi-vimを使うとAtomPubのAPIと通信ができる。 HTTPリクエストを行い、XMLレスポンスをパースし、vimscript内で配列として受け取れる。

こんなスクリプトを書いて確認

api_url = &quot;エントリポイント&quot;
user = &quot;はてなID&quot;
api_key = &quot;はてなAPIKEY
echo webapi#atom#getFeed(api_url, user,api_key)

作るもの

これから作るものの仕様を考えてみた

  • インターフェイスはuniteを使う
  • 通常のVIMの編集作業と変わるようなコマンド、ショートカットは使わない
  • VIMの保存 = サーバへの送信としたい
  • 削除機能は実装しない
  • 編集書式は常にMarkdownとする

コマンド

モード CMD -
ex :Unite hateblo-list 記事一覧を表示する+新規作成ができる
ex :w サーバにデータを保存する

書式

1行目にタグ、タイトルを記述する。 未公開の場合はタイトルの前にD:が表示される。 公開させるにはD:を削除して保存する。

# [:tag1,:tag2] タイトル
# [:tag1,:tag2] D:タイトル

セッティング

let g:hateblo_settings = {
\ 'user': 'はてなID',
\ 'blog': 'はてなブログID',
\ 'api_key': 'APIキー',
\ }

新規作成

Uniteで起動したリストからNewを選択。 プロンプトでタイトルとカテゴリが聞かれる

以下のように入力

TITLE: タイトル
CATEGORIES: cat,cat2
  • 追記[2017/01/01] ~~ 保存で送信はされるが、送信されたデータのentry_urlが取れなかったので、 一度保存したら、Uniteから再び開いて編集モードで送信しないと、 新規記事が何個もできてしまう。。。~~

webapi-vimのソースを読んだら、戻り値でentry_urlを返却していたので、 新規で開く保存 => 作成。 もう一度保存 => 上書き。 になるように処理フローを変更

function! webapi#atom#createEntry(uri, user, pass, entry, ...) abort
  let headdata = a:0 > 0 ? a:000[0] : {}
  let headdata["Content-Type"] = "application/x.atom+xml"
  let headdata["X-WSSE"] = s:createWsse(a:user, a:pass)
  let headdata["WWW-Authenticate"] = "WSSE profile=\"UsernameToken\""
  let res = webapi#http#post(a:uri, s:createXml(a:entry), headdata, "POST")
  let location = filter(res.header, 'v:val =~ "^Location:"')
  if len(location)
    return split(location[0], '\s*:\s\+')[1]
  endif
  return ''
endfunction

ソースコード一式

plugin/plugin.vim

let g:hateblo_draft_marker = &quot;D:&quot;
&quot; :wでサーバに送信する仕組み
augroup hateble_env
  autocmd!
  autocmd BufWriteCmd hateblo:* call hateblo#editor#save()
augroup END

autoload/hateblo/webapi.vim

&quot; WEBAPI用の設定などを取得する
&quot; エントリポイントを取得する
function! hateblo#webapi#getEndPoint()
  return g:hateblo['entry_point']
    \ .'/'
    \ .g:hateblo['user']
    \ .'/'
    \ .g:hateblo['blog']
endfunction

&quot; 記事用のエントリポイントを取得する
function! hateblo#webapi#getEntryEndPoint()
  return hateblo#webapi#getEndPoint()
    \ .'/atom/entry'
endfunction

autoload/hateblo/util.vim

&quot; 文字列操作などのユーティリティを格納する
function! hateblo#util#stripWhitespace(str)
  let l:str = substitute(a:str, '^\s\+', '', '')
  return substitute(l:str,'\s\+$', '', '')
endfunction

autoload/hateblo/entry.vim

&quot; エントリの処理

&quot; エントリ一覧を取得する
function! hateblo#entry#getEntries()
  return hateblo#entry#getEntriesWithURL(hateblo#webapi#getEntryEndPoint())
endfunction

function! hateblo#entry#getEntriesWithURL(api_url)
  let l:feed = webapi#atom#getFeed(a:api_url, g:hateblo['user'],g:hateblo['api_key'])
  let b:hateblo_entries = l:feed['entry']
  let b:hateblo_next_link = ''
  for l:link in l:feed['link']
    if l:link['rel'] == 'next'
      let b:hateblo_next_link = l:link['href']
    endif
  endfor
  return b:hateblo_entries
endfunction


function! hateblo#entry#getList()
  if !exists('b:hateblo_entries')
    call hateblo#entry#getEntries()
  endif

  let l:entries = b:hateblo_entries
  let l:list = []

  for l:entry in l:entries
    if l:entry['app:control']['app:draft'] == 'yes'
      let l:word = '[draft] '.l:entry['title']
    else
      let l:word = l:entry['title']
    endif

    call add(l:list, {
      \ 'word': l:word,
      \ 'source': 'hateblo-list',
      \ 'kind': 'file',
      \ 'action__action': 'edit_entry',
      \ 'action__entry_url': l:entry['link'][0]['href'],
      \ 'draft': l:entry['app:control']['app:draft']
      \})
  endfor

  if b:hateblo_next_link != ''
    call add(l:list, {
      \ 'word': '### NEXT PAGE ###',
      \ 'source': 'hateblo-list',
      \ 'kind': 'file',
      \ 'action__action': 'next_page',
      \ 'action__url': b:hateblo_next_link
      \})
  endif

  call add(l:list, {
    \ 'word': '### NEW ###',
    \ 'source': 'hateblo-list',
    \ 'kind': 'file',
    \ 'action__action': 'new',
    \})

  call add(l:list, {
    \ 'word': '### Reflesh ###',
    \ 'source': 'hateblo-list',
    \ 'kind': 'file',
    \ 'action__action': 'reflesh',
    \})
  return l:list
endfunction


function! hateblo#entry#getCategories(entry)
  let l:categories = []
  for l:category in a:entry['category']
    call add(l:categories, l:category['term'])
  endfor
  return l:categories
endfunction

autoload/hateblo/editor.vim

&quot; 編集
function! hateblo#editor#edit(entry_url)
  let l:entry = webapi#atom#getEntry(a:entry_url, g:hateblo_vim['user'],g:hateblo_vim['api_key'])
  let l:type = 'html'
  execute 'edit hateblo:'.fnameescape(l:entry['title'])
  execute &quot;:%d&quot;
  let b:entry_url = a:entry_url
  let b:entry_is_new = 0
  if l:entry['app:control']['app:draft'] == 'yes'
    let b:entry_is_draft = 1
  else
    let b:entry_is_draft = 0
  endif
  call append(0, hateblo#editor#buildFirstLine(l:entry['title'],hateblo#entry#getCategories(l:entry)))
  call append(2, split(l:entry['content'], '\n'))
  execute &quot;:2&quot;
  if l:entry['content.type']  ==# 'text/x-markdown'
    let l:type = 'markdown'
  elseif l:entry['content.type']  ==# 'text/x-hatena-syntax'
    let l:type = 'hatena'
  endif
  execute 'setlocal filetype='.l:type.'.hateblo'
endfunction

&quot; ファーストラインからタイトルとタグを取得する
function! hateblo#editor#parseFirstLine(line)
  let l:matched = matchlist(a:line, '#\s\+\[\(.\+\)\]\s\+\(.\+\)')
  if len(l:matched) < 1
    return []
  endif
  let l:categories = []
  for l:category in split(l:matched[1],',')
    call add(l:categories, hateblo#util#stripWhitespace(substitute(l:category, '^\s*:', '', '')))
  endfor
  let l:title = l:matched[2]
  return {
    \ 'categories': l:categories,
    \ 'title': l:title
    \}
endfunction

&quot; ファーストラインを作成する
function! hateblo#editor#buildFirstLine(title,categories)
  let l:categories = []
  for l:category in a:categories
    call add(l:categories, &quot;:&quot;.l:category)
  endfor
  if b:entry_is_draft == 1
    return &quot;# [&quot;.join(l:categories, ',').&quot;] &quot;.g:hateblo_draft_marker.a:title
  else
    return &quot;# [&quot;.join(l:categories, ',').&quot;] &quot;.a:title
  endif
endfunction

&quot; 保存
function! hateblo#editor#save()
  let l:data = hateblo#editor#parseFirstLine(getline(1))

  let l:categories = l:data['categories']
  let l:title = l:data['title']
  let l:contents = join(getline(3,'$'), &quot;\n&quot;)

  if l:title[0:len(g:hateblo_draft_marker)-1] ==# g:hateblo_draft_marker
    let l:title = l:title[len(g:hateblo_draft_marker):]
    let l:is_draft = 'yes'
  else
    let l:is_draft = 'no'
  endif

  if b:entry_is_new == 1
    call webapi#atom#createEntry(
      \ hateblo#webapi#getEntryEndPoint(),
      \ g:hateblo_vim['user'],
      \ g:hateblo_vim['api_key'],
      \ {
      \   'title': l:title,
      \   'content': l:contents,
      \   'content.type': 'text/plain',
      \   'content.mode': '',
      \   'app:control': {
      \     'app:draft': l:is_draft,
      \   },
      \   'category': l:categories
      \ })
    echom &quot;Created&quot;
    execute(&quot;:q!&quot;)
    call hateblo#entry#getEntries()
    Unite hateblo-list
  else
    call webapi#atom#updateEntry(
      \ b:entry_url,
      \ g:hateblo_vim['user'],
      \ g:hateblo_vim['api_key'],
      \ {
      \   'title': l:title,
      \   'content': l:contents,
      \   'content.type': 'text/plain',
      \   'content.mode': '',
      \   'app:control': {
      \     'app:draft': l:is_draft,
      \   },
      \   'category': l:categories
      \ })
    echom &quot;Saved&quot;
  endif
  
endfunction

function! hateblo#editor#create()
  let l:data = {}
  let l:data['title'] = input('TITLE: ')
  if len(l:data['title']) < 1
    echom 'Canceled'
    return 0
  endif
  let l:data['categories'] = split(input('CATEGORIES: '),',')
  execute 'tabe hateblo:'.fnameescape(l:data['title'])
  let b:entry_is_draft = 1
  call append(0, hateblo#editor#buildFirstLine(l:data['title'], l:data['categories']))
  &quot; let l:data = hateblo#editor#parseFirstLine(getline(1))
  &quot; if len(l:data) < 1
  &quot;   let l:data = {}
  &quot;   let l:data['title'] = input('TITLE: ')
  &quot;   if len(l:data['title']) < 1
  &quot;     echom 'Canceled'
  &quot;     return 0
  &quot;   endif
  &quot;   let l:data['categories'] = split(input('CATEGORIES: '),',')
  &quot;   execute 'tabe hateblo:'.fnameescape(l:data['title'])
  &quot;   call append(0, hateblo#editor#buildFirstLine(l:data['title'], l:data['categories']))
  &quot; else
  &quot;   execute 'tabe hateblo:'.fnameescape(l:data['title'])
  &quot;   call append(0, getline(0,'$'))
  &quot; endif

  let b:entry_is_new=1
  execute 'setlocal filetype=markdowm.hateblo'
endfunction

autoload/unite/sources/hateblo_list.vim

let s:save_cpo=&cpo
set cpo&vim
&quot; Unite用

let s:source = {
      \ 'name': 'hateblo-list',
      \ 'description': 'Entry list of hateblo',
      \ 'action_table': {
      \  'on_choose': {
      \   }
      \ },
      \ 'default_action': 'on_choose'
      \ }

function! s:source.gather_candidates(args,context)
  return hateblo#entry#getList()
endfunction

function! s:unite_action_on_choose(candidate)
  &quot;echo a:candidate.action__action
  if a:candidate.action__action == 'edit_entry'
    call hateblo#editor#edit(a:candidate['action__entry_url'])
  elseif a:candidate.action__action == 'next_page'
    &quot;echo a:candidate
    call hateblo#entry#getEntriesWithURL(a:candidate.action__url)
    Unite hateblo-list
  elseif a:candidate.action__action == 'reflesh'
    &quot;echo a:candidate
    call hateblo#entry#getEntries()
    Unite hateblo-list
  elseif a:candidate.action__action == 'new'
    call hateblo#editor#create()
  else
    echo 'not impl'
  endif
endfunction

function! s:source.action_table.on_choose.func(candidate)
    call s:unite_action_on_choose(a:candidate)
endfunction

function! unite#sources#hateblo_list#define()
  return s:source
endfunction

let &cpo = s:save_cpo
unlet s:save_cpo

Atompubに興味を持つ

Atompubとは?

はてなをnvimから上げていてふと疑問におもった。 このアップロード形式、atompubというリモートプロシージャらしきものを使っているのだが、なんなんだろうこれ。

というわけで調べてみた

AtomはWeb APIの次世代標準として期待されているものです。AtomはThe Atom Syndication Format及びThe Atom Publishing Protocolという二つの大きな仕様から構成されていますが、このうち,IETFで新しくRFCとなったAtom Publishing Protocol(AtomPub)を中心に解説します。

AtomPubはシンプルですが,その応用力の広さから,様々なWeb APIの標準プロトコルとして広く応用されることが期待されています。Webサービスのサーバ側,クライアント側,双方にこれから関わるであろうエンジニアの方に是非ご一読いただきたいと思います。 http://gihyo.jp/dev/feature/01/atompub

PHPでの実装例でも読んでみよう。

PHPでAtomPub(Atom Publishing Protocol)を使って、テキストファイルから はてなブログに投稿するスクリプトを書きました。http://sprint-life.hatenablog.com/entry/2014/02/27/211524

ふむ。

どうやらWSSE認証というものを使っているらしい。 が、ちょっと記事が過去すぎるので本家をのぞく

http://developer.hatena.ne.jp/ja/documents/blog/apis/atom

http://developer.hatena.ne.jp/ja/documents/auth/apis/wsse

OAuthとWSSE認証があるのか。

OAuthを使うのは面倒なのでWSSE認証でいいかなぁ。

WSSE認証についてわかったこと

  • ようするに、リクエスト毎にHTTPヘッダに認証情報を送りつける

形式のサンプル X-WSSE: UsernameToken Username="hatena", PasswordDigest="ZCNaK2jrXr4+zsCaYK/YLUxImZU=", Nonce="Uh95NQlviNpJQR1MmML+zq6pFxE=", Created="2005-01-18T03:20:15Z"

Username: ユーザ名 Nonce: トークン Created: ISO-8601形式の作成日時 PasswordDigest: Nonce, Created, APIキーを文字連結しSHA1ダイジェストをBase64した文字列

PHPでこれらを作るには

<?php
$nonce = sha1(md5(time()),true);
$created = date('c');
$username = "username"
$digest = base64_encode(sha1($nonce.$created.$key,true));
$header = sprintf('X-WSSE: UsernameToken Usernae="%s", PasswordDigest="%s", Nonce="%s", Created="%s"', $username, $digest, $nonce, $created);

こんな感じか。

はてなにあるように、エントリポイントへのアクセスを試す

<?php
$nonce = base64_encode(sha1(md5(time()),true));
$created = date('c');
$username = "はてなID";
$key = "はてなAPIキー";
$blog = "ブログID";

$digest = base64_encode(sha1($nonce.$created.$key,true));
$wsse = sprintf('X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"', $username, $digest, $nonce, $created);

# エントリポイントの取得
$entry_point = sprintf("https://blog.hatena.ne.jp/%s/%s/atom", $username, $blog);
# 記事リスト
$entry_point = sprintf("https://blog.hatena.ne.jp/%s/%s/atom/entry", $username, $blog);
# 記事
$entry_point = sprintf("https://blog.hatena.ne.jp/%s/%s/atom/entry/10328749687202422059", $username, $blog);

$ch = curl_init($entry_point);
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        $wsse
    ],
    #CURLOPT_HTTPGET => false,
    #CURLOPT_POST => true
]);
$result = curl_exec($ch);

echo $wsse;
var_Dump(curl_getinfo($ch));
echo $result;

XMLが戻ってくるので、こいつをパースするのか、なるほど。 これをVIMScriptで書くにはどうするのだろう。 あと、サーバ側での検証方法は多分、おんなじパラメタでdigest作って、一致すれば良いだけだと思う。

こいつをvimscriptで書くにはどうするのか

これを読んで見ることにする

https://github.com/moznion/hateblo.vim/blob/master/autoload/hateblo.vim

はじまり

はじまり

neovim を使ってブログが書けるならブログを書いてやろうじゃない。

こんなことを考えた年末。

いや、携帯が壊れるし、PCが壊れるし。

まったく、ふんだりけったりな状態で。

まぁ、いつものようにArchlinuxをインストールしなおして、 いろいろやり始めたわけですよ。

そうすると、前に同じ作業をしているのに忘れていることが多いわけで、 そりゃメモをとりたくなるわけですよ。

でも、Linux使いからすればVim以外で文章書くのは苦痛なわけで、 そういやはてぶVimでかけたなと思い出した。

じゃあ、これでいいや。 自分のWEBサイト作るときの参考にもなるし。

ちょっと、ここでこっそりブログをはじめてみます。