" Vimball Archiver by Charles E. Campbell, Jr., Ph.D. UseVimball finish plugin/twitvim.vim [[[1 3133 " ============================================================== " TwitVim - Post to Twitter from Vim " Based on Twitter Vim script by Travis Jeffery " " Version: 0.5.0 " License: Vim license. See :help license " Language: Vim script " Maintainer: Po Shan Cheah " Created: March 28, 2008 " Last updated: June 16, 2010 " " GetLatestVimScripts: 2204 1 twitvim.vim " ============================================================== " Load this module only once. if exists('loaded_twitvim') finish endif let loaded_twitvim = 1 " Avoid side-effects from cpoptions setting. let s:save_cpo = &cpo set cpo&vim " Twitter character limit. Twitter used to accept tweets up to 246 characters " in length and display those in truncated form, but that is no longer the " case. So 140 is now the hard limit. let s:char_limit = 140 " Allow the user to override the API root, e.g. for identi.ca, which offers a " Twitter-compatible API. function! s:get_api_root() return exists('g:twitvim_api_root') ? g:twitvim_api_root : "http://api.twitter.com/1" endfunction " Allow user to set the format for retweets. function! s:get_retweet_fmt() return exists('g:twitvim_retweet_format') ? g:twitvim_retweet_format : "RT %s: %t" endfunction " Allow user to enable Python networking code by setting twitvim_enable_python. function! s:get_enable_python() return exists('g:twitvim_enable_python') ? g:twitvim_enable_python : 0 endfunction " Allow user to enable Perl networking code by setting twitvim_enable_perl. function! s:get_enable_perl() return exists('g:twitvim_enable_perl') ? g:twitvim_enable_perl : 0 endfunction " Allow user to enable Ruby code by setting twitvim_enable_ruby. function! s:get_enable_ruby() return exists('g:twitvim_enable_ruby') ? g:twitvim_enable_ruby : 0 endfunction " Allow user to enable Tcl code by setting twitvim_enable_tcl. function! s:get_enable_tcl() return exists('g:twitvim_enable_tcl') ? g:twitvim_enable_tcl : 0 endfunction " Get proxy setting from twitvim_proxy in .vimrc or _vimrc. " Format is proxysite:proxyport function! s:get_proxy() return exists('g:twitvim_proxy') ? g:twitvim_proxy : '' endfunction " If twitvim_proxy_login exists, use that as the proxy login. " Format is proxyuser:proxypassword " If twitvim_proxy_login_b64 exists, use that instead. This is the proxy " user:password in base64 encoding. function! s:get_proxy_login() if exists('g:twitvim_proxy_login_b64') && g:twitvim_proxy_login_b64 != '' return g:twitvim_proxy_login_b64 else return exists('g:twitvim_proxy_login') ? g:twitvim_proxy_login : '' endif endfunction " Get twitvim_count, if it exists. This will be the number of tweets returned " by :FriendsTwitter, :UserTwitter, and :SearchTwitter. function! s:get_count() if exists('g:twitvim_count') if g:twitvim_count < 1 return 1 elseif g:twitvim_count > 200 return 200 else return g:twitvim_count endif endif return 0 endfunction " User setting to show/hide header in the buffer. Default: show header. function! s:get_show_header() return exists('g:twitvim_show_header') ? g:twitvim_show_header : 1 endfunction " User config for name of OAuth access token file. function! s:get_token_file() return exists('g:twitvim_token_file') ? g:twitvim_token_file : $HOME . "/.twitvim.token" endfunction " User config to disable the OAuth access token file. function! s:get_disable_token_file() return exists('g:twitvim_disable_token_file') ? g:twitvim_disable_token_file : 0 endfunction " Display an error message in the message area. function! s:errormsg(msg) redraw echohl ErrorMsg echomsg a:msg echohl None endfunction " Display a warning message in the message area. function! s:warnmsg(msg) redraw echohl WarningMsg echo a:msg echohl None endfunction " Get Twitter login info from twitvim_login in vimrc. " Format is username:password " If twitvim_login_b64 exists, use that instead. This is the user:password " in base64 encoding. " " This function is for services with Twitter-compatible APIs that use Basic " authentication, e.g. identi.ca function! s:get_twitvim_login_noerror() if exists('g:twitvim_login_b64') && g:twitvim_login_b64 != '' return g:twitvim_login_b64 elseif exists('g:twitvim_login') && g:twitvim_login != '' return g:twitvim_login else return '' endif endfunction " Dummy login string to force OAuth signing in run_curl_oauth(). let s:ologin = "oauth:oauth" " Reset login info. function! s:reset_twitvim_login() let s:access_token = "" let s:access_token_secret = "" call delete(s:get_token_file()) let s:cached_username = "" endfunction " Verify user credentials. This function is actually used to do an OAuth " handshake after deleting the access token. " " Returns 1 if login succeeded, 0 if login failed, <0 for other errors. function! s:check_twitvim_login() redraw echo "Logging into Twitter..." let url = s:get_api_root()."/account/verify_credentials.xml" let [error, output] = s:run_curl_oauth(url, s:ologin, s:get_proxy(), s:get_proxy_login(), {}) if error =~ '401' return 0 endif if error != '' call s:errormsg("Error logging into Twitter: ".error) return -1 endif " The following check should not be required because Twitter is supposed to " return a 401 HTTP status on login failure, but you never know with " Twitter. let error = s:xml_get_element(output, 'error') if error =~ '\ccould not authenticate' return 0 endif if error != '' call s:errormsg("Error logging into Twitter: ".error) return -1 endif redraw echo "Twitter login succeeded." return 1 endfunction " Throw away OAuth access tokens and log in again. This is meant to allow the " user to switch to a different Twitter account. function! s:prompt_twitvim_login() call s:reset_twitvim_login() call s:check_twitvim_login() endfunction let s:cached_login = '' let s:cached_username = '' " See if we can save time by using the cached username. function! s:get_twitvim_cached_username() if s:get_api_root() =~ 'twitter\.com' if s:cached_username == '' return '' endif else " In Twitter-compatible services that use Basic authentication, the " user may have changed the login info on the fly. So we have to watch " out for that. let login = s:get_twitvim_login_noerror() if login == '' || login != s:cached_login return '' endif endif return s:cached_username endfunction " Get Twitter user name by verifying login credentials function! s:get_twitvim_username() " If we already have the info, no need to get it again. let username = s:get_twitvim_cached_username() if username != '' return username endif redraw echo "Verifying login credentials with Twitter..." let url = s:get_api_root()."/account/verify_credentials.xml" let [error, output] = s:run_curl_oauth(url, s:ologin, s:get_proxy(), s:get_proxy_login(), {}) if error != '' call s:errormsg("Error verifying login credentials: ".error) return endif let error = s:xml_get_element(output, 'error') if error != '' call s:errormsg("Error verifying login credentials: ".error) return endif redraw echo "Twitter login credentials verified." let username = s:xml_get_element(output, 'screen_name') " Save it so we don't have to do it again unless the user switches to " a different login. let s:cached_username = username let s:cached_login = s:get_twitvim_login_noerror() return username endfunction " If set, twitvim_cert_insecure turns off certificate verification if using " https Twitter API over cURL or Ruby. function! s:get_twitvim_cert_insecure() return exists('g:twitvim_cert_insecure') ? g:twitvim_cert_insecure : 0 endfunction " === XML helper functions === " Get the content of the n'th element in a series of elements. function! s:xml_get_nth(xmlstr, elem, n) let matchres = matchlist(a:xmlstr, '<'.a:elem.'\%( [^>]*\)\?>\(.\{-}\)', -1, a:n) return matchres == [] ? "" : matchres[1] endfunction " Get the content of the specified element. function! s:xml_get_element(xmlstr, elem) return s:xml_get_nth(a:xmlstr, a:elem, 1) endfunction " Remove any number of the specified element from the string. Used for removing " sub-elements so that you can parse the remaining elements safely. function! s:xml_remove_elements(xmlstr, elem) return substitute(a:xmlstr, '<'.a:elem.'>.\{-}', '', "g") endfunction " Get the attributes of the n'th element in a series of elements. function! s:xml_get_attr_nth(xmlstr, elem, n) let matchres = matchlist(a:xmlstr, '<'.a:elem.'\s\+\([^>]*\)>', -1, a:n) if matchres == [] return {} endif let matchcount = 1 let attrstr = matchres[1] let attrs = {} while 1 let matchres = matchlist(attrstr, '\(\w\+\)="\([^"]*\)"', -1, matchcount) if matchres == [] break endif let attrs[matchres[1]] = matchres[2] let matchcount += 1 endwhile return attrs endfunction " Get attributes of the specified element. function! s:xml_get_attr(xmlstr, elem) return s:xml_get_attr_nth(a:xmlstr, a:elem, 1) endfunction " === End of XML helper functions === " === Time parser === " Convert date to Julian date. function! s:julian(year, mon, mday) let month = (a:mon - 1 + 10) % 12 let year = a:year - month / 10 return a:mday + 365 * year + year / 4 - year / 100 + year / 400 + ((month * 306) + 5) / 10 endfunction " Calculate number of days since UNIX Epoch. function! s:daygm(year, mon, mday) return s:julian(a:year, a:mon, a:mday) - s:julian(1970, 1, 1) endfunction " Convert date/time to UNIX time. (seconds since Epoch) function! s:timegm(year, mon, mday, hour, min, sec) return a:sec + a:min * 60 + a:hour * 60 * 60 + s:daygm(a:year, a:mon, a:mday) * 60 * 60 * 24 endfunction " Convert abbreviated month name to month number. function! s:conv_month(s) let monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] for mon in range(len(monthnames)) if monthnames[mon] == tolower(a:s) return mon + 1 endif endfor return 0 endfunction function! s:timegm2(matchres, indxlist) let args = [] for i in a:indxlist if i < 0 let mon = s:conv_month(a:matchres[-i]) if mon == 0 return -1 endif let args = add(args, mon) else let args = add(args, a:matchres[i] + 0) endif endfor return call('s:timegm', args) endfunction " Parse a Twitter time string. function! s:parse_time(str) " This timestamp format is used by Twitter in timelines. let matchres = matchlist(a:str, '^\w\+,\s\+\(\d\+\)\s\+\(\w\+\)\s\+\(\d\+\)\s\+\(\d\+\):\(\d\+\):\(\d\+\)\s\++0000$') if matchres != [] return s:timegm2(matchres, [3, -2, 1, 4, 5, 6]) endif " This timestamp format is used by Twitter in response to an update. let matchres = matchlist(a:str, '^\w\+\s\+\(\w\+\)\s\+\(\d\+\)\s\+\(\d\+\):\(\d\+\):\(\d\+\)\s\++0000\s\+\(\d\+\)$') if matchres != [] return s:timegm2(matchres, [6, -1, 2, 3, 4, 5]) endif " This timestamp format is used by Twitter Search. let matchres = matchlist(a:str, '^\(\d\+\)-\(\d\+\)-\(\d\+\)T\(\d\+\):\(\d\+\):\(\d\+\)Z$') if matchres != [] return s:timegm2(matchres, range(1, 6)) endif " This timestamp format is used by Twitter Rate Limit. let matchres = matchlist(a:str, '^\(\d\+\)-\(\d\+\)-\(\d\+\)T\(\d\+\):\(\d\+\):\(\d\+\)+00:00$') if matchres != [] return s:timegm2(matchres, range(1, 6)) endif return -1 endfunction " Convert the Twitter timestamp to local time and simplify it. function s:time_filter(str) if !exists("*strftime") return a:str endif let t = s:parse_time(a:str) return t < 0 ? a:str : strftime('%I:%M %p %b %d, %Y', t) endfunction " === End of time parser === " === OAuth code === " Check if we can use Perl for HMAC-SHA1 digests. function! s:check_perl_hmac() let can_perl = 1 perl <import; }; if ($@) { VIM::DoCommand('let can_perl = 0'); } EOF return can_perl endfunction " Compute HMAC-SHA1 digest. (Perl version) function! s:perl_hmac_sha1_digest(key, str) perl <import; my $key = VIM::Eval('a:key'); my $str = VIM::Eval('a:str'); my $hmac = Digest::HMAC_SHA1->new($key); $hmac->add($str); my $signature = $hmac->b64digest; # Length of 27 VIM::DoCommand("let signature = '$signature'"); EOF return signature endfunction " Check if we can use Python for HMAC-SHA1 digests. function! s:check_python_hmac() let can_python = 1 python <import; require LWP::UserAgent; LWP::UserAgent->import; }; if ($@) { VIM::DoCommand('let can_perl = 0'); } EOF return can_perl endfunction " Use Perl to fetch a web page. function! s:perl_curl(url, login, proxy, proxylogin, parms) let error = "" let output = "" perl <import; require LWP::UserAgent; LWP::UserAgent->import; sub make_base64 { my $s = shift; $s =~ /:/ ? encode_base64($s) : $s; } my $ua = LWP::UserAgent->new; my $url = VIM::Eval('a:url'); my $proxy = VIM::Eval('a:proxy'); $proxy ne '' and $ua->proxy('http', "http://$proxy"); my $proxylogin = VIM::Eval('a:proxylogin'); $proxylogin ne '' and $ua->default_header('Proxy-Authorization' => 'Basic '.make_base64($proxylogin)); my %parms = (); my $keys = VIM::Eval('keys(a:parms)'); for $k (split(/\n/, $keys)) { $parms{$k} = VIM::Eval("a:parms['$k']"); } my $login = VIM::Eval('a:login'); if ($login ne '') { if ($login =~ /^OAuth /) { $ua->default_header('Authorization' => $login); # VIM::Msg($login, "ErrorMsg"); } else { $ua->default_header('Authorization' => 'Basic '.make_base64($login)); } } # VIM::Msg($url, "ErrorMsg"); # VIM::Msg(join(' ', keys(%parms)), "ErrorMsg"); my $response = %parms ? $ua->post($url, \%parms) : $ua->get($url); if ($response->is_success) { my $output = $response->content; $output =~ s/'/''/g; VIM::DoCommand("let output ='$output'"); } else { my $error = $response->status_line; $error =~ s/'/''/g; VIM::DoCommand("let error ='$error'"); } EOF return [ error, output ] endfunction " Check if we can use Ruby. " " Note: Before the networking code will function in Ruby under Windows, you " need the patch from here: " http://www.mail-archive.com/vim_dev@googlegroups.com/msg03693.html " " and Bram's correction to the patch from here: " http://www.mail-archive.com/vim_dev@googlegroups.com/msg03713.html " function! s:check_ruby() let can_ruby = 1 ruby < exc VIM.command("let error='#{exc.message}'") end EOF return [error, output] endfunction " Check if we can use Tcl. " " Note: ActiveTcl 8.5 doesn't include Tcllib in the download. You need to run the following after installing ActiveTcl: " " teacup install tcllib " function! s:check_tcl() let can_tcl = 1 tcl <= 0 } { return [base64::encode $s] } return $s } set url [::vim::expr a:url] set headers [list] ::http::config -proxyhost "" set proxy [::vim::expr a:proxy] if { $proxy != "" } { array set prox [uri::split "http://$proxy"] ::http::config -proxyhost $prox(host) ::http::config -proxyport $prox(port) } set proxylogin [::vim::expr a:proxylogin] if { $proxylogin != "" } { lappend headers "Proxy-Authorization" "Basic [make_base64 $proxylogin]" } set login [::vim::expr a:login] if { $login != "" } { if {[string range $login 0 5] == "OAuth "} { lappend headers "Authorization" $login } else { lappend headers "Authorization" "Basic [make_base64 $login]" } } set parms [list] set keys [split [::vim::expr "keys(a:parms)"] "\n"] if { [llength $keys] > 0 } { foreach key $keys { lappend parms $key [::vim::expr "a:parms\['$key']"] } set query [eval [concat ::http::formatQuery $parms]] set res [::http::geturl $url -headers $headers -query $query] } else { set res [::http::geturl $url -headers $headers] } upvar #0 $res state if { $state(status) == "ok" } { if { [ ::http::ncode $res ] >= 400 } { set error $state(http) ::vim::command "let error = '$error'" } else { set output [string map {' ''} $state(body)] ::vim::command "let output = '$output'" } } else { if { [ info exists state(error) ] } { set error [string map {' ''} $state(error)] } else { set error "$state(status) error" } ::vim::command "let error = '$error'" } ::http::cleanup $res EOF return [error, output] endfunction " Find out which method we can use to fetch a web page. function! s:get_curl_method() if !exists('s:curl_method') let s:curl_method = 'curl' if s:get_enable_perl() && has('perl') && s:check_perl() let s:curl_method = 'perl' elseif s:get_enable_python() && has('python') && s:check_python() let s:curl_method = 'python' elseif s:get_enable_ruby() && has('ruby') && s:check_ruby() let s:curl_method = 'ruby' elseif s:get_enable_tcl() && has('tcl') && s:check_tcl() let s:curl_method = 'tcl' endif endif return s:curl_method endfunction function! s:run_curl(url, login, proxy, proxylogin, parms) return s:{s:get_curl_method()}_curl(a:url, a:login, a:proxy, a:proxylogin, a:parms) endfunction function! s:reset_curl_method() if exists('s:curl_method') unlet s:curl_method endif endfunction function! s:show_curl_method() echo 'Method:' s:get_curl_method() endfunction " For debugging. Reset networking method. if !exists(":TwitVimResetMethod") command TwitVimResetMethod :call reset_curl_method() endif " For debugging. Show current networking method. if !exists(":TwitVimShowMethod") command TwitVimShowMethod :call show_curl_method() endif " === End of networking code === " === Buffer stack code === " Each buffer record holds the following fields: " " buftype: Buffer type = dmrecv, dmsent, search, public, friends, user, replies, list " user: For user buffers if other than current user " list: List slug if displaying a Twitter list. " page: Keep track of pagination. " statuses: Tweet IDs. For use by in_reply_to_status_id " inreplyto: IDs of predecessor messages for @-replies. " dmids: Direct Message IDs. " buffer: The buffer text. " showheader: 1 if header is shown in this buffer, 0 if header is hidden. let s:curbuffer = {} let s:bufstack = [] " Maximum items in the buffer stack. Adding a new item after this limit will " get rid of the first item. let s:bufstackmax = 10 " Buffer stack pointer. -1 if no items yet. May not point to the end of the " list if user has gone back one or more buffers. let s:bufstackptr = -1 " Add current buffer to the buffer stack at the next position after current. " Remove all buffers after that. function! s:add_buffer() " If stack is already full, remove the buffer at the bottom of the stack to " make room. if s:bufstackptr >= s:bufstackmax call remove(s:bufstack, 0) let s:bufstackptr -= 1 endif let s:bufstackptr += 1 " Suppress errors because there may not be anything to remove after current " position. silent! call remove(s:bufstack, s:bufstackptr, -1) call add(s:bufstack, s:curbuffer) endfunction " If current buffer is same type as the buffer at the buffer stack pointer then " just copy it into the buffer stack. Otherwise, add it to buffer stack. function! s:save_buffer() if s:curbuffer == {} return endif " Save buffer contents and cursor position. let twit_bufnr = bufwinnr('^'.s:twit_winname.'$') if twit_bufnr > 0 let curwin = winnr() execute twit_bufnr . "wincmd w" let s:curbuffer.buffer = getline(1, '$') let s:curbuffer.view = winsaveview() execute curwin . "wincmd w" else let s:curbuffer.view = {} endif " If current buffer is the same type as buffer at the top of the stack, " then just copy it. if s:bufstackptr >= 0 && s:curbuffer.buftype == s:bufstack[s:bufstackptr].buftype && s:curbuffer.list == s:bufstack[s:bufstackptr].list && s:curbuffer.user == s:bufstack[s:bufstackptr].user && s:curbuffer.page == s:bufstack[s:bufstackptr].page let s:bufstack[s:bufstackptr] = deepcopy(s:curbuffer) return endif " Otherwise, push the current buffer onto the stack. call s:add_buffer() endfunction " Go back one buffer in the buffer stack. function! s:back_buffer() call s:save_buffer() if s:bufstackptr < 1 call s:warnmsg("Already at oldest buffer. Can't go back further.") return -1 endif let s:bufstackptr -= 1 let s:curbuffer = deepcopy(s:bufstack[s:bufstackptr]) call s:twitter_wintext_view(s:curbuffer.buffer, "timeline", s:curbuffer.view) return 0 endfunction " Go forward one buffer in the buffer stack. function! s:fwd_buffer() call s:save_buffer() if s:bufstackptr + 1 >= len(s:bufstack) call s:warnmsg("Already at newest buffer. Can't go forward.") return -1 endif let s:bufstackptr += 1 let s:curbuffer = deepcopy(s:bufstack[s:bufstackptr]) call s:twitter_wintext_view(s:curbuffer.buffer, "timeline", s:curbuffer.view) return 0 endfunction if !exists(":BackTwitter") command BackTwitter :call back_buffer() endif if !exists(":ForwardTwitter") command ForwardTwitter :call fwd_buffer() endif " For debugging. Show the buffer stack. function! s:show_bufstack() for i in range(len(s:bufstack) - 1, 0, -1) echo i.':' 'type='.s:bufstack[i].buftype 'user='.s:bufstack[i].user 'page='.s:bufstack[i].page endfor endfunction if !exists(":TwitVimShowBufstack") command TwitVimShowBufstack :call show_bufstack() endif " For debugging. Show curbuffer variable. if !exists(":TwitVimShowCurbuffer") command TwitVimShowCurbuffer :echo s:curbuffer endif " === End of buffer stack code === " Add update to Twitter buffer if public, friends, or user timeline. function! s:add_update(output) if has_key(s:curbuffer, 'buftype') && (s:curbuffer.buftype == "public" || s:curbuffer.buftype == "friends" || s:curbuffer.buftype == "user" || s:curbuffer.buftype == "replies" || s:curbuffer.buftype == "list" || s:curbuffer.buftype == "retweeted_by_me" || s:curbuffer.buftype == "retweeted_to_me") " Parse the output from the Twitter update call. let line = s:format_status_xml(a:output) " Line number where new tweet will be inserted. It should be 3 if " header is shown and 1 if header is hidden. let insline = s:curbuffer.showheader ? 3 : 1 " Add the status ID to the current buffer's statuses list. call insert(s:curbuffer.statuses, s:xml_get_element(a:output, 'id'), insline) " Add in-reply-to ID to current buffer's in-reply-to list. call insert(s:curbuffer.inreplyto, s:xml_get_element(a:output, 'in_reply_to_status_id'), insline) let twit_bufnr = bufwinnr('^'.s:twit_winname.'$') if twit_bufnr > 0 let curwin = winnr() execute twit_bufnr . "wincmd w" set modifiable call append(insline - 1, line) execute "normal! ".insline."G" set nomodifiable let s:curbuffer.buffer = getline(1, '$') execute curwin . "wincmd w" endif endif endfunction " Count number of characters in a multibyte string. Use technique from " :help strlen(). function! s:mbstrlen(s) return strlen(substitute(a:s, ".", "x", "g")) endfunction " Common code to post a message to Twitter. function! s:post_twitter(mesg, inreplyto) let parms = {} " Add in_reply_to_status_id if status ID is available. if a:inreplyto != 0 let parms["in_reply_to_status_id"] = a:inreplyto endif let mesg = a:mesg " Remove trailing newline. You see that when you visual-select an entire " line. Don't let it count towards the tweet length. let mesg = substitute(mesg, '\n$', '', "") " Convert internal newlines to spaces. let mesg = substitute(mesg, '\n', ' ', "g") let mesglen = s:mbstrlen(mesg) " Check tweet length. Note that the tweet length should be checked before " URL-encoding the special characters because URL-encoding increases the " string length. if mesglen > s:char_limit call s:warnmsg("Your tweet has ".(mesglen - s:char_limit)." too many characters. It was not sent.") elseif mesglen < 1 call s:warnmsg("Your tweet was empty. It was not sent.") else redraw echo "Sending update to Twitter..." let url = s:get_api_root()."/statuses/update.xml" let parms["status"] = mesg let parms["source"] = "twitvim" let [error, output] = s:run_curl_oauth(url, s:ologin, s:get_proxy(), s:get_proxy_login(), parms) if error != '' call s:errormsg("Error posting your tweet: ".error) else call s:add_update(output) redraw echo "Your tweet was sent. You used ".mesglen." characters." endif endif endfunction " Prompt user for tweet and then post it. " If initstr is given, use that as the initial input. function! s:CmdLine_Twitter(initstr, inreplyto) call inputsave() redraw let mesg = input("Your Twitter: ", a:initstr) call inputrestore() call s:post_twitter(mesg, a:inreplyto) endfunction " Extract the user name from a line in the timeline. function! s:get_user_name(line) let line = substitute(a:line, '^+ ', '', '') let matchres = matchlist(line, '^\(\w\+\):') return matchres != [] ? matchres[1] : "" endfunction " This is for a local mapping in the timeline. Start an @-reply on the command " line to the author of the tweet on the current line. function! s:Quick_Reply() let username = s:get_user_name(getline('.')) if username != "" " If the status ID is not available, get() will return 0 and " post_twitter() won't add in_reply_to_status_id to the update. call s:CmdLine_Twitter('@'.username.' ', get(s:curbuffer.statuses, line('.'))) endif endfunction " Extract all user names from a line in the timeline. Return the poster's name as well as names from all the @replies. function! s:get_all_names(line) let names = [] let dictnames = {} let username = s:get_user_name(getline('.')) if username != "" " Add this to the beginning of the list because we want the tweet " author to be the main addressee in the reply to all. let names = [ username ] let dictnames[tolower(username)] = 1 endif let matchcount = 1 while 1 let matchres = matchlist(a:line, '@\(\w\+\)', -1, matchcount) if matchres == [] break endif let name = matchres[1] " Don't add duplicate names. if !has_key(dictnames, tolower(name)) call add(names, name) let dictnames[tolower(name)] = 1 endif let matchcount += 1 endwhile return names endfunction " Reply to everyone mentioned on a line in the timeline. function! s:Reply_All() let names = s:get_all_names(getline('.')) " Remove the author from the reply list so that he doesn't end up replying " to himself. let user = s:get_twitvim_username() let names2 = [] for name in names if name != user call add(names2, name) endif endfor let replystr = '@'.join(names2, ' @').' ' if names != [] " If the status ID is not available, get() will return 0 and " post_twitter() won't add in_reply_to_status_id to the update. call s:CmdLine_Twitter(replystr, get(s:curbuffer.statuses, line('.'))) endif endfunction " This is for a local mapping in the timeline. Start a direct message on the " command line to the author of the tweet on the current line. function! s:Quick_DM() let username = s:get_user_name(getline('.')) if username != "" " call s:CmdLine_Twitter('d '.username.' ', 0) call s:send_dm(username, '') endif endfunction " Allow user to switch to old-style retweets by setting twitvim_old_retweet. function! s:get_old_retweet() return exists('g:twitvim_old_retweet') ? g:twitvim_old_retweet : 0 endfunction " Extract the tweet text from a timeline buffer line. function! s:get_tweet(line) let line = substitute(a:line, '^\w\+:\s\+', '', '') let line = substitute(line, '\s\+|[^|]\+|$', '', '') " Remove newlines. let line = substitute(line, "\n", '', 'g') return line endfunction " Retweet is for replicating a tweet from another user. function! s:Retweet() let line = getline('.') let username = s:get_user_name(line) if username != "" let retweet = substitute(s:get_retweet_fmt(), '%s', '@'.username, '') let retweet = substitute(retweet, '%t', s:get_tweet(line), '') call s:CmdLine_Twitter(retweet, 0) endif endfunction " Use new-style retweet API to retweet a tweet from another user. function! s:Retweet_2() " Do an old-style retweet if user has set twitvim_old_retweet. if s:get_old_retweet() call s:Retweet() return endif let status = get(s:curbuffer.statuses, line('.')) if status == 0 " Fall back to old-style retweeting if we can't get this tweet's status " ID. call s:Retweet() return endif let parms = {} " Force POST instead of GET. let parms["dummy"] = "dummy1" let url = s:get_api_root()."/statuses/retweet/".status.".xml" redraw echo "Retweeting..." let [error, output] = s:run_curl_oauth(url, s:ologin, s:get_proxy(), s:get_proxy_login(), parms) if error != '' call s:errormsg("Error retweeting: ".error) else call s:add_update(output) redraw echo "Retweeted." endif endfunction " Show which tweet this one is replying to below the current line. function! s:show_inreplyto() let lineno = line('.') let inreplyto = get(s:curbuffer.inreplyto, lineno) if inreplyto == 0 call s:warnmsg("No in-reply-to information for current line.") return endif redraw echo "Querying Twitter for in-reply-to tweet..." let url = s:get_api_root()."/statuses/show/".inreplyto.".xml" let [error, output] = s:run_curl_oauth(url, s:ologin, s:get_proxy(), s:get_proxy_login(), {}) if error != '' call s:errormsg("Error getting in-reply-to tweet: ".error) return endif let error = s:xml_get_element(output, 'error') if error != '' call s:errormsg("Error getting in-reply-to tweet: ".error) return endif let line = s:format_status_xml(output) " Add the status ID to the current buffer's statuses list. call insert(s:curbuffer.statuses, s:xml_get_element(output, 'id'), lineno + 1) " Add in-reply-to ID to current buffer's in-reply-to list. call insert(s:curbuffer.inreplyto, s:xml_get_element(output, 'in_reply_to_status_id'), lineno + 1) " Already in the correct buffer so no need to search or switch buffers. set modifiable call append(lineno, '+ '.line) set nomodifiable let s:curbuffer.buffer = getline(1, '$') redraw echo "In-reply-to tweet found." endfunction " Truncate a string. Add '...' to the end of string was longer than " the specified number of characters. function! s:strtrunc(s, len) let slen = strlen(substitute(a:s, ".", "x", "g")) let s = substitute(a:s, '^\(.\{,'.a:len.'}\).*$', '\1', '') if slen > a:len let s .= '...' endif return s endfunction " Delete tweet or DM on current line. function! s:do_delete_tweet() let lineno = line('.') let isdm = (s:curbuffer.buftype == "dmrecv" || s:curbuffer.buftype == "dmsent") let obj = isdm ? "message" : "tweet" let uobj = isdm ? "Message" : "Tweet" let id = get(isdm ? s:curbuffer.dmids : s:curbuffer.statuses, lineno) " The delete API call requires POST, not GET, so we supply a fake parameter " to force run_curl() to use POST. let parms = {} let parms["id"] = id let url = s:get_api_root().'/'.(isdm ? "direct_messages" : "statuses")."/destroy/".id.".xml" let [error, output] = s:run_curl_oauth(url, s:ologin, s:get_proxy(), s:get_proxy_login(), parms) if error != '' call s:errormsg("Error deleting ".obj.": ".error) return endif let error = s:xml_get_element(output, 'error') if error != '' call s:errormsg("Error deleting ".obj.": ".error) return endif if isdm call remove(s:curbuffer.dmids, lineno) else call remove(s:curbuffer.statuses, lineno) call remove(s:curbuffer.inreplyto, lineno) endif " Already in the correct buffer so no need to search or switch buffers. set modifiable normal! dd set nomodifiable let s:curbuffer.buffer = getline(1, '$') redraw echo uobj "deleted." endfunction " Delete tweet or DM on current line. function! s:delete_tweet() let lineno = line('.') let isdm = (s:curbuffer.buftype == "dmrecv" || s:curbuffer.buftype == "dmsent") let obj = isdm ? "message" : "tweet" let uobj = isdm ? "Message" : "Tweet" let id = get(isdm ? s:curbuffer.dmids : s:curbuffer.statuses, lineno) if id == 0 call s:warnmsg("No erasable ".obj." on current line.") return endif call inputsave() let answer = input('Delete "'.s:strtrunc(getline('.'), 40).'"? (y/n) ') call inputrestore() if answer == 'y' || answer == 'Y' call s:do_delete_tweet() else redraw echo uobj "not deleted." endif endfunction " Prompt user for tweet. if !exists(":PosttoTwitter") command PosttoTwitter :call CmdLine_Twitter('', 0) endif nnoremenu Plugin.TwitVim.Post\ from\ cmdline :call CmdLine_Twitter('', 0) " Post current line to Twitter. if !exists(":CPosttoTwitter") command CPosttoTwitter :call post_twitter(getline('.'), 0) endif nnoremenu Plugin.TwitVim.Post\ current\ line :call post_twitter(getline('.'), 0) " Post entire buffer to Twitter. if !exists(":BPosttoTwitter") command BPosttoTwitter :call post_twitter(join(getline(1, "$")), 0) endif " Post visual selection to Twitter. noremap Visual y:call post_twitter(@", 0) noremap