root/lang/vim/hatena/plugin/hatena.vim @ 3772

Revision 3772, 11.2 kB (checked in by motemen, 5 years ago)

lang/vim/hatena: import initial version

Line 
1scriptencoding 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
42command! -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]
57command! -nargs=? -complete=customlist,HatenaEnumUsers HatenaUser   if strlen('<args>') | let g:hatena_user='<args>' | else | echo g:hatena_user | endif
58
59nnoremap <Leader>he :HatenaEdit<CR>
60" }}}
61" ===========================
62
63" ===========================
64"   スクリプト設定 {{{
65
66" はてなのユーザID
67if !exists('g:hatena_user')
68    let g:hatena_user = ''
69endif
70
71" サブアカなども含めたIDのリスト
72if !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
78endif
79
80" クッキーを保存しておくか? (1: 保存しておく 0: Vim終了時に削除)
81if !exists('g:hatena_hold_cookie')
82    let g:hatena_hold_cookie = 1
83endif
84
85" スクリプトのベースディレクトリ (クッキーの保存に使われるだけ)
86if !exists('g:hatena_base_dir')
87    let g:hatena_base_dir = substitute(expand('<sfile>:p:h'), '[/\\]plugin$', '', '')
88endif
89
90" 常に `ちょっとした更新' にする? (1: 常にちょっとした更新)
91if !exists('g:hatena_always_trivial')
92    let g:hatena_always_trivial = 0
93endif
94
95let g:hatena_syntax_html = 1
96
97if !g:hatena_hold_cookie
98    autocmd VimLeave * call delete(b:hatena_login_info[2])
99endif
100
101" :HatenaEdit で編集バッファを開くコマンド
102let g:hatena_edit_command = 'edit!'
103
104let s:curl_cmd = 'curl -k'
105let s:hatena_login_url      = 'https://www.hatena.ne.jp/login'
106let s:hatena_base_url       = 'http://d.hatena.ne.jp/'
107let 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"   ログインに失敗: 空リストを返す。
121function! 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
174endfunction
175
176function! 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
266endfunction
267
268function! 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 '更新しました'
314endfunction
315
316function! HatenaEnumUsers(...) " ユーザ名を列挙
317    if !exists('g:hatena_users')
318        let g:hatena_users = []
319    endif
320    return g:hatena_users
321endfunction
322
323function! 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, '&gt;',   '>', 'g')
330    let string = substitute(string, '&lt;',   '<', 'g')
331    let string = substitute(string, '&quot;', '"', 'g')
332    let string = substitute(string, '&amp;',  '\&', 'g')
333    return string
334endfunction
335
336function! 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]
347endfunction
348
349" }}}
350" ===========================
Note: See TracBrowser for help on using the browser.