" Vimball Archiver by Charles E. Campbell, Jr., Ph.D. UseVimball finish plugin/twitvim.vim [[[1 4685 " ============================================================== " TwitVim - Post to Twitter from Vim " Based on Twitter Vim script by Travis Jeffery " " Version: 0.6.2 " License: Vim license. See :help license " Language: Vim script " Maintainer: Po Shan Cheah " Created: March 28, 2008 " Last updated: February 23, 2011 " " 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 " User agent header string. let s:user_agent = 'TwitVim 0.6.2 2011-02-17' " 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 != '' let errormsg = s:xml_get_element(output, 'error') call s:errormsg("Error logging into Twitter: ".(errormsg != '' ? errormsg : 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 != '' let errormsg = s:xml_get_element(output, 'error') call s:errormsg("Error verifying login credentials: ".(errormsg != '' ? errormsg : 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 " === JSON parser === let s:parse_string = {} function! s:set_parse_string(s) let s:parse_string = { 'str' : a:s, 'ptr' : 0 } endfunction function! s:is_digit(c) return a:c =~ '\d' endfunction function! s:is_hexdigit(c) return a:c =~ '\x' endfunction function! s:is_alpha(c) return a:c =~ '\a' endfunction " Get next character. If peek is true, don't advance string pointer. function! s:lookahead_char(peek) let str = s:parse_string.str let len = strlen(str) let ptr = s:parse_string.ptr if ptr >= len return '' endif if str[ptr] == '\' if ptr + 1 < len if stridx('"\/bfnrt', str[ptr + 1]) >= 0 let s = eval('"\'.str[ptr + 1].'"') if !a:peek let s:parse_string.ptr = ptr + 2 endif " Tokenizer needs to distinguish " from \" inside a string. return '\'.s elseif str[ptr + 1] == 'u' let s = '' for i in range(ptr + 2, ptr + 5) if i < len && s:is_hexdigit(str[i]) let s .= str[i] endif endfor if s != '' let s2 = eval('"\u'.s.'"') if !a:peek let s:parse_string.ptr = ptr + 2 + strlen(s) endif return s2 endif endif endif endif " If we don't recognize any longer char tokens, just return the current " char. let s = str[ptr] if !a:peek let s:parse_string.ptr = ptr + 1 endif return s endfunction function! s:getchar() return s:lookahead_char(0) endfunction function! s:peekchar() return s:lookahead_char(1) endfunction function! s:peekstr(n) return strpart(s:parse_string.str, s:parse_string.ptr, a:n) endfunction function! s:parse_error_msg(what) return printf("Parse error near '%s': %s", s:peekstr(30), a:what) endfunction " Get next token from JSON string. " Returns: [ tokentype, value ] function! s:get_token() while 1 let c = s:getchar() if c == '' return [ 'eof', '' ] elseif c == '"' let s = '' while 1 let c = s:getchar() if c == '"' || c == '' return ['string', s] endif " Strip off the escaping backslash. if c[0] == '\' let c = c[1] endif let s .= c endwhile elseif stridx('{}[],:', c) >= 0 return [ 'char', c ] elseif s:is_alpha(c) let s = c while s:is_alpha(s:peekchar()) let c = s:getchar() let s .= c endwhile return [ 'keyword', s ] elseif s:is_digit(c) || c == '-' " number = [-]d[d...][.d[d...]][(e|E)[(-|+)]d[d...]] let mode = 'int' let s = c while 1 let c = s:peekchar() if s:is_digit(c) let c = s:getchar() let s .= c elseif c == '.' && mode == 'int' let mode = 'frac' let c = s:getchar() let s .= c elseif (c == 'e' || c == 'E') && (mode == 'int' || mode == 'frac') let mode = 'exp' let c = s:getchar() let s .= c let c = s:peekchar() if c == '-' || c == '+' let c = s:getchar() let s .= c endif else " Clean up some malformed floating-point numbers that Vim " would reject. let s = substitute(s, '^\.', '0.', '') let s = substitute(s, '-\.', '-0.', '') let s = substitute(s, '\.[eE]', '.0E', '') let s = substitute(s, '[-+eE.]$', '&0', '') " This takes care of the case where there is " an exponent but no frac part. if s =~ '[Ee]' && s != '\.' let s = substitute(s, '[Ee]', '.0&', '') endif return ['number', eval(s)] endif endwhile endif endwhile endfunction " value = string | number | object | array | true | false | null function! s:parse_value(tok) let tok = a:tok if tok[0] == 'string' || tok[0] == 'number' return [ tok[1], s:get_token() ] elseif tok == [ 'char', '{' ] return s:parse_object(tok) elseif tok == [ 'char', '[' ] return s:parse_array(tok) elseif tok[0] == 'keyword' if tok[1] == 'true' return [ 1, s:get_token() ] elseif tok[1] == 'false' return [ 0, s:get_token() ] elseif tok[1] == 'null' return [ {}, s:get_token() ] else throw s:parse_error_msg("unrecognized keyword '".tok[1]."'") endif elseif tok[0] == 'eof' throw s:parse_error_msg("unexpected EOF") endif endfunction " elements = value | value ',' elements function! s:parse_elements(tok) let [ resultx, tok ] = s:parse_value(a:tok) let result = [ resultx ] if tok == [ 'char', ',' ] let [ result2, tok ] = s:parse_elements(s:get_token()) call extend(result, result2) endif return [ result, tok ] endfunction " array = '[' ']' | '[' elements ']' function! s:parse_array(tok) if a:tok == [ 'char', '[' ] let tok = s:get_token() if tok == [ 'char', ']' ] return [ [], s:get_token() ] endif let [ result, tok ] = s:parse_elements(tok) if tok != [ 'char', ']' ] throw s:parse_error_msg("']' expected") endif return [ result, s:get_token() ] else throw s:parse_error_msg("'[' expected") endif endfunction " pair = string ':' value function! s:parse_pair(tok) if a:tok[0] == 'string' let key = a:tok[1] let tok = s:get_token() if tok == [ 'char', ':' ] let [ result, tok ] = s:parse_value(s:get_token()) return [ { key : result }, tok ] else throw s:parse_error_msg("':' expected") endif else throw s:parse_error_msg("string (key name) expected") endif endfunction " members = pair | pair ',' members function! s:parse_members(tok) let [ result, tok ] = s:parse_pair(a:tok) if tok == [ 'char', ',' ] let [ result2, tok ] = s:parse_members(s:get_token()) call extend(result, result2) endif return [ result, tok ] endfunction " object = '{' '}' | '{' members '}' function! s:parse_object(tok) if a:tok == [ 'char', '{' ] let tok = s:get_token() if tok == [ 'char', '}' ] return [ {}, s:get_token() ] endif let [ result, tok ] = s:parse_members(tok) if tok != [ 'char', '}' ] throw s:parse_error_msg("'}' expected") endif return [ result, s:get_token() ] else throw s:parse_error_msg("'{' expected") endif endfunction function! s:parse_json(str) try call s:set_parse_string(a:str) let [ result, tok ] = s:parse_object(s:get_token()) return result catch /^Parse error/ echoerr v:exception endtry 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 <reset_hmac_method() endif " For debugging. Show current Hmac method. if !exists(":TwitVimShowHmacMethod") command TwitVimShowHmacMethod :call show_hmac_method() endif let s:gc_consumer_key = "HyshEU8SbcsklPQ6ouF0g" let s:gc_consumer_secret = "U1uvxLjZxlQAasy9Kr5L2YAFnsvYTOqx1bk7uJuezQ" let s:gc_req_url = "http://api.twitter.com/oauth/request_token" let s:gc_access_url = "http://api.twitter.com/oauth/access_token" let s:gc_authorize_url = "https://api.twitter.com/oauth/authorize" " Simple nonce value generator. This needs to be randomized better. function! s:nonce() if !exists("s:nonce_val") || s:nonce_val < 1 let s:nonce_val = localtime() + 109 endif let retval = s:nonce_val let s:nonce_val += 109 return retval endfunction " Split a URL into base and params. function! s:split_url(url) let urlarray = split(a:url, '?') let baseurl = urlarray[0] let parms = {} if len(urlarray) > 1 for pstr in split(urlarray[1], '&') let [key, value] = split(pstr, '=') let parms[key] = value endfor endif return [baseurl, parms] endfunction " Produce signed content using the parameters provided via parms using the " chosen method, url and provided token secret. Note that in the case of " getting a new Request token, the secret will be "" function! s:getOauthResponse(url, method, parms, token_secret) let parms = copy(a:parms) " Add some constants to hash let parms["oauth_consumer_key"] = s:gc_consumer_key let parms["oauth_signature_method"] = "HMAC-SHA1" let parms["oauth_version"] = "1.0" " Get the timestamp and add to hash let parms["oauth_timestamp"] = localtime() let parms["oauth_nonce"] = s:nonce() let [baseurl, urlparms] = s:split_url(a:url) call extend(parms, urlparms) " Alphabetically sort by key and form a string that has " the format key1=value1&key2=value2&... " Must UTF8 encode and then URL encode the values. let content = "" for key in sort(keys(parms)) let value = s:url_encode(parms[key]) let content .= key . "=" . value . "&" endfor let content = content[0:-2] " Form the signature base string which is comprised of 3 " pieces, with each piece URL encoded. " [METHOD_UPPER_CASE]&[url]&content let signature_base_str = a:method . "&" . s:url_encode(baseurl) . "&" . s:url_encode(content) let hmac_sha1_key = s:url_encode(s:gc_consumer_secret) . "&" . s:url_encode(a:token_secret) let signature = s:hmac_sha1_digest(hmac_sha1_key, signature_base_str) " Add padding character to make a multiple of 4 per the " requirement of OAuth. if strlen(signature) % 4 let signature .= "=" endif let content = "OAuth " for key in keys(parms) if key =~ "oauth" let value = s:url_encode(parms[key]) let content .= key . '="' . value . '", ' endif endfor let content .= 'oauth_signature="' . s:url_encode(signature) . '"' return content endfunction " Convert an OAuth endpoint to https if API root is https. function! s:to_https(url) let url = a:url if s:get_api_root()[:5] == 'https:' if url[:4] == 'http:' let url = 'https:'.url[5:] endif endif return url endfunction " Perform the OAuth dance to authorize this client with Twitter. function! s:do_oauth() " Call oauth/request_token to get request token from Twitter. let parms = { "oauth_callback": "oob", "dummy" : "1" } let req_url = s:to_https(s:gc_req_url) let oauth_hdr = s:getOauthResponse(req_url, "POST", parms, "") let [error, output] = s:run_curl(req_url, oauth_hdr, s:get_proxy(), s:get_proxy_login(), { "dummy" : "1" }) if error != '' call s:errormsg("Error from oauth/request_token: ".error) return [-1, '', ''] endif let matchres = matchlist(output, 'oauth_token=\([^&]\+\)&') if matchres != [] let request_token = matchres[1] endif let matchres = matchlist(output, 'oauth_token_secret=\([^&]\+\)&') if matchres != [] let token_secret = matchres[1] endif " Launch web browser to let user allow or deny the authentication request. let auth_url = s:gc_authorize_url . "?oauth_token=" . request_token " If user has not set up twitvim_browser_cmd, just display the " authentication URL and ask the user to visit that URL. if !exists('g:twitvim_browser_cmd') || g:twitvim_browser_cmd == '' " Attempt to shorten the auth URL. let newurl = s:call_isgd(auth_url) if newurl != "" let auth_url = newurl else let newurl = s:call_bitly(auth_url) if newurl != "" let auth_url = newurl endif endif echo "Visit the following URL in your browser to authenticate TwitVim:" echo auth_url else if s:launch_browser(auth_url) < 0 return [-2, '', ''] endif endif call inputsave() let pin = input("Enter Twitter OAuth PIN: ") call inputrestore() if pin == "" call s:warnmsg("No OAuth PIN entered") return [-3, '', ''] endif " Call oauth/access_token to swap request token for access token. let parms = { "dummy" : 1, "oauth_token" : request_token, "oauth_verifier" : pin } let access_url = s:to_https(s:gc_access_url) let oauth_hdr = s:getOauthResponse(access_url, "POST", parms, token_secret) let [error, output] = s:run_curl(access_url, oauth_hdr, s:get_proxy(), s:get_proxy_login(), { "dummy" : 1 }) if error != '' call s:errormsg("Error from oauth/access_token: ".error) return [-4, '', ''] endif let matchres = matchlist(output, 'oauth_token=\([^&]\+\)&') if matchres != [] let request_token = matchres[1] endif let matchres = matchlist(output, 'oauth_token_secret=\([^&]\+\)&') if matchres != [] let token_secret = matchres[1] endif return [ 0, request_token, token_secret ] endfunction " Sign a request with OAuth and send it. function! s:run_curl_oauth(url, login, proxy, proxylogin, parms) if a:login != '' && a:url =~ 'twitter\.com' if !exists('s:access_token') || s:access_token == '' let tokens = [] if !s:get_disable_token_file() && filereadable(s:get_token_file()) " Try to read access tokens from token file. let tokens = readfile(s:get_token_file(), "t", 3) endif if tokens == [] " If unsuccessful at reading token file, do the OAuth handshake. let [ retval, s:access_token, s:access_token_secret ] = s:do_oauth() if retval < 0 return [ "Error from do_oauth(): ".retval, '' ] endif if !s:get_disable_token_file() " Save access tokens to the token file. let v:errmsg = "" if writefile([ s:access_token, s:access_token_secret ], s:get_token_file()) < 0 call s:errormsg('Error writing token file: '.v:errmsg) endif endif else let [s:access_token, s:access_token_secret] = tokens endif endif let parms = copy(a:parms) let parms["oauth_token"] = s:access_token let oauth_hdr = s:getOauthResponse(a:url, a:parms == {} ? 'GET' : 'POST', parms, s:access_token_secret) return s:run_curl(a:url, oauth_hdr, a:proxy, a:proxylogin, a:parms) else if a:login != '' let login = s:get_twitvim_login_noerror() if login == '' return [ 'Login info not set. Please add to vimrc: let twitvim_login="USER:PASS"', '' ] endif else let login = a:login endif return s:run_curl(a:url, login, a:proxy, a:proxylogin, a:parms) endif endfunction " === End of OAuth code === " === Networking code === function! s:url_encode_char(c) let utf = iconv(a:c, &encoding, "utf-8") if utf == "" let utf = a:c endif let s = "" for i in range(strlen(utf)) let s .= printf("%%%02X", char2nr(utf[i])) endfor return s endfunction " URL-encode a string. function! s:url_encode(str) return substitute(a:str, '[^a-zA-Z0-9_.~-]', '\=s:url_encode_char(submatch(0))', 'g') endfunction " Use curl to fetch a web page. function! s:curl_curl(url, login, proxy, proxylogin, parms) let error = "" let output = "" let curlcmd = "curl -s -S " if s:get_twitvim_cert_insecure() let curlcmd .= "-k " endif if a:proxy != "" let curlcmd .= '-x "'.a:proxy.'" ' endif if a:proxylogin != "" if stridx(a:proxylogin, ':') != -1 let curlcmd .= '-U "'.a:proxylogin.'" ' else let curlcmd .= '-H "Proxy-Authorization: Basic '.a:proxylogin.'" ' endif endif if a:login != "" if a:login =~ "^OAuth " let curlcmd .= '-H "Authorization: '.a:login.'" ' elseif stridx(a:login, ':') != -1 let curlcmd .= '-u "'.a:login.'" ' else let curlcmd .= '-H "Authorization: Basic '.a:login.'" ' endif endif let got_json = 0 for [k, v] in items(a:parms) if k == '__json' let got_json = 1 let curlcmd .= '-d "'.substitute(v, '"', '\\"', 'g').'" ' else let curlcmd .= '-d "'.s:url_encode(k).'='.s:url_encode(v).'" ' endif endfor if got_json let curlcmd .= '-H "Content-Type: application/json" ' endif let curlcmd .= '-H "User-Agent: '.s:user_agent.'" ' let curlcmd .= '"'.a:url.'"' let output = system(curlcmd) let errormsg = s:xml_get_element(output, 'error') if v:shell_error != 0 let error = output elseif errormsg != '' let error = errormsg endif return [ error, output ] endfunction " Check if we can use Python. function! s:check_python() 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); } else { $ua->default_header('Authorization' => 'Basic '.make_base64($login)); } } $ua->default_header('User-Agent' => VIM::Eval("s:user_agent")); my $response; if (defined $parms{'__json'}) { $response = $ua->post($url, 'Content-Type' => 'application/json', Content => $parms{'__json'}); } else { $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 $output = $response->content; $output =~ s/'/''/g; VIM::DoCommand("let output ='$output'"); 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] if {[string tolower [string range $url 0 7]] == "https://"} { # Load and register support for https URLs. package require tls ::http::register https 443 ::tls::socket } 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]" } } lappend headers "User-Agent" [::vim::expr "s:user_agent"] set parms [list] set keys [split [::vim::expr "keys(a:parms)"] "\n"] if { [llength $keys] > 0 } { if { [lsearch -exact $keys "__json"] != -1 } { set query [::vim::expr "a:parms\['__json']"] lappend headers "Content-Type" "application/json" } else { 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'" set output [string map {' ''} $state(body)] ::vim::command "let output = '$output'" } 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 " We need to convert our parameters to UTF-8. In curl_curl() this is already " handled as part of our url_encode() function, so we only need to do this for " other net methods. Also, of course, we don't have to do anything if the " encoding is already UTF-8. function! s:iconv_parms(parms) if s:get_curl_method() == 'curl' || &encoding == 'utf-8' return a:parms endif let parms2 = {} for k in keys(a:parms) let v = iconv(a:parms[k], &encoding, 'utf-8') if v == '' let v = a:parms[k] endif let parms2[k] = v endfor return parms2 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, s:iconv_parms(a:parms)) endfunction function! s:reset_curl_method() unlet! s:curl_method endfunction function! s:show_curl_method() echo 'Net 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, retweeted_by_me, retweeted_to_me, favorites " 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. " view: viewport saved with winsaveview() " showheader: 1 if header is shown in this buffer, 0 if header is hidden. let s:curbuffer = {} " The info buffer record holds the following fields: " " buftype: profile, friends, followers, listmembers, listsubs, userlists, " userlistmem, userlistsubs, listinfo " next_cursor: Used for paging. " prev_cursor: Used for paging. " cursor: Used for refresh. " user: User name " list: List name " buffer: The buffer text. " view: viewport saved with winsaveview() " showheader: 1 if header is shown in this buffer, 0 if header is hidden. let s:infobuffer = {} " ptr = 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:bufstack = { 'ptr': -1, 'stack': [] } let s:infobufstack = { 'ptr': -1, 'stack': [] } " Maximum items in the buffer stack. Adding a new item after this limit will " get rid of the first item. let s:bufstackmax = 10 " Add current buffer to the buffer stack at the next position after current. " Remove all buffers after that. function! s:add_buffer(infobuf) let stack = a:infobuf ? s:infobufstack : s:bufstack let cur = a:infobuf ? s:infobuffer : s:curbuffer " If stack is already full, remove the buffer at the bottom of the stack to " make room. if stack.ptr >= s:bufstackmax call remove(stack.stack, 0) let stack.ptr -= 1 endif let stack.ptr += 1 " Suppress errors because there may not be anything to remove after current " position. silent! call remove(stack.stack, stack.ptr, -1) call add(stack.stack, cur) endfunction " Check if two buffers show the same info based on attributes. function! s:is_same(infobuf, a, b) let a = a:a let b = a:b if a:infobuf if a.buftype == b.buftype && a.cursor == b.cursor && a.user == b.user && a.list == b.list return 1 endif else if a.buftype == b.buftype && a.list == b.list && a.user == b.user && a.page == b.page return 1 endif endif return 0 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(infobuf) let stack = a:infobuf ? s:infobufstack : s:bufstack let cur = a:infobuf ? s:infobuffer : s:curbuffer let winname = a:infobuf ? s:user_winname : s:twit_winname if cur == {} return endif " Save buffer contents and cursor position. let twit_bufnr = bufwinnr('^'.winname.'$') if twit_bufnr > 0 let curwin = winnr() execute twit_bufnr . "wincmd w" let cur.buffer = getline(1, '$') let cur.view = winsaveview() execute curwin . "wincmd w" " If current buffer is the same type as buffer at the top of the stack, " then just copy it. if stack.ptr >= 0 && s:is_same(a:infobuf, cur, stack.stack[stack.ptr]) let stack.stack[stack.ptr] = deepcopy(cur) else " Otherwise, push the current buffer onto the stack. call s:add_buffer(a:infobuf) endif endif " If twit_bufnr returned -1, the user closed the window manually. So we " have nothing to save. Do not alter the buffer stack. endfunction " Go back one buffer in the buffer stack. function! s:back_buffer(infobuf) let stack = a:infobuf ? s:infobufstack : s:bufstack call s:save_buffer(a:infobuf) if stack.ptr < 1 call s:warnmsg("Already at oldest buffer. Can't go back further.") return -1 endif let stack.ptr -= 1 if a:infobuf let s:infobuffer = deepcopy(stack.stack[stack.ptr]) else let s:curbuffer = deepcopy(stack.stack[stack.ptr]) endif let cur = a:infobuf ? s:infobuffer : s:curbuffer let wintype = a:infobuf ? 'userinfo' : 'timeline' call s:twitter_wintext_view(cur.buffer, wintype, cur.view) return 0 endfunction " Go forward one buffer in the buffer stack. function! s:fwd_buffer(infobuf) let stack = a:infobuf ? s:infobufstack : s:bufstack call s:save_buffer(a:infobuf) if stack.ptr + 1 >= len(stack.stack) call s:warnmsg("Already at newest buffer. Can't go forward.") return -1 endif let stack.ptr += 1 if a:infobuf let s:infobuffer = deepcopy(stack.stack[stack.ptr]) else let s:curbuffer = deepcopy(stack.stack[stack.ptr]) endif let cur = a:infobuf ? s:infobuffer : s:curbuffer let wintype = a:infobuf ? 'userinfo' : 'timeline' call s:twitter_wintext_view(cur.buffer, wintype, cur.view) return 0 endfunction if !exists(":BackTwitter") command BackTwitter :call back_buffer(0) endif if !exists(":ForwardTwitter") command ForwardTwitter :call fwd_buffer(0) endif if !exists(":BackInfoTwitter") command BackInfoTwitter :call back_buffer(1) endif if !exists(":ForwardInfoTwitter") command ForwardInfoTwitter :call fwd_buffer(1) endif " For debugging. Show the buffer stack. function! s:show_bufstack(infobuf) let stack = a:infobuf ? s:infobufstack : s:bufstack for i in range(len(stack.stack) - 1, 0, -1) let s = i.':' let s .= ' type='.stack.stack[i].buftype let s .= ' user='.stack.stack[i].user let s .= ' list='.stack.stack[i].list if a:infobuf let s .= ' cursor='.stack.stack[i].cursor else let s .= ' page='.stack.stack[i].page endif echo s endfor endfunction if !exists(":TwitVimShowBufstack") command TwitVimShowBufstack :call show_bufstack(0) endif if !exists(":TwitVimShowInfoBufstack") command TwitVimShowInfoBufstack :call show_bufstack(1) endif " For debugging. Show curbuffer variable. if !exists(":TwitVimShowCurbuffer") command TwitVimShowCurbuffer :echo s:curbuffer endif " For debugging. Show infobuffer variable. if !exists(":TwitVimShowInfobuffer") command TwitVimShowInfobuffer :echo s:infobuffer 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" setlocal modifiable call append(insline - 1, line) execute "normal! ".insline."G" setlocal 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 != '' let errormsg = s:xml_get_element(output, 'error') call s:errormsg("Error posting your tweet: ".(errormsg != '' ? errormsg : 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 != '' let errormsg = s:xml_get_element(output, 'error') call s:errormsg("Error retweeting: ".(errormsg != '' ? errormsg : 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 != '' let errormsg = s:xml_get_element(output, 'error') call s:errormsg("Error getting in-reply-to tweet: ".(errormsg != '' ? errormsg : 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. setlocal modifiable call append(lineno, '+ '.line) setlocal 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 != '' let errormsg = s:xml_get_element(output, 'error') call s:errormsg("Error deleting ".obj.": ".(errormsg != '' ? errormsg : 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. setlocal modifiable normal! dd setlocal 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 " Fave or Unfave tweet on current line. function! s:fave_tweet(unfave) let id = get(s:curbuffer.statuses, line('.')) if id == 0 call s:warnmsg('Nothing to '.(a:unfave ? 'unfavorite' : 'favorite').' on current line.') return endif redraw echo (a:unfave ? 'Unfavoriting' : 'Favoriting') 'the tweet...' " favorites/create and favorites/destroy both require 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().'/favorites/'.(a:unfave ? 'destroy' : 'create').'/'.id.'.xml' let [error, output] = s:run_curl_oauth(url, s:ologin, s:get_proxy(), s:get_proxy_login(), parms) if error != '' let errormsg = s:xml_get_element(output, 'error') call s:errormsg("Error ".(a:unfave ? 'unfavoriting' : 'favoriting')." the tweet: ".(errormsg != '' ? errormsg : error)) return endif redraw echo 'Tweet' (a:unfave ? 'unfavorited.' : 'favorited.') 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