PR通ってます!!!(跳ねるほど嬉しい)
随分と前のことになりますが、
PR対応をしていただいたため、Qiitaにvimから投稿できるようになりました!!!
やったね!!
[new] Todo list公開しときました、issueでできればいいのだがどうやらforkにはissueかけないようなので...
https://qiita.com/Cj-bc/items/c0bcb8e3e7de86c821b9
進捗報告(最新版)
なんか割と需要ありそうなので書いておきます。
現状下に貼っているgithubから入手できますが、細かい部分をケアしていないため不具合多数出る可能性もあります...
コマンド | 状態 | 備考 |
---|---|---|
:Qiita -l |
O | |
:Qiita -l のリストから項目選択 |
O | |
:Qiita -l <user> |
O | |
:Qiita (投稿) |
O | |
:Qiita -e (編集) |
O | |
:Qiita -d |
O | エラー時のメッセージがおかしいのでちょっと直す必要があります。 |
:Qiita XXXXXXXX |
O | 指定されたitem返すやつ |
:Unite qiita |
O | |
:CtrlPQiita |
O | |
API libraryとして | O | 詳しくは下記 |
API libraryとして
method | 状態 | docs | 備考 |
---|---|---|---|
qiita#createApi | O | O | |
qiita#createApiWithAuth | - | - | そもそもパスワード認証用のAPIがなくなっているようなので消滅 |
api.rate_limit | O | O | APIエンドポイントは消えているので、headerから取得する |
api.user | O | O | |
api.tag | O | O | 新たなエンドポイントが増えているが、描き直すべきか否か |
api.tags | O | O | |
api.post_item | O | O | |
user.items | O | O | |
user.item | O | O | |
user.stocks | O | O | |
tag.item | O | O | |
item.delete | O | O |
Qiita-vimとは
@mattn 氏作の、vim
からQiita
への投稿、閲覧、更新など色々できるプラグインです。
しかし最終更新日時が6年前、Qiita API v2
に対応していないため現在使うことができません。
しかしどうしてもvimからQiitaを使いたい(Qiita書くためだけにGUI環境に出てきたくない)ので、自分でなんとか対応させてみることにしました。
Qiita: mattn/Qiita.vim 作りました!
Github: mattn/qiita-vim
作業中のfork: Cj-bc/qiita-vim, feature/v2
branch
デバッグ方法
主には、:debug
と変数の定数置き換えによってデバッグしました。
:debug
についてはちょっと書いたのでこちらを: Cj-bc/vim scriptデバッグ方法
定数書き換えとは
勝手に呼んでいるだけです()
挙動が怪しいなと思ったところを定数に書き換え、同時に複数の不具合が起こらないようにしました。
今回は、固定できるところ、つまりself.url_name
(QiitaのURLにある名前。@
以下のやつ。)を自分の名前(Cj-bc
)に固定したり、
今回のケースでは省いてもそこまで問題がなさそうな処理を省いたりしました。
具体例ですがほんのちょっとだけ長くなるのでしまっておきます
例を置いておきます、コメントアウトされているのがテストのためにいじったコードです。
実際のデバッグ時にはそれらのコメントアウトを外し、既存の該当する部分と置き換えて走らせました。
...
try
let old_undolevels = &undolevels
silent %d _
redraw | echon 'Listing items... '
let items = a:api.user(a:user).items()
"call setline(1, split(join(["this is test", "second line"], "\n"))) " for debug
"call setline(1, split(join(map(items, 'v:val.uuid . ": " . v:val.title'), "\n"), "\n")) " for debug 2
call setline(1, split(join(map(items, 'v:val.uuid . ": " . webapi#html#decodeEntityReference(v:val.title)'), "\n"), "\n"))
catch
bw!
...
function! s:user.items()
"let res = webapi#json#decode(webapi#http#get(printf('https://qiita.com/api/v2/users/%s/items', 'Cj-bc'), {'token': self.token}).content)
let res = webapi#json#decode(webapi#http#get(printf('https://qiita.com/api/v2/users/%s/items', self.url_name), {'token': self.token}).content)
if type(res) == 4 && has_key(res, 'error')
throw res.error
endif
if type(res) != 3
throw 'invalid response'
endif
...
APIをv2に書き換える
これは至極単純で
:%s/v1/v2/g
で行いました。
一応、間違って置換されてないか確かめるために/v2
で確認もしました
2. うまく走らないのをなんとかする
大雑把ですがめっちゃ時間かかりました...
ちなみに、大掛かりにデバッグとかしてますが遠回りしただけだったりします(単純に仕様変更のせいだったりした)
2.1 Vim(call):E716: Key not present in Dictionary: uuid . ": " . v:val.title
これです
これは結局、APIの仕様変更に伴い削除された値を読みに行っていたのが問題でした。
具体的にはここ
call setline(1, split(join(map(items, 'v:val.uuid . ": " . webapi#html#decodeEntityReference(v:val.title)'), "\n"), "\n"))
github
使用されているAPI: /api/v2/users/:user_id/items
前後のコード読まないとなんともわからないとは思いますが、v:val.uuid
の部分が違っていました。
これ、uuid
は削除されてるんですね...v2
だと...
代わりにid
なるものがありましたので、置き換えたら動きました。やったね。
解決までの道のり
call setline(1, split(join(["this is test", "second line"], "\n")))
まずこれで置き換えてエラーが出ないことを確認。置き換える前の部分に問題があるなと推定(ちょっと雑すぎるかも?)
call setline(1, split(join(map(items, 'v:val.uuid . ": " . v:val.title)'), "\n"), "\n"))
webapi#html#decodeEntityReference
の部分は、URLエンコードを外すためのものらしく、今回の件とは関係なさそうなのでとりあえず外す。
この時点で文字列は表示されてなかったため、URLエンコードされたままでもまぁ問題ないと判断。
これでもエラーが出る& エラーが.uuid
で始まることから、uuid
を疑う。
ここでリファレンス見たらuuid
なるものが存在せず、代わりにid
なるものがあるので使って見た。
そしたら動いたので置換しておいた。
ここまで約1日かかった...エラーからすぐに見破れなかったのはちょっと悲しい
この後webapi#html#decodeEntityReference
ももとに戻してエラーが出ないことを確認、この部分についてはおそらくこれで完了
現状
:Qiita -l
コマンドを実行すると

2.2 E716: Key not present in Dictionary: url

こいつはなかなかトリッキーでした
トリッキーというか、:debug
をみるまではわかりづらかった...
これはwrite_item()
を実行している間に出たものなのですが、ここで使用されているAPIを調べると、引数にurl
パラメータは存在している。おかしい。
結局をいうと、実は認証がうまく行っていなかったのだということがわかった

とりあえずtokenを変えてみたりはしたがうまくいかない。この解決策に関しては後述する(未作成なので作成次第リンク貼ります)として、何よりこのエラーを消そうと思う(認証できなかった場合ははっきりそう明記させたい)。
つまり、エラーハンドリングをする。
エラー判別を直す
APIの仕様変更により、API側からのエラーの返り方に変更があったようです。
そのため、きちんとエラーが捕捉されずに放置されてしまったわけです...
なので、リファレンスをみながら該当箇所を直します
具体的には、エラーのレスポンスが以下のように変更されています:
{
"message": "Not found",
"type": "not_found"
}
元のコードではerror
が含まれる場合をエラーとみなしていたので、すり抜けていたわけですね。
直します
- if has_key(res, 'error')
+ if has_key(res, 'type')
- throw res.error
+ throw res.type
endif
捕捉するのはtype
でいいのか悩んだのですが、他にtype
が返ってくることはなさそうだったのでこれにしました。
問題ないといいな...?
脇道に逸れて例外捕捉
本題とは逸れますがこれも改善の一歩ということで...(というか、きちんとハンドリングしてくれた方がデバッグも楽)
さて、一箇所だけtry-catch使っている部分がありますので
try
...
catch
bw!
redraw
echohl ErrorMsg | echomsg v:exception | echohl None
return
finally
...
endtry
ここを参考にして、例外捕捉を行いました
具体的な設置箇所:
箇所 | いつ |
---|---|
qiita#login() |
qiita#createApiWithAuth 実行時 |
write_item() |
api.post_item 実行時 |
list_user_items() |
a:api.user(a:user).items() 周り実行時 |
qiita#Qiita() |
qiita#login 実行時 |
この結果、きちんとエラーを表示できるようになりました。
~/.qiita-vim
(設定ファイル)が存在するけどtokenが違う/ない
などで認証できない場合:
~/.qiita-vim
がなくて、プロンプトから入力してるけど認証できない場合:
もうちょっとわかりやすくできそうだけど...今はとりあえずこんなもんでいいかなと(エラー情報どのくらい表示するかは悩む)
一つ目の方のエラーメッセージは、「tokenが違います」的なのを出せたら良いかもしれない。
認証できない問題
やっと解消できました...
これもまた、APIの仕様変更によるものでした。
APIバージョン | token送信方法 |
---|---|
v1 | Jsonでbody の中身として送信 |
v2 | HeaderにAuthorization: Bearer <token> として埋め込み |
これを直して一件落着...かと思いきやまだダメだった!
とりあえず認証は通るようになり、代わりに2.3のエラーが出るようになりました
現状
コマンド | 状態 | 備考 |
---|---|---|
:Qiita -l |
O | |
:Qiita -l のリストから項目選択 |
O | |
:Qiita -l <user> |
X | 何故か自分のものが表示されてしまう |
:Qiita (投稿) |
△ | 作業中 |
:Qiita -e (編集) |
X | |
:Qiita -d |
X | |
:Qiita XXXXXXXX |
O | 指定されたitem返すやつ |
:Unite qiita |
X | |
:CtrlPQiita |
X | |
qiita-interfaceとして | X | まだきちんと調べてない、コマンド系終わったら対応 |
2.3 bad_request
/invalid_json
ま〜、ヒントの少ない問題だこと...
APIの認証は無事に通るようになりましたが、今度はJSON書式がなっていないのだそうです。
与えられた情報はこれだけ
{
"message": "invalid_json",
"type": "invalid_json"
}
{
"message": "bad_request",
"type": "bad_request"
}
まー情報の少ないこと...でも逆に楽しくなってきてガシガシやってました。
先に結論
body
のprivate
キーに与える値がbool値でなかったことが問題だったようです。
これは、vimの仕様変更に伴うものなのか、それともQiita側の仕様変更に伴うものなのか...わかりませんがとりあえず直しました。
- \ "private": 0,
+ \ "private": v:false,
そして、vim8から搭載された、json_encode
とjson_decode
をwebapi#json#encode
/webapi#json#decode
の代わりに採用しました。
(そうでないとwebapi#json#encode
/webapi#json#decode
のアップデートに時間がかかってしまうため。)
解決までの道のり
まず、怪しいのはbody
部分ぽかったので、何か必要な値が足りなかったり、不要なものが入ってたりしないか?とリファレンス見ながら探してました。
が、特になし。(tweet
やgist
を追加してみたりしたが変化なかった)
上記作業中に、private
の値について疑問を持ちました。
リファレンスによればbool値なのに何故0
で指定されていたからです。
まぁおそらく、0
と1
(もしくは0
とそれ以外)などで真偽を書いているのだろうと推測したものの気になって調べました。
すると、vim8になってから真偽値が追加されたとの情報が!!
引用:
Vim script には、true/false などの bool 値や、null などの値は存在しませんでした。
このままだと JSON と相互に変換するのに支障が出るため、新しくこれらを表す値が追加されました。v:false
v:true
v:null
v:none
これらによって、bool 値や null を含む JSON も正しく相互変換されます。
これにならい、v:false
/v:true
に書き換えて動かすと、
うご...きそうなんだけど動かない...
今度はinvalid_json
って言われる。
どうやらJSONの書式か何かがおかしいようです。
...お?そういえばさっき何か見たな??
これだ
引用:
Vim script には、true/false などの bool 値や、null などの値は存在しませんでした。
このままだと JSON と相互に変換するのに支障が出るため、新しくこれらを表す値が追加されました。
Qiita-vim
では、JSONのエンコード・デコードにwebapi-vim
の関数が使われていました。
しかしそれらはvim8以前の更新が最後となっているため、vim8で追加されたv:false
/v:true
などに対応していませんでした。そのためうまくエンコードしてくれなかったわけです。
webapi
に手直しをするか...?本来はそれもしたいけど、あまり割いている時間はなかったので、先ほどのページで見かけたjson_encode
/json_decode
を使用することにしました。
すると...
Qiita-vim v2対応進捗です
— Cj-bc_sd.sh (@Cj_bc_sd) October 14, 2018
現状できることは
:Qiita で投稿
:Qiita -l で自分の投稿の閲覧
何気に更新している、これに関するQiita記事https://t.co/KzleqQywIq pic.twitter.com/yzEs4D0rsT
綺麗にできた!!!
現状
コマンド | 状態 | 備考 |
---|---|---|
:Qiita -l |
O | |
:Qiita -l のリストから項目選択 |
O | |
:Qiita -l <user> |
X | 何故か自分のものが表示されてしまう |
:Qiita (投稿) |
O | |
:Qiita -e (編集) |
X | |
:Qiita -d |
X | |
:Qiita XXXXXXXX |
O | 指定されたitem返すやつ |
:Unite qiita |
X | |
:CtrlPQiita |
X | |
qiita-interfaceとして | X | まだきちんと調べてない、コマンド系終わったら対応 |
2.4 :Qiita -l <username>
で自分の投稿一覧が表示されてしまう
謎だったものの一つ...
何故か、:Qiita -l <username>
としても:Qiita -l
と同じ動作になってしまっていました。
わかってしまったら簡単だったんですがね...
解決までの道のり
とりあえず、qiita#Qiita
からコードを辿り、その後:debug Qiita -l <username>
でステップ実行しながら確認しました。
初めは、qiita#Qiita
から<username>
がうまく伝えられてないのかな?と思ったのですが問題ありませんでした。
その後もlist_user_items
内でも問題はなく...
若干書くのがめんどくなってきた(単純作業でしかなかったので)ので結論を書きます。
....まさかの自分のミス....
前、問題解決する為に付け足したコードが悪さをしたようです...
具体的には、これまではurl_name
というパラメータが存在していたのにv2
にてなくなっていたことが原因でした。
url_name
はつまり、@cj-bc
などの名前に当たるわけで、それをtokenの持ち主のものに設定するように書いてしまっていた...(元はそもそも存在していなかった)
なので、これをきちんと正しいユーザーの情報になるように変更を入れました。
(テスト用にmattnさんの名前で調べてみました。)
2.5 :Qiita -e
が動かない
:Qiita -e
を実行すると
write_item: updating item: Vim(let):E474: Invalid argument
こんな感じのこと言われます。(ログのプレフィックスは適当)
先に結論
編集するためのAPIに渡すパラメータが不要に多かったのと、HTTP methodが違っていたのが問題でした。
そのため、リファレンスに合わせてパラメータを減らし、HTTP methodの修正をしました。
+ let content = {'body': self['body'],
+ \ 'id': self['id'],
+ \ 'title': self['title'],
+ \ 'tags': self['tags'],
+ \ 'private': v:false,
- let res = json_decode(webapi#http#post(printf('https://qiita.com/api/v2/items/%s', self['id']), json_encode(self), {'Content-Type': 'application/json', 'X-HTTP-Method-Override': 'PUT', 'Authorization': 'Bearer ' . self.token}).content)
+ let res = json_decode(webapi#http#post(printf('https://qiita.com/api/v2/items/%s', self['id']), json_encode(content), {'Content-Type': 'application/json', 'X-HTTP-Method-Override': 'PATCH', 'Authorization': 'Bearer ' . self.token}).content)
解決までの道のり
invalid argument
と言われているのでとりあえずリファレンスを参照します。
するとHTTP methodがPATCH
である事がわかったので変更。
HTTP methodが規定されているのはここ
let res = json_decode(webapi#http#post(printf('https://qiita.com/api/v2/items/%s', self['id']), json_encode(self), {'Content-Type': 'application/json', 'X-HTTP-Method-Override': 'PUT', 'Authorization': 'Bearer ' . self.token}).content)
'X-HTTP-Method-Override': 'PUT'
として指定されているので、これをPATCH
に変更します。
webapi#http#post
の仕様上、第四引数にHTTP methodを指定することもできますが、元のスタイルに合わせておきました。
しかしこれでも動かない...
invalid argument
なので:debug Qiita -e
でトレースして見ると、どうやらjson_encode
でエラーを吐かれているらしい。
json_encode(self)
となっているが、self
を覗いてみると、リファレンスをみる限り不要な情報も同時に投げているようでした。
そこに意図があったのか、(それともただ単に綺麗なコードだからか)わかりませんが、渡す内容を削ってみることにしました。
let content = {'body': self['body'],
\ 'id': self['id'],
\ 'title': self['title'],
\ 'tags': self['tags'],
\ 'private': v:false,
\}
このように定義し、json_encode(self)
の代わりにjson_encode(content)
として動かすと...
[後でここに画像が多分きます。wifiないのでおうち帰ってから。]
うまく動いた!!
元々は、APIに渡す情報が過多だったのかなと思い、必要最低限と思われるところまで削ったわけですが、
おそらく情報過多なわけではなかったと思います(そもそもAPIを叩きに行っていなかったため)
結果オーライなのですが、きちんと検証した方が良いかも...?
ということで...
現状
コマンド | 状態 | 備考 |
---|---|---|
:Qiita -l |
O | |
:Qiita -l のリストから項目選択 |
O | |
:Qiita -l <user> |
O | |
:Qiita (投稿) |
O | |
:Qiita -e (編集) |
O | |
:Qiita -d |
X | |
:Qiita XXXXXXXX |
O | 指定されたitem返すやつ |
:Unite qiita |
X | |
:CtrlPQiita |
X | |
qiita-interfaceとして | X | まだきちんと調べてない、コマンド系終わったら対応 |
2.6 :Qiita -d
の修正
これは割と簡単だった。ってかほんと単純に直ったのであまり書くことありません。
徐々に記事更新がだるくなってきており(主にはリアルタイムで書いていないのとSS撮り忘れていて資料がなかったりするため)、
若干雑になってきてるけどゴメンなさい(雑でも最後まで書きあげる方を優先したい)
まず、HTTP methodの指定方法がよくなかったので直します。
webapi#http#post
への引数内で、method指定する引数が{X-HTTP-Method-Override: 'DELETE}
となっていた部分を修正しました。
二通り方法がある(methodへの引数としてDELETE
のみを指定するor先ほどの辞書をheaderのリストに追加する)のですが、
他の箇所との一貫性を考え後者にしました。
エラー捕捉の修正
前は、リクエストの戻り値のheader
にHTTP status codeがあったようですが、現状だとstatus
として分離されていたので、それに合わせて書き直しました。
とりあえずそんなところです。
現状
コマンド | 状態 | 備考 |
---|---|---|
:Qiita -l |
O | |
:Qiita -l のリストから項目選択 |
O | |
:Qiita -l <user> |
O | |
:Qiita (投稿) |
O | |
:Qiita -e (編集) |
O | |
:Qiita -d |
O | エラー時のメッセージがおかしいのでちょっと直す必要があります。 |
:Qiita XXXXXXXX |
O | 指定されたitem返すやつ |
:Unite qiita |
X | |
:CtrlPQiita |
X | |
qiita-interfaceとして | X | まだきちんと調べてない、コマンド系終わったら対応 |
2.7 Unite
/CtrlP
対応
これは単純にuuid
をid
に変更しただけで動きました。
ただ、自分は普段どちらも使っていないため、見つかってない不具合がまだある可能性があります...
PRのコードレビューの時に見つかってほしい...(投げやりorz)
現状
コマンド | 状態 | 備考 |
---|---|---|
:Qiita -l |
O | |
:Qiita -l のリストから項目選択 |
O | |
:Qiita -l <user> |
O | |
:Qiita (投稿) |
O | |
:Qiita -e (編集) |
O | |
:Qiita -d |
O | エラー時のメッセージがおかしいのでちょっと直す必要があります。 |
:Qiita XXXXXXXX |
O | 指定されたitem返すやつ |
:Unite qiita |
O | |
:CtrlPQiita |
O | |
qiita-interfaceとして | X | まだきちんと調べてない、コマンド系終わったら対応 |
この先も随時更新しまふ