| 1 | scriptencoding utf-8 |
|---|
| 2 | " hatena.vim |
|---|
| 3 | " Author: motemen <motemen@gmail.com> |
|---|
| 4 | " Version: 20070830 |
|---|
| 5 | " vim: set ts=4 sw=4: |
|---|
| 6 | |
|---|
| 7 | " =========================== |
|---|
| 8 | " インストール {{{ |
|---|
| 9 | |
|---|
| 10 | " - hatena/plugin/hatena.vim |
|---|
| 11 | " - hatena/syntax/hatena.vim |
|---|
| 12 | " - hatena/cookies (空ディレクトリ) |
|---|
| 13 | " |
|---|
| 14 | " 以上のファイル/ディレクトリを適当な場所に置いて、パスを通して下さい。 |
|---|
| 15 | " |
|---|
| 16 | " 例 (.vimrc): |
|---|
| 17 | " > set runtimepath+=$VIM/hatena |
|---|
| 18 | |
|---|
| 19 | " }}} |
|---|
| 20 | " =========================== |
|---|
| 21 | |
|---|
| 22 | " =========================== |
|---|
| 23 | " 使用方法 {{{ |
|---|
| 24 | |
|---|
| 25 | " > :HatenaUser [グループ名:]ユーザ名 |
|---|
| 26 | " もしくは |
|---|
| 27 | " > :let g:hatena_user = '[グループ名:]ユーザ名' |
|---|
| 28 | " としてユーザ名を設定し、 |
|---|
| 29 | " > :HatenaEdit [[[YYYY]MM]DD] |
|---|
| 30 | " で編集バッファが開きます。日記を書いたら :w で送信します。 |
|---|
| 31 | |
|---|
| 32 | " }}} |
|---|
| 33 | " =========================== |
|---|
| 34 | |
|---|
| 35 | " =========================== |
|---|
| 36 | " コマンド {{{ |
|---|
| 37 | |
|---|
| 38 | " はてなにログインし、日記を編集する |
|---|
| 39 | " Usage: |
|---|
| 40 | " :HatenaEdit [[[YYYY]MM]DD] |
|---|
| 41 | " 日付の形式は YYYYMMDD, YYYY/MM/DD, YYYY-MM-DD |
|---|
| 42 | command! -nargs=? HatenaEdit call <SID>HatenaEdit(<args>) |
|---|
| 43 | |
|---|
| 44 | " :HatenaEdit で開いたバッファの内容をはてなに送信し、日記を更新する |
|---|
| 45 | " Usage: |
|---|
| 46 | " :HatenaUpdate [title_of_the_day] |
|---|
| 47 | " title_of_the_day を指定しない場合は既に設定されているタイトルが使われる |
|---|
| 48 | "command! -nargs=? HatenaUpdate call <SID>HatenaUpdate(<args>) |
|---|
| 49 | |
|---|
| 50 | " :HatenaUpdate と一緒だけど、`ちょっとした更新' にする |
|---|
| 51 | "command! -nargs=? HatenaUpdateTrivial let b:trivial=1 | call <SID>HatenaUpdate(<args>) |
|---|
| 52 | |
|---|
| 53 | " はてなのユーザを切り換える |
|---|
| 54 | " 指定しなかった場合は表示する |
|---|
| 55 | " Usage: |
|---|
| 56 | " :HatenaUser [username] |
|---|
| 57 | command! -nargs=? -complete=customlist,HatenaEnumUsers HatenaUser if strlen('<args>') | let g:hatena_user='<args>' | else | echo g:hatena_user | endif |
|---|
| 58 | |
|---|
| 59 | nnoremap <Leader>he :HatenaEdit<CR> |
|---|
| 60 | " }}} |
|---|
| 61 | " =========================== |
|---|
| 62 | |
|---|
| 63 | " =========================== |
|---|
| 64 | " スクリプト設定 {{{ |
|---|
| 65 | |
|---|
| 66 | " はてなのユーザID |
|---|
| 67 | if !exists('g:hatena_user') |
|---|
| 68 | let g:hatena_user = '' |
|---|
| 69 | endif |
|---|
| 70 | |
|---|
| 71 | " サブアカなども含めたIDのリスト |
|---|
| 72 | if !exists('g:hatena_users') |
|---|
| 73 | if g:hatena_user != '' |
|---|
| 74 | let g:hatena_users = [g:hatena_user] |
|---|
| 75 | else |
|---|
| 76 | let g:hatena_users = [] |
|---|
| 77 | endif |
|---|
| 78 | endif |
|---|
| 79 | |
|---|
| 80 | " クッキーを保存しておくか? (1: 保存しておく 0: Vim終了時に削除) |
|---|
| 81 | if !exists('g:hatena_hold_cookie') |
|---|
| 82 | let g:hatena_hold_cookie = 1 |
|---|
| 83 | endif |
|---|
| 84 | |
|---|
| 85 | " スクリプトのベースディレクトリ (クッキーの保存に使われるだけ) |
|---|
| 86 | if !exists('g:hatena_base_dir') |
|---|
| 87 | let g:hatena_base_dir = substitute(expand('<sfile>:p:h'), '[/\\]plugin$', '', '') |
|---|
| 88 | endif |
|---|
| 89 | |
|---|
| 90 | " 常に `ちょっとした更新' にする? (1: 常にちょっとした更新) |
|---|
| 91 | if !exists('g:hatena_always_trivial') |
|---|
| 92 | let g:hatena_always_trivial = 0 |
|---|
| 93 | endif |
|---|
| 94 | |
|---|
| 95 | let g:hatena_syntax_html = 1 |
|---|
| 96 | |
|---|
| 97 | if !g:hatena_hold_cookie |
|---|
| 98 | autocmd VimLeave * call delete(b:hatena_login_info[2]) |
|---|
| 99 | endif |
|---|
| 100 | |
|---|
| 101 | " :HatenaEdit で編集バッファを開くコマンド |
|---|
| 102 | let g:hatena_edit_command = 'edit!' |
|---|
| 103 | |
|---|
| 104 | let s:curl_cmd = 'curl -k' |
|---|
| 105 | let s:hatena_login_url = 'https://www.hatena.ne.jp/login' |
|---|
| 106 | let s:hatena_base_url = 'http://d.hatena.ne.jp/' |
|---|
| 107 | let s:hatena_group_base_url = 'http://%s.g.hatena.ne.jp/' |
|---|
| 108 | |
|---|
| 109 | " }}} |
|---|
| 110 | " =========================== |
|---|
| 111 | |
|---|
| 112 | " =========================== |
|---|
| 113 | " スクリプト本体 {{{ |
|---|
| 114 | |
|---|
| 115 | " はてなにログインする |
|---|
| 116 | " ユーザ名は g:hatena_user から取得。存在しなければユーザに尋ねる。 |
|---|
| 117 | " クッキーでログインを試み、ダメならパスワードでログインする。 |
|---|
| 118 | " |
|---|
| 119 | " ログインに成功: [ベースURL, ユーザID, クッキーファイル] を返す。 |
|---|
| 120 | " ログインに失敗: 空リストを返す。 |
|---|
| 121 | function! s:HatenaLogin() |
|---|
| 122 | if !strlen(g:hatena_user) |
|---|
| 123 | let hatena_user = input('はてなユーザID(user/group:user): ', '', 'customlist,HatenaEnumUsers') |
|---|
| 124 | else |
|---|
| 125 | let hatena_user = g:hatena_user |
|---|
| 126 | endif |
|---|
| 127 | |
|---|
| 128 | let [base_url, user] = s:GetBaseURLAndUser(hatena_user) |
|---|
| 129 | |
|---|
| 130 | let tmpfile = tempname() |
|---|
| 131 | |
|---|
| 132 | " クッキーを保存するファイル |
|---|
| 133 | if has('win32') |
|---|
| 134 | let cookie_file = g:hatena_base_dir . '\cookies\' . user |
|---|
| 135 | else |
|---|
| 136 | let cookie_file = g:hatena_base_dir . '/cookies/' . user |
|---|
| 137 | endif |
|---|
| 138 | |
|---|
| 139 | " クッキーがある場合はクッキーでログインを試みる |
|---|
| 140 | if filereadable(cookie_file) |
|---|
| 141 | let reply_header = system(s:curl_cmd . ' ' . base_url . user . '/edit -b "' . cookie_file . '" -D - -o ' . tmpfile) |
|---|
| 142 | if reply_header =~? 'Location: https:' |
|---|
| 143 | " httpsなグループへ |
|---|
| 144 | let base_url = substitute(base_url, '^http', 'https', '') |
|---|
| 145 | let reply_header = system(s:curl_cmd . ' ' . base_url . user . '/edit -b "' . cookie_file . '" -D - -o ' . tmpfile) |
|---|
| 146 | endif |
|---|
| 147 | if reply_header !~? 'Location:' |
|---|
| 148 | echo 'ログインしてます' |
|---|
| 149 | return [base_url, user, cookie_file] |
|---|
| 150 | else |
|---|
| 151 | call delete(cookie_file) |
|---|
| 152 | endif |
|---|
| 153 | endif |
|---|
| 154 | |
|---|
| 155 | " パスワードでログイン |
|---|
| 156 | let password = inputsecret('Password: ') |
|---|
| 157 | |
|---|
| 158 | if !len(password) |
|---|
| 159 | echo 'キャンセルしました' |
|---|
| 160 | return [] |
|---|
| 161 | endif |
|---|
| 162 | |
|---|
| 163 | let content = system(s:curl_cmd . ' ' . s:hatena_login_url . ' -d name=' . user . ' -d password=' . password . ' -d mode=enter -c "' . cookie_file . '"') |
|---|
| 164 | |
|---|
| 165 | call delete(tmpfile) |
|---|
| 166 | |
|---|
| 167 | if content !~ '<div [^>]*class="error-message"' |
|---|
| 168 | echo 'ログインしました' |
|---|
| 169 | return [base_url, user, cookie_file] |
|---|
| 170 | else |
|---|
| 171 | echoerr 'ログインに失敗しました' |
|---|
| 172 | return [] |
|---|
| 173 | endif |
|---|
| 174 | endfunction |
|---|
| 175 | |
|---|
| 176 | function! s:HatenaEdit(...) " 編集する |
|---|
| 177 | " ログイン |
|---|
| 178 | if !exists('b:hatena_login_info') |
|---|
| 179 | let hatena_login_info = s:HatenaLogin() |
|---|
| 180 | if !len(hatena_login_info) |
|---|
| 181 | return |
|---|
| 182 | endif |
|---|
| 183 | else |
|---|
| 184 | let hatena_login_info = b:hatena_login_info |
|---|
| 185 | endif |
|---|
| 186 | |
|---|
| 187 | let [base_url, user, cookie_file] = hatena_login_info |
|---|
| 188 | |
|---|
| 189 | " 編集する日付を取得 |
|---|
| 190 | if a:0 > 0 |
|---|
| 191 | let date = a:1 |
|---|
| 192 | else |
|---|
| 193 | let date = input('Date: ', strftime('%Y%m%d')) |
|---|
| 194 | endif |
|---|
| 195 | |
|---|
| 196 | " 20051124, 2005-11-24, 11/24, 24 といった日付を認識 |
|---|
| 197 | let pat = '\%(\%(\(\d\d\d\d\)[/-]\=\)\=\(\d\d\)[/-]\=\)\=\(\d\d\)' |
|---|
| 198 | let matches = matchlist(date, pat) |
|---|
| 199 | if !len(matches) |
|---|
| 200 | echoerr '日時のフォーマットが正しくありません!(YYYYMMDD)' |
|---|
| 201 | return |
|---|
| 202 | endif |
|---|
| 203 | |
|---|
| 204 | let [year, month, day] = matches[1:3] |
|---|
| 205 | |
|---|
| 206 | if !strlen(day) |
|---|
| 207 | echoerr '日時のフォーマットが正しくありません!(YYYYMMDD)' |
|---|
| 208 | return |
|---|
| 209 | endif |
|---|
| 210 | |
|---|
| 211 | if !strlen(year) | let year = strftime('%Y') | endif |
|---|
| 212 | if !strlen(month) | let month = strftime('%m') | endif |
|---|
| 213 | |
|---|
| 214 | " 編集ページを取得 |
|---|
| 215 | let content = system(s:curl_cmd . ' "' . base_url . user . '/edit?date=' . year . month . day . '" -b "' . cookie_file . '"') |
|---|
| 216 | if base_url =~ 'g.hatena' |
|---|
| 217 | let content = iconv(content, 'utf-8', &enc) |
|---|
| 218 | let fenc = 'utf-8' |
|---|
| 219 | else |
|---|
| 220 | let content = iconv(content, 'euc-jp', &enc) |
|---|
| 221 | let fenc = 'euc-jp' |
|---|
| 222 | endif |
|---|
| 223 | |
|---|
| 224 | " セッション(編集バッファ)を作成 |
|---|
| 225 | let tmpfile = tempname() |
|---|
| 226 | execute g:hatena_edit_command tmpfile |
|---|
| 227 | set filetype=hatena |
|---|
| 228 | setlocal noswapfile |
|---|
| 229 | let &fileencoding = fenc |
|---|
| 230 | let b:rkm = matchstr(content, 'name="rkm"\s*value="\zs[^"]*\ze"') |
|---|
| 231 | |
|---|
| 232 | if !strlen(b:rkm) |
|---|
| 233 | echoerr 'ログインできませんでした' |
|---|
| 234 | if exists('s:user') |
|---|
| 235 | unlet s:user |
|---|
| 236 | endif |
|---|
| 237 | return |
|---|
| 238 | endif |
|---|
| 239 | |
|---|
| 240 | let b:hatena_login_info = hatena_login_info |
|---|
| 241 | let b:year = year |
|---|
| 242 | let b:month = month |
|---|
| 243 | let b:day = day |
|---|
| 244 | let diary_title = matchstr(content, '<title>\zs.\{-}\ze</title>') |
|---|
| 245 | let day_title = matchstr(content, '<input .\{-}name="title" .\{-}value="\zs.\{-}\ze"') |
|---|
| 246 | let timestamp = matchstr(content, 'name="timestamp"\s*value="\zs[^"]*\ze"') |
|---|
| 247 | let body = s:HtmlUnescape(matchstr(content, '<textarea.\{-}name="body"[^>]*>\zs.\{-}\ze</textarea>')) |
|---|
| 248 | let b:trivial = g:hatena_always_trivial |
|---|
| 249 | let b:diary_title = diary_title |
|---|
| 250 | let b:day_title = day_title |
|---|
| 251 | let b:timestamp = timestamp |
|---|
| 252 | let b:prev_titlestring = &titlestring |
|---|
| 253 | |
|---|
| 254 | autocmd BufWritePost <buffer> call s:HatenaUpdate() | autocmd! BufWritePost <buffer> |
|---|
| 255 | autocmd WinLeave <buffer> let &titlestring = b:prev_titlestring |
|---|
| 256 | autocmd WinEnter <buffer> let &titlestring = b:diary_title . ' ' . b:year . '-' . b:month . '-' . b:day . ' [' . b:hatena_login_info[1] . ']' |
|---|
| 257 | let &titlestring = b:diary_title . ' ' . b:year . '-' . b:month . '-' . b:day . ' [' . user . ']' |
|---|
| 258 | |
|---|
| 259 | let nopaste = !&paste |
|---|
| 260 | set paste |
|---|
| 261 | execute 'normal i' . body |
|---|
| 262 | if nopaste |
|---|
| 263 | set nopaste |
|---|
| 264 | endif |
|---|
| 265 | |
|---|
| 266 | endfunction |
|---|
| 267 | |
|---|
| 268 | function! s:HatenaUpdate(...) " 更新する |
|---|
| 269 | " 日時を取得 |
|---|
| 270 | if !exists('b:hatena_login_info') || !exists('b:year') || !exists('b:month') || !exists('b:day') || !exists('b:day_title') || !exists('b:rkm') |
|---|
| 271 | echoerr ':HatanaEdit してから :HatenaUpdate して下さい' |
|---|
| 272 | return |
|---|
| 273 | endif |
|---|
| 274 | |
|---|
| 275 | " ログイン |
|---|
| 276 | if !exists('b:hatena_login_info') |
|---|
| 277 | let b:hatena_login_info = s:HatenaLogin() |
|---|
| 278 | if !len(b:hatena_login_info) |
|---|
| 279 | return |
|---|
| 280 | endif |
|---|
| 281 | endif |
|---|
| 282 | |
|---|
| 283 | let [base_url, user, cookie_file] = b:hatena_login_info |
|---|
| 284 | |
|---|
| 285 | " まずは全消去 |
|---|
| 286 | let post_data = ' -F mode=enter' |
|---|
| 287 | \ . ' -F year=' . b:year . ' -F month=' . b:month . ' -F day=' . b:day |
|---|
| 288 | \ . ' -F rkm=' . b:rkm |
|---|
| 289 | \ . ' -F body= -F title=' |
|---|
| 290 | call system(s:curl_cmd . ' ' . base_url . user . '/edit -b "' . cookie_file . '"' . post_data) |
|---|
| 291 | |
|---|
| 292 | if a:0 > 0 |
|---|
| 293 | let b:day_title = a:1 |
|---|
| 294 | "else |
|---|
| 295 | " let b:day_title = input('タイトル: ', b:day_title) |
|---|
| 296 | endif |
|---|
| 297 | |
|---|
| 298 | if &modified |
|---|
| 299 | write |
|---|
| 300 | endif |
|---|
| 301 | let body_file = expand('%') |
|---|
| 302 | |
|---|
| 303 | let post_data = ' -F mode=enter' |
|---|
| 304 | \ . ' -F timestamp=' . b:timestamp . ' -F rkm=' . b:rkm |
|---|
| 305 | \ . ' -F year=' . b:year . ' -F month=' . b:month . ' -F day=' . b:day |
|---|
| 306 | \ . ' -F date=' . b:year . b:month . b:day |
|---|
| 307 | \ . ' -F "body=<' . body_file . '"' |
|---|
| 308 | \ . ' -F image= -F title=' . b:day_title |
|---|
| 309 | |
|---|
| 310 | " ポスト |
|---|
| 311 | let result = system(s:curl_cmd . ' ' . base_url . user . '/edit -b "' . cookie_file . '"' . post_data . ' -D -') |
|---|
| 312 | |
|---|
| 313 | echo '更新しました' |
|---|
| 314 | endfunction |
|---|
| 315 | |
|---|
| 316 | function! HatenaEnumUsers(...) " ユーザ名を列挙 |
|---|
| 317 | if !exists('g:hatena_users') |
|---|
| 318 | let g:hatena_users = [] |
|---|
| 319 | endif |
|---|
| 320 | return g:hatena_users |
|---|
| 321 | endfunction |
|---|
| 322 | |
|---|
| 323 | function! s:HtmlUnescape(string) " HTMLエスケープを解除 |
|---|
| 324 | let string = a:string |
|---|
| 325 | while match(string, '&#\d\+;') != -1 |
|---|
| 326 | let num = matchstr(string, '&#\zs\d\+\ze;') |
|---|
| 327 | let string = substitute(string, '&#\d\+;', nr2char(num), '') |
|---|
| 328 | endwhile |
|---|
| 329 | let string = substitute(string, '>', '>', 'g') |
|---|
| 330 | let string = substitute(string, '<', '<', 'g') |
|---|
| 331 | let string = substitute(string, '"', '"', 'g') |
|---|
| 332 | let string = substitute(string, '&', '\&', 'g') |
|---|
| 333 | return string |
|---|
| 334 | endfunction |
|---|
| 335 | |
|---|
| 336 | function! s:GetBaseURLAndUser(hatena_user) " a:hatena_user からグループ/ユーザを取得 |
|---|
| 337 | let pair = split(a:hatena_user, ':') |
|---|
| 338 | if len(pair) > 1 |
|---|
| 339 | let base_url = printf(s:hatena_group_base_url, pair[0]) |
|---|
| 340 | let user = pair[1] |
|---|
| 341 | else |
|---|
| 342 | let base_url = s:hatena_base_url |
|---|
| 343 | let user = a:hatena_user |
|---|
| 344 | endif |
|---|
| 345 | |
|---|
| 346 | return [base_url, user] |
|---|
| 347 | endfunction |
|---|
| 348 | |
|---|
| 349 | " }}} |
|---|
| 350 | " =========================== |
|---|