Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
12893 lines (12065 sloc) 464 KB
;;; twittering-mode.el --- Major mode for Twitter
;; Copyright (C) 2009-2015 Tadashi MATSUO
;; 2007, 2009-2011 Yuto Hayamizu.
;; 2008 Tsuyoshi CHO
;; 2014, 2015 Xavier Maillard
;; Author: Tadashi MATSUO <tad@mymail.twin.ne.jp>
;; Y. Hayamizu <y.hayamizu@gmail.com>
;; Tsuyoshi CHO <Tsuyoshi.CHO+develop@Gmail.com>
;; Alberto Garcia <agarcia@igalia.com>
;; Xavier Maillard <xavier@maillard.im>
;; Created: Sep 4, 2007
;; Version: HEAD
;; Identity: $Id$
;; Keywords: twitter web
;; URL: http://twmode.sf.net/
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; twittering-mode.el is a major mode for Twitter.
;; You can check friends timeline, and update your status on Emacs.
;;; Feature Request:
;; URL : http://twitter.com/d00dle/statuses/577876082
;; * Status Input from Popup buffer and C-cC-c to POST.
;; URL : http://code.nanigac.com/source/view/419
;; * update status for region
;;; Code:
(eval-when-compile (require 'cl)
(require 'easymenu))
(require 'xml)
(eval-and-compile
;; On byte-compilation, Emacs21 requires loading the libraries
;; distributed with twittering-mode.el for macros defined in them.
(when (> 22 emacs-major-version)
(setq load-path
(append (mapcar (lambda (dir)
(expand-file-name
dir
(if load-file-name
(or (file-name-directory load-file-name)
".")
".")))
'("url-emacs21" "emacs21"))
load-path))))
(when (> 22 emacs-major-version)
(and (require 'un-define nil t)
;; the explicitly require 'unicode to update a workaround with
;; navi2ch. see a comment of `twittering-ucs-to-char' for more
;; details.
(require 'unicode nil t))
(defadvice url-scheme-register-proxy (around twittering-fix-process-env (scheme) activate)
(let ((process-environment
(apply 'append
(let ((env-var (concat scheme "_proxy")))
(mapcar
(lambda (str)
(if (string-match
(concat "^\\("
(regexp-opt (list (upcase env-var)
(downcase env-var)))
"\\)=$")
str)
nil
(list str)))
process-environment)))))
ad-do-it)))
(require 'url)
(defgroup twittering-mode nil
"Settings for twittering-mode."
:link '(url-link "https://github.com/hayamiz/twittering-mode")
:prefix "twittering-"
:group 'hypermedia)
(defconst twittering-mode-version "HEAD")
(defconst twittering-mode-identity "$Id$")
(defvar twittering-api-host "api.twitter.com")
(defvar twittering-api-search-host "search.twitter.com")
(defvar twittering-web-host "twitter.com")
(defvar twittering-oauth-request-token-url-without-scheme
"://api.twitter.com/oauth/request_token")
(defvar twittering-oauth-authorization-url-base-without-scheme
"://api.twitter.com/oauth/authorize?oauth_token=")
(defvar twittering-oauth-access-token-url-without-scheme
"://api.twitter.com/oauth/access_token")
(defun twittering-mode-version ()
"Display a message for twittering-mode version."
(interactive)
(let ((version-string
(format "twittering-mode-v%s" twittering-mode-version)))
(if (interactive-p)
(message "%s" version-string)
version-string)))
(defcustom twittering-auth-method 'oauth
"*Authentication method to use with `twittering-mode'.
Choose between symbols `oauth' (default), `basic' or `xauth'.
OAuth Authentication requires `twittering-oauth-consumer-key' and
`twittering-oauth-consumer-secret'.
Additionally, it requires an external command `curl' or another
command included in `tls-program', which may be `openssl' or
`gnutls-cli', for SSL."
:group 'twittering-mode
:type '(choice :tag "Twitter authentication method"
(const :tag "Basic authentication" :value basic)
(const :tag "OAuth authentication" :value oauth)
(const :tag "xAuth authentication" :value xauth)))
(defvar twittering-account-authorization nil
"State of account authorization for `twittering-username' and
`twittering-password'. The value is one of the following symbols:
nil -- The account have not been authorized yet.
queried -- The authorization has been queried, but not finished yet.
authorized -- The account has been authorized.")
(defcustom twittering-oauth-use-ssl t
"*If non-nil, use SSL authentication for OAuth.
Twitter requires SSL on authorization via OAuth."
:group 'twittering-mode
:type 'boolean)
(defcustom twittering-oauth-invoke-browser nil
"*If non-nil, invoke a browser on authorization of access key automatically."
:type 'boolean
:group 'twittering-mode)
(defvar twittering-oauth-consumer-key nil)
(defvar twittering-oauth-consumer-secret nil)
(defvar twittering-oauth-access-token-alist nil)
(defconst twittering-max-number-of-tweets-on-retrieval 200
"The maximum number of `twittering-number-of-tweets-on-retrieval'.")
(defcustom twittering-number-of-tweets-on-retrieval 20
"*Number of tweets which will be retrieved in one request.
The upper limit is `twittering-max-number-of-tweets-on-retrieval'."
:type 'integer
:group 'twittering-mode)
(defcustom twittering-tinyurl-service 'tinyurl
"*Shorten URI service to use.
This must be one of key symbols of `twittering-tinyurl-services-map'.
To use bit.ly or j.mp services, you have to configure
`twittering-bitly-login' and `twittering-bitly-api-key'."
:type '(radio (symbol :tag "bit.ly"
:value bit.ly)
(symbol :tag "goo.gl"
:value goo.gl)
(symbol :tag "is.gd"
:value is.gd)
(symbol :tag "j.mp"
:value j.mp)
(symbol :tag "migre.me"
:value migre.me)
(symbol :tag "tinyurl"
:value tinyurl)
(symbol :tag "toly"
:value toly))
:group 'twittering-mode)
(defcustom twittering-tinyurl-services-map
'((bit.ly twittering-make-http-request-for-bitly
(lambda (service reply)
(if (string-match "\n\\'" reply)
(substring reply 0 (match-beginning 0))
reply)))
(goo.gl
(lambda (service longurl)
(twittering-make-http-request-from-uri
"POST" '(("Content-Type" . "application/json"))
"https://www.googleapis.com/urlshortener/v1/url"
(concat "{\"longUrl\": \"" longurl "\"}")))
(lambda (service reply)
(when (string-match "\"id\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\""
reply)
(match-string 1 reply))))
(is.gd . "http://is.gd/create.php?format=simple&url=")
(j.mp twittering-make-http-request-for-bitly
(lambda (service reply)
(if (string-match "\n\\'" reply)
(substring reply 0 (match-beginning 0))
reply)))
(migre.me . "http://migre.me/api.txt?url=")
(tinyurl . "http://tinyurl.com/api-create.php?url=")
(toly
(lambda (service longurl)
(twittering-make-http-request-from-uri
"POST" nil
"http://to.ly/api.php"
(concat "longurl=" (twittering-percent-encode longurl))))))
"Alist of URL shortening services.
The key is a symbol specifying the service.
The value is a string or a list consisting of two elements at most.
If the value is a string, `(concat THE-FIRST-ELEMENT longurl)' is used as the
URL invoking the service.
If the value is a list, it is interpreted as follows.
The first element specifies how to make a HTTP request for shortening a URL.
If the first element is a string, `(concat THE-FIRST-ELEMENT longurl)' is
used as the URL invoking the service.
If the first element is a function, it is called as `(funcall THE-FIRST-ELEMENT
service-symbol longurl)' to obtain a HTTP request alist for invoking the
service, which must be generated by `twittering-make-http-request'.
The second element specifies how to post-process a HTTP reply by the HTTP
request.
If the second element is nil, the reply is directly used as a shortened URL.
If the second element is a function, it is called as `(funcall
THE-SECOND-ELEMENT service-symbol HTTP-reply-string)' and its result is used
as a shortened URL."
:type 'alist
:group 'twittering-mode)
(defcustom twittering-bitly-login nil
"*The login name for URL shortening service bit.ly and j.mp."
:type '(choice (const nil)
string)
:group 'twittering-mode)
(defcustom twittering-bitly-api-key nil
"*API key for `bit.ly' and `j.mp' URL shortening services."
:type '(choice (const nil)
string)
:group 'twittering-mode)
(defvar twittering-mode-map (make-sparse-keymap))
(defvar twittering-mode-menu-on-uri-map (make-sparse-keymap "Twittering Mode"))
(defvar twittering-mode-on-uri-map (make-sparse-keymap))
(defvar twittering-tweet-history nil)
(defvar twittering-user-history nil)
(defvar twittering-timeline-history nil)
(defvar twittering-hashtag-history nil)
(defvar twittering-search-history nil)
(defvar twittering-current-hashtag nil
"A hash tag string currently set. You can set it by calling
`twittering-set-current-hashtag'.")
(defvar twittering-timer nil
"Timer object for timeline refreshing will be stored here.
DO NOT SET VALUE MANUALLY.")
(defcustom twittering-timer-interval 90
"Number of seconds to wait before an auto-reload occurs.
Number of API calls per hour is limited so this value should be 60 or more."
:type 'integer
:group 'twittering-mode)
(defvar twittering-timer-for-redisplaying nil
"Timer object for timeline redisplay statuses will be stored here.
DO NOT SET VALUE MANUALLY.")
(defvar twittering-timer-interval-for-redisplaying 5.0
"The interval of auto redisplaying statuses.
Each time Emacs remains idle for the interval, twittering-mode updates parts
requiring to be redrawn.")
(defcustom twittering-username nil
"*A username of your Twitter account."
:type '(choice (const nil)
string)
:group 'twittering-mode)
(defcustom twittering-password nil
"*A password of your Twitter account. Leave it blank is the
recommended way because writing a password in .emacs file is so
dangerous."
:type '(choice (const nil)
string)
:group 'twittering-mode)
(defcustom twittering-initial-timeline-spec-string ":home"
"*An initial timeline spec string or a list of timeline spec strings.
This specifies one or more initial timeline spec strings, which are
automatically visited when invoking `twittering-mode' or `twit'.
If it is a string, it specifies a timeline spec string.
If it is a list of strings, it specifies multiple timeline spec strings."
:type '(choice (const nil)
string
(repeat string))
:group 'twittering-mode)
(defvar twittering-timeline-spec nil
"The timeline spec for the current buffer.")
(defvar twittering-timeline-spec-string ""
"The timeline spec string for the current buffer.")
(defcustom twittering-timeline-spec-alias nil
"*Alist for aliases of timeline spec.
Each element is (NAME . SPEC-STRING), where NAME is a string and
SPEC-STRING is a string or a function that returns a timeline spec string.
The alias can be referred as \"$NAME\" or \"$NAME(ARG)\" in timeline spec
string. If SPEC-STRING is a string, ARG is simply ignored.
If SPEC-STRING is a function, it is called with a string argument.
For the style \"$NAME\", the function is called with nil.
For the style \"$NAME(ARG)\", the function is called with a string ARG.
For example, if you specify
`((\"FRIENDS\" . \"my-account/friends-list\")
(\"related-to\" .
,(lambda (username)
(if username
(format \":search/to:%s OR from:%s OR @%s/\"
username username username)
\":home\")))),
then you can use \"$FRIENDS\" and \"$related-to(USER)\" as
\"my-account/friends-list\" and \":search/to:USER OR from:USER OR @USER/\",
respectively."
:type 'alist
:group 'twittering-mode)
(defvar twittering-current-timeline-spec-string nil
"The current timeline spec string. This variable should not be referred
directly. Use `twittering-current-timeline-spec-string' or
`twittering-current-timeline-spec'.")
(defvar twittering-list-index-retrieved nil)
(defvar twittering-process-info-alist nil
"Alist of active process and timeline spec retrieved by the process.")
(defvar twittering-server-info-alist nil
"Alist of server information.")
(defvar twittering-api-limit-info-alist '()
"Alist of an API identifier and an alist representing rate limit for the API.")
(defvar twittering-timeline-spec-to-api-table '()
"Alist of a timeline spec and an API identifier for retrieving the timeline.")
(defcustom twittering-mode-init-hook nil
"*Hook run after initializing global variables for `twittering-mode'."
:type 'hook
:group 'twittering-mode)
(defcustom twittering-mode-hook nil
"*Hook run every time a buffer is initialized as a `twittering-mode' buffer."
:type 'hook
:group 'twittering-mode)
(defvar twittering-cookie-alist nil
"Alist for stroing cookies for each account.
This variable stores an alist.
A key of the alist is a string that is a screen name of an account.
A value of the alist is a cookie alist which corresponds to a list of
a pair of a cookie name and value.")
(defvar twittering-new-tweets-count 0
"Number of new tweets when `twittering-new-tweets-hook' is run.")
(defvar twittering-new-tweets-spec nil
"Timeline spec, which new tweets belong to, when
`twittering-new-tweets-hook' is run.")
(defvar twittering-new-tweets-statuses nil
"New tweet status messages, when
`twittering-new-tweets-hook' is run.")
(defcustom twittering-new-tweets-hook nil
"*Hook run when new tweets are received.
You can read `twittering-new-tweets-count' or `twittering-new-tweets-spec'
to get the number of new tweets received when this hook is run."
:type 'hook
:group 'twittering-mode)
(defvar twittering-rendered-new-tweets-spec nil
"A timeline spec of newly rendered tweets.
This variable is bound when invoking hooks registered with
`twittering-new-tweets-rendered-hook'.")
(defvar twittering-rendered-new-tweets-spec-string nil
"A timeline spec string of newly rendered tweets.
This variable is bound when invoking hooks registered with
`twittering-new-tweets-rendered-hook'.")
(defvar twittering-rendered-new-tweets nil
"A list of newly rendered tweets.
Hooks registered with `twittering-new-tweets-rendered-hook' can use this
variable as a list of rendered tweets. Each tweet is represented as an alist.
You can refer to a property of a tweet alist as
(cdr (assq PROPERTY-SYMBOL TWEET-ALIST)).
Valid symbols are following; id, text, user-name, user-screen-name, user-id,
source, source-uri.
In the list, tweets are placed in order of time. The car of the list is the
latest one, and the last is the oldest one.")
(defcustom twittering-new-tweets-rendered-hook nil
"*Hook run when new tweets are rendered.
When the registered functions are called, the current buffer is the buffer
that the new tweets are just rendered on.
The functions can refer to the timeline spec and timeline spec string as
`twittering-rendered-new-tweets-spec' and
`twittering-rendered-new-tweets-spec-string', repectively.
Hooks can also use the local variable `twittering-rendered-new-tweets' as a
list of rendered tweets.
For the detail of the representation of tweets, see the variable
`twittering-rendered-new-tweets'."
:type 'hook
:group 'twittering-mode)
(defvar twittering-active-mode nil
"Non-nil if new statuses should be retrieved periodically.
Do not modify this variable directly. Use `twittering-activate-buffer',
`twittering-deactivate-buffer', `twittering-toggle-activate-buffer' or
`twittering-set-active-flag-for-buffer'.")
(defvar twittering-jojo-mode nil)
(defcustom twittering-reverse-mode nil
"*Non-nil means tweets are aligned in reverse order of `http://twitter.com/'."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-display-remaining nil
"*If non-nil, display remaining of rate limit on the mode-line."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-display-connection-method t
"*If non-nil, display the current connection method on the mode-line."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-status-format "%RT{%FACE[bold]{RT}}%i %s, %@:\n%FOLD[ ]{%T // from %f%L%r%R%QT{\n+----\n%FOLD[|]{%i %s, %@:\n%FOLD[ ]{%T // from %f%L%r%R}}\n+----}}\n "
"Format string for rendering statuses.
Ex. \"%i %s, %@:\\n%FILL{ %T // from %f%L%r%R}\n \"
Items:
%s - screen_name
%S - name
%i - profile_image
%d - description
%l - location
%L - \" [location]\"
%r - \" sent to user\" (use on direct_messages{,_sent})
%r - \" in reply to user\" (use on other standard timeline)
%R - \" (retweeted by user)\"
%RT{...} - strings rendered only when the tweet is a retweet.
The braced strings are rendered with the information of the
retweet itself instead of that of the retweeted original tweet.
For example, %s for a retweet means who posted the original
tweet, but %RT{%s} means who retweeted it.
%QT{...} - strings rendered only when the tweet quotes a tweet.
The braced strings are rendered with the information of the
quoted tweet. For example, %QT{%s} means the author of the
quoted tweet.
%u - url
%j - user.id
%p - protected?
%c - created_at (raw UTC string)
%C{time-format-str} - created_at (formatted with time-format-str)
%@{time-format-str} - X seconds ago (formatted with time-format-str)
%T - raw text
%t - text filled as one paragraph
%' - truncated
%FACE[face-name]{...} - strings decorated with the specified face.
%FIELD[format-str]{field-name}
- a value of the given field of a tweet formatted with format-str.
The format-str is optional. As a field-name, you can use
\"retweet_count\", \"favorite_count\" and so on.
%FIELD-IF-NONZERO[format-str]{field-name}
- similar to %FIELD[...]{...} except that this makes an empty string
if the field value is zero.
%FILL[prefix]{...} - strings filled as a paragraph. The prefix is optional.
You can use any other specifiers in braces.
%FOLD[prefix]{...} - strings folded within the frame width.
The prefix is optional. This keeps newlines and does not
squeeze a series of white spaces.
You can use any other specifiers in braces.
%f - source
%# - id"
:type 'string
:group 'twittering-mode)
(defcustom twittering-retweet-format '(nil _ " RT: %t (via @%s)")
"*A format string or a skeleton for retweet.
If the value is a string, it means a format string for generating an initial
string of a retweet. The format string is converted with the below replacement
table. And then, the cursor is placed on the next of the initial string.
It is equivalent to the skeleton '(nil STRING _).
Note that this string is inserted before the edit skeleton specified by
`twittering-edit-skeleton' is performed.
If the value is a list, it is treated as a skeleton used with
`skeleton-insert'. The strings included in the list are converted with the
following replacement table. And then, the list with converted strings is
inserted by `skeleton-insert'.
Note that this skeleton is performed before the edit skeleton specified by
`twittering-edit-skeleton' is performed.
Replacement table:
%s - The screen-name of the cited tweet.
%t - The text of the cited tweet.
%u - The URL of the cited tweet.
%# - The ID of the cited tweet.
%% - % itself."
:type 'sexp
:group 'twittering-mode)
(defcustom twittering-fill-column nil
"*The `fill-column' used for \"%FILL{...}\" in `twittering-status-format'.
If nil, the fill-column is automatically calculated."
:type '(choice (const nil)
integer)
:group 'twittering-mode)
(defcustom twittering-show-replied-tweets t
"*The number of replied tweets which will be showed in one tweet.
If the value is not a number and is non-nil, show all replied tweets
which is already fetched.
If the value is nil, doesn't show replied tweets."
:type '(choice (const :tag "Do not show replied tweets"
:value nil)
(const :tag "Show all replied tweets"
:value t)
(integer :tag "Number of replied tweet"))
:group 'twittering-mode)
(defcustom twittering-default-show-replied-tweets nil
"*The number of default replied tweets which will be shown in one tweet.
This value will be used only when showing new tweets.
See `twittering-show-replied-tweets' for more details."
:type '(choice (const nil)
integer)
:group 'twittering-mode)
(defcustom twittering-disable-overlay-on-too-long-string nil
"*If non-nil, disable overlay on too long string on edit buffer.
If nil, `twittering-edit-mode' puts an overlay `twittering-warning-overlay' on
characters exceeding the maximum length.
On some environments, some input methods seem to interfere the update of the
overlay. In such case, you may avoid the problems by setting this variable to
non-nil."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-use-show-minibuffer-length t
"*Show current length of minibuffer if this variable is non-nil.
We suggest that you should set to nil to disable the showing function
when it conflict with your input method (such as AquaSKK, etc.)"
:type 'boolean
:group 'twittering-mode)
(defvar twittering-notify-successful-http-get t)
(defcustom twittering-use-ssl t
"*Use SSL connection if this variable is non-nil.
SSL connections use an external command as a backend."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-allow-insecure-server-cert nil
"*If non-nil, `twittering-mode' allows insecure server certificates."
:type 'boolean
:group 'twittering-mode)
(defvar twittering-curl-program nil
"Cache a result of `twittering-find-curl-program'.
DO NOT SET VALUE MANUALLY.")
(defvar twittering-curl-program-https-capability nil
"Cache a result of `twittering-start-http-session-curl-https-p'.
DO NOT SET VALUE MANUALLY.")
(defvar twittering-curl-program-http2-capability nil
"Cache a result of `twittering-start-http-session-curl-http2-p'.
DO NOT SET VALUE MANUALLY.")
(defvar twittering-wget-program nil
"Cache a result of `twittering-find-wget-program'.
DO NOT SET VALUE MANUALLY.")
(defcustom twittering-tls-program nil
"*List of strings containing commands to start TLS stream to a host.
Each entry in the list is tried until a connection is successful.
%h is replaced with server hostname, %p with port to connect to.
Also see `tls-program'.
If nil, this is initialized with a list of valied entries extracted from
`tls-program'."
:type '(repeat string)
:group 'twittering-mode)
(defcustom twittering-connection-type-order
'(curl wget urllib-http native urllib-https)
"*A list of connection methods in the preferred order."
:type 'list
:group 'twittering-mode)
(defun twittering-connection-build-customize-option ()
"Generate a valid `defcustom' entry to build `twittering-connection-type-table' variable."
(list 'repeat
(list
'cons :tag "Connection"
'(symbol :tag "Name" :value "")
'(repeat
:tag "Connection method definition"
(choice
(cons :tag "Check test method"
(const :format "" check)
(choice :value t (const :tag "Do not check" t)
(function :tag "Check function")))
(cons :tag "Display name"
(const :format "" display-name)
string)
(cons :tag "HTTPS connection method"
(const :format "" https)
(choice :value nil (const :tag "None" nil)
(const :tag "True" t)
(function :tag "HTTPS test function")))
(cons :tag "Send HTTP request function"
(const :format "" send-http-request)
function)
(cons :tag "Pre process buffer"
(const :format "" pre-process-buffer)
function))))))
(defcustom twittering-connection-type-table
'((native
(check . t)
(send-http-request . twittering-send-http-request-native)
(pre-process-buffer . twittering-pre-process-buffer-native))
(curl
(check . twittering-start-http-session-curl-p)
(https . twittering-start-http-session-curl-https-p)
(send-http-request . twittering-send-http-request-curl)
(pre-process-buffer . twittering-pre-process-buffer-curl))
(wget
(check . twittering-start-http-session-wget-p)
(https . ignore)
(send-http-request . twittering-send-http-request-wget)
(pre-process-buffer . twittering-pre-process-buffer-wget))
(urllib-http
(display-name . "urllib")
(check . twittering-start-http-session-urllib-p)
(https . nil)
(send-http-request . twittering-send-http-request-urllib)
(pre-process-buffer . twittering-pre-process-buffer-urllib))
(urllib-https
(display-name . "urllib")
(check . twittering-start-http-session-urllib-p)
(https . twittering-start-http-session-urllib-https-p)
(send-http-request . twittering-send-http-request-urllib)
(pre-process-buffer . twittering-pre-process-buffer-urllib)))
"A list of alist of connection methods."
:group 'twittering-mode
:type (twittering-connection-build-customize-option))
(defvar twittering-format-status-function-source ""
"The status format string that has generated the current
`twittering-format-status-function'.")
(defvar twittering-format-status-function nil
"The formating function generated from `twittering-format-status-function-source'.")
(defvar twittering-format-status-function-without-compile nil
"The formating function generated from `twittering-format-status-function-source',
which is a lambda expression without being compiled.")
(defvar twittering-timeline-data-table (make-hash-table :test 'equal))
(defcustom twittering-username-face 'twittering-username-face
"*Face used to display USERNAME."
:type 'face
:group 'twittering-mode)
(defcustom twittering-uri-face 'twittering-uri-face
"*Face used to display URIs."
:type 'face
:group 'twittering-mode)
(defcustom twittering-use-native-retweet nil
"*If non-nil, post retweet using native retweets."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-update-status-function
'twittering-update-status-from-pop-up-buffer
"*The function which is used to post a tweet.
It takes the following 5 arguments, INIT-STR, REPLY-TO-ID, USERNAME,
TWEET-TYPE and CURRENT-SPEC.
The first argument INIT-STR is nil or an initial text to be edited.
REPLY-TO-ID and USERNAME are an ID and a user-screen-name of a tweet to
which you are going to reply. If the tweet is not a reply, they are nil.
TWEET-TYPE is a symbol specifying a type of a tweet being edited. It must
be one of 'direct-message, 'normal, 'organic-retweet and 'reply.
CURRENT-SPEC means on which timeline the function is called.
Twittering-mode provides two functions for updating status:
* `twittering-update-status-from-minibuffer': edit tweets in minibuffer
* `twittering-update-status-from-pop-up-buffer': edit tweets in pop-up buffer"
:type '(choice (const :tag "built-in: from minibuffer"
twittering-update-status-from-minibuffer)
(const :tag "built-in: from a popup buffer"
twittering-update-status-from-pop-up-buffer)
(function :tag "Your own function"))
:group 'twittering-mode)
(defcustom twittering-request-confirmation-on-posting nil
"*If non-nil, confirmation will be requested on posting a tweet edited in
pop-up buffer."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-use-master-password nil
"*If non-nil, store private information encrypted with a master password."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-private-info-file (expand-file-name "~/.twittering-mode.gpg")
"*File for storing encrypted private information.
Only used when `twittering-use-master-password' is non-nil."
:group 'twittering-mode
:type 'file)
(defvar twittering-private-info-file-loaded nil
"Whether the private info file has been loaded or not.")
(defvar twittering-variables-stored-with-encryption
'(twittering-oauth-access-token-alist))
(defvar twittering-api-prefix "1/")
(defvar twittering-search-api-method "search")
(defvar twittering-web-path-prefix "")
(defconst twittering-service-method-table
'((twitter (status-url . twittering-get-status-url-twitter)
(search-url . twittering-get-search-url-twitter))
(twitter-api-v1.1
(status-url . twittering-get-status-url-twitter)
(search-url . twittering-get-search-url-twitter))
(statusnet (status-url . twittering-get-status-url-statusnet)
(search-url . twittering-get-search-url-statusnet)))
"A list of alist of service methods.")
(defcustom twittering-service-method 'twitter-api-v1.1
"*Service method for `twittering-mode'.
The symbol `twitter' means Twitter Service.
The symbol `statusnet' means StatusNet Service.
Default to `twitter-api-v1.1' which is an alias for `twitter'.
See also `twittering-service-method-table'."
:type (if (> (length (mapcar #'car twittering-service-method-table)) 0)
`(choice ,@(mapcar (lambda (entry) `(const ,(car entry)))
twittering-service-method-table))
'symbol)
:group 'twittering-mode)
(defcustom twittering-timeline-header-face 'twittering-timeline-header-face
"*Face for the header on `twittering-mode'.
The face is used for rendering `twittering-timeline-header'."
:type 'face
:group 'twittering-mode)
(defcustom twittering-timeline-footer-face 'twittering-timeline-footer-face
"*Face for the footer on `twittering-mode'.
The face is used for rendering `twittering-timeline-footer'."
:type 'face
:group 'twittering-mode)
(defcustom twittering-timeline-header "-- Press Enter here to update --\n"
"*Timeline header string on `twittering-mode'.
The string is rendered on the beginning of a `twittering-mode' buffer.
Its face is specified by `twittering-timeline-header-face'."
:type 'string
:group 'twittering-mode)
(defcustom twittering-timeline-footer "-- Press Enter here to update --"
"*Timeline footer string on `twittering-mode'.
The string is rendered on the end of a `twittering-mode' buffer.
Its face is specified by `twittering-timeline-footer-face'."
:type 'string
:group 'twittering-mode)
(defcustom twittering-pop-to-buffer-function
'twittering-pop-to-buffer-in-bottom-largest-window
"*Function being invoked by `twittering-pop-to-buffer'.
It will receive an argument, the buffer being selected.
For example, the following functions can be used; `pop-to-buffer',
`twittering-pop-to-buffer-simple',
`twittering-pop-to-buffer-in-current-window',
`twittering-pop-to-buffer-in-largest-window', and
`twittering-pop-to-buffer-in-bottom-largest-window'."
:type 'function
:group 'twittering-mode)
;; FIXME: change to something better than alist
(defcustom twittering-relative-retrieval-interval-alist
'(("\\`:direct.*\\'" 4)
(":home" ":mentions" 1)
(t 1))
"*An alist of relative intervals of retrieving timelines.
Each element looks like (TIMELINE-SPEC-REGEXP RELATIVE-INTERVAL).
TIMELINE-SPEC-REGEXP must be t or a regexp string specifying primary
timeline specs.
If TIMELINE-SPEC-REGEXP is t, it matches all timelines.
RELATIVE-INTERVAL must be zero or a positive integer specifying relative
interval of retrieving timelines that match TIMELINE-SPEC-REGEXP.
An interval for a timeline is determined as follows;
1. Find the first element where TIMELINE-SPEC-REGEXP matches the
timeline or TIMELINE-SPEC-REGEXP is t.
If no elements are found, the interval is `twittering-timer-interval'.
2. Check the RELATIVE-INTERVAL of the element.
If RELATIVE-INTERVAL is a positive integer, the interval is
RELATIVE-INTERVAL times as long as `twittering-timer-interval'.
If RELATIVE-INTERVAL is zero, the interval is infinity.
The timeline is not retrieved automatically."
:type 'alist
:group 'twittering-mode)
(defvar twittering-relative-retrieval-count-alist '()
"An alist for counting retrieval of primary timelines.")
(defvar twittering-filter-alist '()
"*An alist of hidden tweet patterns for each primary timeline.
Each element looks like:
(TIMELINE-SPECIFIER (SYM1 . REGEXP1) (SYM2 . REGEXP2) ...).
TIMELINE-SPECIFIER must be a string or a list of strings.
Each string is a regexp for specifying primary timelines.
Note that you cannot specify composite timelines such as \":merge\",
\":exclude-if\" or \":exclude-re\".
Following regexps (REGEXP1, REGEXP2, ...) specify which tweet should
be hidden in a certain timeline.
In a timeline that matches TIMELINE-SPECIFIER, a tweet is hidden if
its elements specified by SYM1, SYM2, ... match corresponding REGEXP1, REGEXP2,
... respectively.
If a timeline matches multiple specifiers, all regexps of matched elements
are effective.
For example, if you specify
'(((\":home\" \":mentions\") (text . \"http://\"))
(\"^[^:]\" (text . \"sample\") (user-screen-name . \"\\`FOO\\'\"))
(\"twitter/.*\" (text . \"^aa\"))),
the following tweets are hidden.
- tweets including \"http://\" in the home timeline and the mentions timeline,
- tweets that are posted by the user FOO and include \"sample\"
in user timelines and list timelines,
- tweets including \"aa\" at a beginning of a line in list timelines of
twitter, such as \"twitter/media\" or \"twitter/support\".")
;;;;
;;;; Macro and small utility function
;;;;
(defun assocref (item alist)
(cdr (assoc item alist)))
(defmacro list-push (value listvar)
`(setq ,listvar (cons ,value ,listvar)))
(defmacro case-string (str &rest clauses)
`(cond
,@(mapcar
(lambda (clause)
(let ((keylist (car clause))
(body (cdr clause)))
`(,(if (listp keylist)
`(or ,@(mapcar (lambda (key) `(string-equal ,str ,key))
keylist))
't)
,@body)))
clauses)))
(defmacro twittering-wait-while (timeout interval condition &optional form &rest timeout-forms)
"Wait while CONDITION returns non-nil until TIMEOUT seconds passes.
The form CONDITION is repeatedly evaluated for every INTERVAL seconds
until CONDITION returns nil or TIMEOUT seconds passes unless TIMEOUT is nil.
If TIMEOUT is nil, there is no time limit.
If CONDITION returns nil, evaluate the form FORM and return its value.
If TIMEOUT seconds passes, evaluate the forms TIMEOUT-FORMS and return
the value of the last form in TIMEOUT-FORMS."
`(lexical-let (,@(when timeout `((timeout ,timeout)))
(interval ,interval)
(current 0.0))
(while (and ,@(when timeout '((< current timeout)))
,condition)
(sleep-for interval)
(setq current (+ current interval)))
,(when (or form timeout-forms)
(if (null timeout)
form
`(if (< current timeout)
,form
,@timeout-forms)))))
(defun twittering-extract-matched-substring-all (regexp str)
(let ((pos 0)
(result nil))
(while (string-match regexp str pos)
(setq result (cons (match-string 1 str) result))
(setq pos (match-end 0)))
(reverse result)))
(defun twittering-process-alive-p (proc)
"Return non-nil if PROC is alive."
(not (memq (process-status proc) '(nil closed exit failed signal))))
(defun twittering-start-process-with-sentinel (name buffer program args sentinel)
"Start a program in a subprocess with a sentinel.
This function is the same as `start-process' except that SENTINEL must
be invoked when the process is successfully started."
(let ((proc (apply 'start-process name buffer program args)))
(when (and proc (functionp sentinel))
(if (twittering-process-alive-p proc)
(set-process-sentinel proc sentinel)
;; Ensure that the sentinel is invoked if a subprocess is
;; successfully started.
(funcall sentinel proc "finished")))
proc))
(defun twittering-parse-time-string (str &optional round-up)
"Parse the time-string STR into (SEC MIN HOUR DAY MON YEAR DOW DST TZ).
This function is the same as `parse-time-string' except to complement the
lacked parameters with the current time.
If ROUND-UP is nil, complement the lacked parameters with the oldest ones.
If ROUND-UP is non-nil, complement the lacked parameters with the latest ones.
For example, (twittering-parse-time-string \"2012-04-20\")
returns (0 0 0 20 4 2012 nil nil 32400).
And (twittering-parse-time-string \"2012-04-20\" t)
returns (59 59 23 20 4 2012 nil nil 32400).
The values are identical to those of `decode-time', but any values that are
unknown are returned as nil."
(let* ((parsed (parse-time-string str))
(current (decode-time (current-time)))
(replacement-alist
`((SEC . ,(if round-up
59
0))
(MIN . ,(if round-up
59
0))
(HOUR . ,(if round-up
23
0))
(DAY . nil)
(MON . nil)
(YEAR . nil)
(DOW . nil)
(DST . nil)
(TZ . nil)))
(sym-list (mapcar 'car replacement-alist))
(result nil))
(while (and parsed current sym-list)
(let* ((sym (car sym-list))
(v (or (car parsed)
(cdr (assq sym replacement-alist))
;; If `sym' is not 'DOW and it is bound to nil
;; in `replacement-alist', use `current'.
(unless (eq sym 'DOW)
(car current)))))
(setq result (cons v result)))
(setq parsed (cdr parsed))
(setq current (cdr current))
(setq sym-list (cdr sym-list)))
(reverse result)))
(defun twittering-normalize-string (str)
(if (require 'ucs-normalize nil t)
(ucs-normalize-NFC-string str)
str))
;;;;
;;;; Utility for portability
;;;;
(defun twittering-remove-duplicates (list)
"Return a copy of LIST with all duplicate elements removed.
This is non-destructive version of `delete-dups' which is not
defined in Emacs21."
(if (fboundp 'delete-dups)
(delete-dups (copy-sequence list))
(let ((rest list)
(result nil))
(while rest
(unless (member (car rest) result)
(setq result (cons (car rest) result)))
(setq rest (cdr rest)))
(nreverse result))))
(defun twittering-completing-read (prompt collection &optional predicate require-match initial-input hist def inherit-input-method)
"Read a string in the minibuffer, with completion.
This is a modified version of `completing-read' and accepts candidates
as a list of a string on Emacs21."
;; completing-read() of Emacs21 does not accepts candidates as
;; a list. Candidates must be given as an alist.
(let* ((collection (twittering-remove-duplicates collection))
(collection
(if (and (> 22 emacs-major-version)
(listp collection)
(stringp (car collection)))
(mapcar (lambda (x) (cons x nil)) collection)
collection)))
(completing-read prompt collection predicate require-match
initial-input hist def inherit-input-method)))
(defun twittering-add-to-history (history-var elt &optional maxelt keep-all)
(if (functionp 'add-to-history)
(add-to-history history-var elt maxelt keep-all)
(let* ((added (cons elt
(if (and (not keep-all)
(boundp 'history-delete-duplicates)
history-delete-duplicates)
(delete elt (symbol-value history-var))
(symbol-value history-var))))
(maxelt (or maxelt history-length))
(len (length added)))
(set history-var
(if (<= len maxelt)
added
(butlast added (- len maxelt)))))))
(if (fboundp 'assoc-string)
(defalias 'twittering-assoc-string 'assoc-string)
(defun twittering-assoc-string (key list &optional case-fold)
"Like `assoc' but specifically for strings (and symbols).
This returns the first element of LIST whose car matches the string or
symbol KEY, or nil if no match exists. When performing the
comparison, symbols are first converted to strings, and unibyte
strings to multibyte. If the optional arg CASE-FOLD is non-nil, case
is ignored.
Unlike `assoc', KEY can also match an entry in LIST consisting of a
single string, rather than a cons cell whose car is a string.
This is reimplemented version of `assoc-string' which is not
defined in Emacs21."
(let* ((key (if (stringp key)
key
(symbol-name key)))
(regexp (concat "\\`" key "\\'"))
(rest list)
(result nil)
(case-fold-search case-fold))
(while (not (null rest))
(let* ((current (car rest))
(current-key
(if (listp current)
(car current)
current))
(current-key
(if (stringp current-key)
current-key
(symbol-name current-key))))
(if (string-match key current-key)
(setq result current
rest nil)
(setq rest (cdr rest)))))
result)))
;;;;
;;;; Debug mode
;;;;
(defvar twittering-debug-mode nil)
(defvar twittering-debug-buffer "*debug*")
(defun twittering-get-or-generate-buffer (buffer)
(if (bufferp buffer)
(if (buffer-live-p buffer)
buffer
(generate-new-buffer (buffer-name buffer)))
(if (stringp buffer)
(or (get-buffer buffer)
(generate-new-buffer buffer)))))
(defun twittering-debug-buffer ()
(twittering-get-or-generate-buffer twittering-debug-buffer))
(defmacro debug-print (obj)
(let ((obsym (gensym)))
`(let ((,obsym ,obj))
(if twittering-debug-mode
(with-current-buffer (twittering-debug-buffer)
(insert "[debug] " (prin1-to-string ,obsym))
(newline)
,obsym)
,obsym))))
(defun debug-printf (fmt &rest args)
(when twittering-debug-mode
(with-current-buffer (twittering-debug-buffer)
(insert "[debug] " (apply 'format fmt args))
(newline))))
(defun twittering-debug-mode ()
(interactive)
(setq twittering-debug-mode
(not twittering-debug-mode))
(message (if twittering-debug-mode "debug mode:on" "debug mode:off")))
;;;;
;;;; Proxy setting / functions
;;;;
(defgroup twittering-proxy nil
"Subgroup handling `twittering-mode' proxy setup."
:group 'twittering-mode)
(defcustom twittering-proxy-use nil
"*If non-nil, use PROXY.
See also `twittering-proxy-server' for documentation."
:type 'boolean
:group 'twittering-mode)
(defcustom twittering-proxy-server nil
"*Proxy server for `twittering-mode'.
If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS.
To use individual proxies for HTTP and HTTPS, both `twittering-proxy-server'
and `twittering-proxy-port' must be nil."
:group 'twittering-proxy
:type '(choice (const nil) string))
(defcustom twittering-proxy-port nil
"*Port number for `twittering-mode'.
If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS.
To use individual proxies for HTTP and HTTPS, both `twittering-proxy-server'
and `twittering-proxy-port' must be nil."
:group 'twittering-proxy
:type '(choice (const nil)
integer))
(defvar twittering-proxy-keep-alive nil)
(defcustom twittering-proxy-user nil
"*Username for `twittering-proxy-server'.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS.")
(defcustom twittering-proxy-password nil
"*Password for `twittering-proxy-server'.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
string))
(defcustom twittering-http-proxy-server nil
"*HTTP proxy server for `twittering-mode'.
If nil, it is initialized on entering `twittering-mode'.
The port number is specified by `twittering-http-proxy-port'.
For HTTPS connection, the proxy specified by `twittering-https-proxy-server'
and `twittering-https-proxy-port' is used.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
string))
(defcustom twittering-http-proxy-port nil
"*Port number of a HTTP proxy server for `twittering-mode'.
If nil, it is initialized on entering `twittering-mode'.
The server is specified by `twittering-http-proxy-server'.
For HTTPS connection, the proxy specified by `twittering-https-proxy-server'
and `twittering-https-proxy-port' is used.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
integer))
(defcustom twittering-http-proxy-keep-alive nil
"*If non-nil, the Keep-alive is enabled. This is experimental."
:group 'twittering-proxy
:type 'boolean)
(defcustom twittering-http-proxy-user nil
"*Username for `twittering-http-proxy-server'.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
string))
(defcustom twittering-http-proxy-password nil
"*Password for `twittering-http-proxy-server'.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
string))
(defcustom twittering-https-proxy-server nil
"*HTTPS proxy server for `twittering-mode'.
If nil, it is initialized on entering `twittering-mode'.
The port number is specified by `twittering-https-proxy-port'.
For HTTP connection, the proxy specified by `twittering-http-proxy-server'
and `twittering-http-proxy-port' is used.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
string))
(defcustom twittering-https-proxy-port nil
"*Port number of a HTTPS proxy server for `twittering-mode'.
If nil, it is initialized on entering `twittering-mode'.
The server is specified by `twittering-https-proxy-server'.
For HTTP connection, the proxy specified by `twittering-http-proxy-server'
and `twittering-http-proxy-port' is used.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
integer))
(defcustom twittering-https-proxy-keep-alive nil
"*If non-nil, the Keep-alive is enabled. This is experimental."
:group 'twittering-proxy
:type 'boolean)
(defcustom twittering-https-proxy-user nil
"*Username for `twittering-https-proxy-server'.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
string))
(defcustom twittering-https-proxy-password nil
"*Password for `twittering-https-proxy-server'.
NOTE: If both `twittering-proxy-server' and `twittering-proxy-port' are
non-nil, the variables `twittering-proxy-*' have priority over other
variables `twittering-http-proxy-*' or `twittering-https-proxy-*'
regardless of HTTP or HTTPS."
:group 'twittering-proxy
:type '(choice (const nil)
string))
(defun twittering-normalize-proxy-vars ()
"Normalize the type of `twittering-http-proxy-port' and
`twittering-https-proxy-port'."
(mapc (lambda (sym)
(let ((value (symbol-value sym)))
(cond
((null value)
nil)
((integerp value)
nil)
((stringp value)
(set sym (string-to-number value)))
(t
(set sym nil)))))
'(twittering-proxy-port
twittering-http-proxy-port
twittering-https-proxy-port)))
(defun twittering-proxy-info (scheme &optional item)
"Return an alist for proxy configuration registered for SCHEME.
SCHEME must be a string \"http\", \"https\" or a symbol 'http or 'https.
The server name is a string and the port number is an integer."
(twittering-normalize-proxy-vars)
(let ((scheme (if (symbolp scheme)
(symbol-name scheme)
scheme))
(info-list
`((("http" "https")
. ((server . ,twittering-proxy-server)
(port . ,twittering-proxy-port)
(keep-alive . ,twittering-proxy-keep-alive)
(user . ,twittering-proxy-user)
(password . ,twittering-proxy-password)))
(("http")
. ((server . ,twittering-http-proxy-server)
(port . ,twittering-http-proxy-port)
(keep-alive . ,twittering-http-proxy-keep-alive)
(user . ,twittering-http-proxy-user)
(password . ,twittering-http-proxy-password)))
(("https")
. ((server . ,twittering-https-proxy-server)
(port . ,twittering-https-proxy-port)
(keep-alive . ,twittering-https-proxy-keep-alive)
(user . ,twittering-https-proxy-user)
(password . ,twittering-https-proxy-password))))))
(let ((info
(car (remove nil
(mapcar
(lambda (entry)
(when (member scheme (car entry))
(let ((info (cdr entry)))
(when (and (cdr (assq 'server info))
(cdr (assq 'port info)))
info))))
info-list)))))
(if item
(cdr (assq item info))
info))))
(defun twittering-url-proxy-services ()
"Return the current proxy configuration for `twittering-mode' in the format
of `url-proxy-services'."
(remove nil (mapcar
(lambda (scheme)
(let ((server (twittering-proxy-info scheme 'server))
(port (twittering-proxy-info scheme 'port)))
(when (and server port)
`(,scheme . ,(format "%s:%s" server port)))))
'("http" "https"))))
(defun twittering-find-proxy (scheme)
"Find proxy server and its port from the environmental variables and return
a cons pair of them.
SCHEME must be \"http\" or \"https\"."
(cond
((require 'url-methods nil t)
(url-scheme-register-proxy scheme)
(let* ((proxy-service (assoc scheme url-proxy-services))
(proxy (if proxy-service (cdr proxy-service) nil)))
(if (and proxy
(string-match "^\\([^:]+\\):\\([0-9]+\\)$" proxy))
(let ((host (match-string 1 proxy))
(port (string-to-number (match-string 2 proxy))))
(cons host port))
nil)))
(t
(let* ((env-var (concat scheme "_proxy"))
(env-proxy (or (getenv (upcase env-var))
(getenv (downcase env-var))))
(default-port (if (string= "https" scheme) "443" "80")))
(if (and env-proxy
(string-match
"^\\(https?://\\)?\\([^:/]+\\)\\(:\\([0-9]+\\)\\)?/?$"
env-proxy))
(let* ((host (match-string 2 env-proxy))
(port-str (or (match-string 4 env-proxy) default-port))
(port (string-to-number port-str)))
(cons host port))
nil)))))
(defun twittering-setup-proxy ()
(when (require 'url-methods nil t)
;; If `url-scheme-registry' is not initialized,
;; `url-proxy-services' will be reset by calling
;; `url-insert-file-contents' or `url-retrieve-synchronously', etc.
;; To avoid it, initialize `url-scheme-registry' by calling
;; `url-scheme-get-property' before calling such functions.
(url-scheme-get-property "http" 'name)
(url-scheme-get-property "https" 'name))
(unless (and twittering-http-proxy-server
twittering-http-proxy-port)
(let ((info (twittering-find-proxy "http")))
(setq twittering-http-proxy-server (car-safe info))
(setq twittering-http-proxy-port (cdr-safe info))))
(unless (and twittering-https-proxy-server
twittering-https-proxy-port)
(let ((info (twittering-find-proxy "https")))
(setq twittering-https-proxy-server (car-safe info))
(setq twittering-https-proxy-port (cdr-safe info))))
(if (and twittering-proxy-use
(null (twittering-proxy-info "http"))
(null (twittering-proxy-info "https")))
(progn
(message "Disabling proxy due to lack of configuration.")
(setq twittering-proxy-use nil))
t))
(defun twittering-toggle-proxy ()
(interactive)
(setq twittering-proxy-use
(not twittering-proxy-use))
(if (twittering-setup-proxy)
(message (if twittering-proxy-use "Use Proxy:on" "Use Proxy:off")))
(twittering-update-mode-line))
;;;;
;;;; Functions for URL library
;;;;
(defcustom twittering-url-show-status nil
"*If non-nil, show a running total of bytes transferred by urllib.
This has effect only if either \"urllib-httpp\" or \"urllib-https\" is used
as the connection method."
:group 'twittering-mode
:type 'boolean)
;;;;
;;;; CA certificate
;;;;
(defvar twittering-cert-file nil
"The full-path of the file including the certificates authorizing
servers on SSL.")
(defconst twittering-ca-cert-list
'(
;; #BEGIN-CERTIFICATE
;; Verisign Class 3 Public Primary Certification Authority - G3
;; issuer= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3
;; subject= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3
;; serial=9B7E0649A33E62B9D5EE90487129EF57
;; SHA1 Fingerprint=13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6
;; notBefore=Oct 1 00:00:00 1999 GMT
;; notAfter=Jul 16 23:59:59 2036 GMT
"-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
"
;; GeoTrust Global CA
;; issuer= /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
;; subject= /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
;; serial=023456
;; SHA1 Fingerprint=DE:28:F4:A4:FF:E5:B9:2F:A3:C5:03:D1:A3:49:A7:F9:96:2A:82:12
;; notBefore=May 21 04:00:00 2002 GMT
;; notAfter=May 21 04:00:00 2022 GMT
"-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----
"
;; DigiCert High Assurance EV Root CA
;; issuer= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
;; subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
;; serial=02AC5C266A0B409B8F0B79F2AE462577
;; SHA1 Fingerprint=5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25
;; notBefore=Nov 10 00:00:00 2006 GMT
;; notAfter=Nov 10 00:00:00 2031 GMT
"-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
"
;; VeriSign Class 3 Public Primary Certification Authority - G5
;; issuer= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5
;; subject= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5
;; serial=18DAD19E267DE8BB4A2158CDCC6B3B4A
;; SHA1 Fingerprint=4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5
;; notBefore=Nov 8 00:00:00 2006 GMT
;; notAfter=Jul 16 23:59:59 2036 GMT
"-----BEGIN CERTIFICATE-----
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
-----END CERTIFICATE-----
"
;; VeriSign Universal Root Certification Authority
;; issuer= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority
;; subject= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority
;; serial=401AC46421B31321030EBBE4121AC51D
;; SHA1 Fingerprint=36:79:CA:35:66:87:72:30:4D:30:A5:FB:87:3B:0F:A7:7B:B7:0D:54
;; notBefore=Apr 2 00:00:00 2008 GMT
;; notAfter=Dec 1 23:59:59 2037 GMT
"-----BEGIN CERTIFICATE-----
MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
7M2CYfE45k+XmCpajQ==
-----END CERTIFICATE-----
"
;; VeriSign Class 3 Public Primary Certification Authority - G4
;; issuer= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4
;; subject= /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4
;; serial=2F80FE238C0E220F486712289187ACB3
;; SHA1 Fingerprint=22:D5:D8:DF:8F:02:31:D1:8D:F7:9D:B7:CF:8A:2D:64:C9:3F:6C:3A
;; notBefore=Nov 5 00:00:00 2007 GMT
;; notAfter=Jan 18 23:59:59 2038 GMT
"-----BEGIN CERTIFICATE-----
MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
-----END CERTIFICATE-----
"
;; #END-CERTIFICATE
))
(defun twittering-delete-ca-cert ()
(when (and twittering-cert-file
(file-exists-p twittering-cert-file))
(delete-file twittering-cert-file))
(setq twittering-cert-file nil))
(defun twittering-ensure-ca-cert ()
"Return a full-path of the file including CA certificates.
If it does not exist, create it. The directory includes root certificates
in \"hash format\". In detail, see verify(1SSL)."
(unless twittering-cert-file
(let ((coding-system-for-write 'iso-safe)
(file (make-temp-file "twmode-cacert")))
(with-temp-file file
(apply 'insert twittering-ca-cert-list))
(setq twittering-cert-file file)
(add-hook 'kill-emacs-hook 'twittering-delete-ca-cert)))
twittering-cert-file)
;;;;
;;;; User agent
;;;;
(defvar twittering-user-agent-function 'twittering-user-agent-default-function)
(defun twittering-user-agent-default-function ()
"Twittering mode default User-Agent function."
(format "Emacs/%d.%d Twittering-mode/%s"
emacs-major-version emacs-minor-version
twittering-mode-version))
(defun twittering-user-agent ()
"Return User-Agent header string."
(funcall twittering-user-agent-function))
;;;;
;;;; Basic HTTP functions (general)
;;;;
(defun twittering-percent-encode (str &optional coding-system)
"Encode STR according to Percent-Encoding defined in RFC 3986."
(twittering-oauth-url-encode str coding-system))
(defun twittering-lookup-connection-type (use-ssl &optional order table)
"Return available entry extracted fron connection type table.
TABLE is connection type table, which is an alist of type symbol and its
item alist, such as
'((native (check . t)
(https . twittering-start-http-session-native-tls-p)
(start . twittering-start-http-session-native))
(curl (check . twittering-start-http-session-curl-p)
(https . twittering-start-http-session-curl-https-p)
(start . twittering-start-http-session-curl))) .
ORDER means the priority order of type symbols.
If USE-SSL is nil, the item `https' is ignored.
When the type `curl' has priority and is available for the above table,
the function returns
'((check . twittering-start-http-session-curl-p)
(https . twittering-start-http-session-curl-https-p)
(start . twittering-start-http-session-curl)) ."
(let ((rest (or order twittering-connection-type-order))
(table (or table twittering-connection-type-table))
(result nil))
(while (and rest (null result))
(let* ((candidate (car rest))
(entry (cons `(symbol . ,candidate)
(cdr (assq candidate table))))
(entry (if (assq 'display-name entry)
entry
(cons `(display-name . ,(symbol-name candidate))
entry)))
(validate (lambda (item)
(let ((v (cdr (assq item entry))))
(or (null v) (eq t v) (functionp v)))))
(confirm (lambda (item)
(let ((v (cdr (assq item entry))))
(cond
((null v) nil)
((eq t v) t)
((functionp v) (funcall v)))))))
(if (and (funcall validate 'check)
(or (not use-ssl) (funcall validate 'https)))
(cond
((and (funcall confirm 'check)
(or (not use-ssl) (funcall confirm 'https)))
(setq rest nil)
(setq result entry))
(t
(setq rest (cdr rest))))
(message "The configuration for conncetion type `%s' is invalid."
candidate)
(setq rest nil))))
result))
(defun twittering-get-connection-method-name (use-ssl)
"Return a name of the preferred connection method.
If USE-SSL is non-nil, return a connection method for HTTPS.
If USE-SSL is nil, return a connection method for HTTP."
(cdr (assq 'display-name (twittering-lookup-connection-type use-ssl))))
(defun twittering-lookup-http-start-function (&optional order table)
"Decide a connection method from currently available methods."
(let ((entry
(twittering-lookup-connection-type twittering-use-ssl order table)))
(cdr (assq 'send-http-request entry))))
(defun twittering-ensure-connection-method (&optional order table)
"Ensure a connection method with a compromise.
Return nil if no connection methods are available with a compromise."
(let* ((use-ssl (or twittering-use-ssl twittering-oauth-use-ssl))
(entry (twittering-lookup-connection-type use-ssl order table)))
(cond
(entry
t)
((and (null entry) use-ssl
(yes-or-no-p "HTTPS(SSL) is unavailable. Use HTTP instead? "))
;; Fall back on connection without SSL.
(setq twittering-use-ssl nil)
(setq twittering-oauth-use-ssl nil)
(twittering-update-mode-line)
(twittering-ensure-connection-method order table))
(t
(message "No connection methods are available.")
nil))))
(defun twittering-make-http-request (method header-list host port path query-parameters post-body use-ssl)
"Returns an alist specifying a HTTP request.
METHOD specifies HTTP method. It must be \"GET\" or \"POST\".
HEADER-LIST is a list of (field-name . field-value) specifying HTTP header
fields. The fields \"Host\", \"User-Agent\" and \"Content-Length\" are
automatically filled if necessary.
HOST specifies the host.
PORT specifies the port. This must be an integer.
PATH specifies the absolute path in URI (without query string).
QUERY-PARAMTERS is a string or an alist.
If QUERY-PARAMTERS is a string, it is treated as an encoded query string.
If QUERY-PARAMTERS is an alist, it represents a list of cons pairs of
string, (query-key . query-value).
POST-BODY specifies the post body sent when METHOD equals to \"POST\".
If POST-BODY is nil, no body is posted.
If USE-SSL is non-nil, the request is performed with SSL.
The result alist includes the following keys, where a key is a symbol.
method: HTTP method such as \"GET\" or \"POST\".
scheme: the scheme name. \"http\" or \"https\".
host: the host to which the request is sent.
port: the port to which the request is sent (integer).
path: the absolute path string. Note that it does not include query string.
query-string: the query string.
encoded-query-alist: the alist consisting of pairs of encoded query-name and
encoded query-value.
uri: the URI. It includes the query string.
uri-without-query: the URI without the query string.
header-list: an alist specifying pairs of a parameter and its value in HTTP
header field.
post-body: the entity that will be posted."
(let* ((scheme (if use-ssl "https" "http"))
(default-port (if use-ssl 443 80))
(port (if port port default-port))
(query-string
(cond
((stringp query-parameters)
query-parameters)
((consp query-parameters)
(mapconcat (lambda (pair)
(cond
((stringp pair)
(twittering-percent-encode pair))
((consp pair)
(format
"%s=%s"
(twittering-percent-encode (car pair))
(twittering-percent-encode (cdr pair))))
(t
nil)))
query-parameters
"&"))
(t
nil)))
(encoded-query-alist
(cond
((stringp query-parameters)
;; Query name and its value must be already encoded.
(mapcar (lambda (str)
(if (string-match "=" str)
(let ((key (substring str 0 (match-beginning 0)))
(value (substring str (match-end 0))))
`(,key . ,value))
`(,str . nil)))
(split-string query-parameters "&")))
((consp query-parameters)
(mapcar (lambda (pair)
(cond
((stringp pair)
(cons (twittering-percent-encode pair) nil))
((consp pair)
(cons (twittering-percent-encode (car pair))
(twittering-percent-encode (cdr pair))))
(t
nil)))
query-parameters))
(t
nil)))
(uri-without-query
(concat scheme "://"
host
(when (and port (not (= port default-port)))
(format ":%d" port))
path))
(uri
(if query-string
(concat uri-without-query "?" query-string)
uri-without-query))
(header-list
`(,@(when (and (string= method "POST")
(not (assoc "Content-Length" header-list)))
`(("Content-Length" . ,(format "%d" (length post-body)))))
,@(unless (assoc "Host" header-list)
`(("Host" . ,host)))
,@(unless (assoc "User-Agent" header-list)
`(("User-Agent" . ,(twittering-user-agent))))
,@header-list)))
(cond
((not (member method '("POST" "GET")))
(error "Unknown HTTP method: %s" method)
nil)
((not (string-match "^/" path))
(error "Invalid HTTP path: %s" path)
nil)
(t
`((method . ,method)
(scheme . ,scheme)
(host . ,host)
(port . ,port)
(path . ,path)
(query-string . ,query-string)
(encoded-query-alist . ,encoded-query-alist)
(uri . ,uri)
(uri-without-query . ,uri-without-query)
(header-list . ,header-list)
(post-body . ,post-body))))))
(defun twittering-make-http-request-from-uri (method header-list uri &optional post-body)
"Returns an alist specifying a HTTP request.
The result alist has the same form as an alist generated by
`twittering-make-http-request'.
METHOD specifies HTTP method. It must be \"GET\" or \"POST\".
HEADER-LIST is a list of (field-name . field-value) specifying HTTP header
fields. The fields \"Host\" and \"User-Agent\" are automatically filled
if necessary.
URI specifies the URI including query string.
POST-BODY specifies the post body sent when METHOD equals to \"POST\".
If POST-BODY is nil, no body is posted."
(let* ((parts-alist
(let ((parsed-url (url-generic-parse-url uri)))
;; This is required for the difference of url library
;; distributed with Emacs 22 and 23.
(cond
((and (fboundp 'url-p) (url-p parsed-url))
;; Emacs 23 and later.
`((scheme . ,(url-type parsed-url))
(host . ,(url-host parsed-url))
(port . ,(url-portspec parsed-url))
(path . ,(url-filename parsed-url))))
((vectorp parsed-url)
;; Emacs 22.
`((scheme . ,(aref parsed-url 0))
(host . ,(aref parsed-url 3))
(port . ,(aref parsed-url 4))
(path . ,(aref parsed-url 5))))
(t
nil))))
(path (let ((path (cdr (assq 'path parts-alist))))
(if (string-match "\\`\\(.*\\)\\?" path)
(match-string 1 path)
path)))
(query-string (let ((path (cdr (assq 'path parts-alist))))
(if (string-match "\\?\\(.*\\)\\'" path)
(match-string 1 path)
nil))))
(twittering-make-http-request method header-list
(cdr (assq 'host parts-alist))
(cdr (assq 'port parts-alist))
path
query-string
post-body
(string= "https"
(cdr (assq 'scheme parts-alist))))))
(defun twittering-make-connection-info (request &optional additional order table)
"Make an alist specifying the information of connection for REQUEST.
REQUEST must be an alist that has the same keys as that generated by
`twittering-make-http-request'.
ADDITIONAL is appended to the tail of the result alist.
Following ADDITIONAL, an entry in TABLE is also appended to the result alist,
where `twittering-lookup-connection-type' determines the entry according to
the priority order ORDER.
If ORDER is nil, `twittering-connection-type-order' is used in place of ORDER.
If TABLE is nil, `twittering-connection-type-table' is used in place of TABLE.
The parameter symbols are following:
use-ssl: whether SSL is enabled or not.
allow-insecure-server-cert: non-nil if an insecure server certificate is
allowed on SSL.
cacert-file-fullpath: the full-path of a file including the certificates
authorizing a server certificate on SSL. The file must be in PEM format.
use-proxy: non-nil if using a proxy.
proxy-server: a proxy server or nil.
proxy-port: a port for connecting the proxy (integer) or nil.
proxy-user: a username for connecting the proxy or nil.
proxy-password: a password for connecting the proxy or nil.
request: an alist specifying a HTTP request."
(let* ((order (or order twittering-connection-type-order))
(table (or table twittering-connection-type-table))
(scheme (cdr (assq 'scheme request)))
(use-ssl (string= "https" scheme))
(entry (twittering-lookup-connection-type use-ssl order table)))
`((use-ssl . ,use-ssl)
(allow-insecure-server-cert
. ,twittering-allow-insecure-server-cert)
(cacert-file-fullpath
. ,(when use-ssl (twittering-ensure-ca-cert)))
(use-proxy . ,twittering-proxy-use)
,@(when twittering-proxy-use
`((proxy-server . ,(twittering-proxy-info scheme 'server))
(proxy-port . ,(twittering-proxy-info scheme 'port))
(proxy-user . ,(twittering-proxy-info scheme 'user))
(proxy-password . ,(twittering-proxy-info scheme 'password))))
(request . ,request)
,@additional
,@entry)))
(defun twittering-get-response-header (buffer)
"Extract HTTP response header from HTTP response.
BUFFER may be a buffer or the name of an existing buffer which contains the HTTP response."
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(if (search-forward-regexp "\r?\n\r?\n" nil t)
(prog1
(buffer-substring (point-min) (match-end 0))
(when twittering-debug-mode
(debug-printf "connection-info=%s\n" connection-info)
(debug-print "HTTP response header:\n--BEGIN\n")
(debug-print (buffer-substring (point-min) (match-end 0)))
(debug-print "--END\n")))
nil))))
(defun twittering-make-header-info-alist (header-str)
"Make HTTP header alist from HEADER-STR.
The alist consists of pairs of field-name and field-value, such as
'((\"Content-Type\" . \"application/xml\; charset=utf-8\")
(\"Content-Length\" . \"2075\"))."
(let* ((lines (split-string header-str "\r?\n"))
(status-line (car lines))
(header-lines (cdr lines)))
(when (string-match
"^\\(HTTP/1\\.[01]\\|HTTP/2\\(?:\\.0\\)?\\) \\([0-9][0-9][0-9]\\)\\(.*\\)$"
status-line)
(append `((status-line . ,status-line)
(http-version . ,(match-string 1 status-line))
(status-code . ,(match-string 2 status-line))
(reason-phrase . ,(match-string 3 status-line)))
(remove nil
(mapcar
(lambda (line)
(when (string-match "^\\([^: ]*\\): *\\(.*\\)$" line)
(cons (match-string 1 line) (match-string 2 line))))
header-lines))))))
(defun twittering-get-content-subtype-symbol-from-header-info (header-info)
"Return a symbol corresponding to the subtype of content-type."
(let* ((content-type
;; According to RFC2616, field name of a HTTP header is
;; case-insensitive.
(car
(remove
nil
(mapcar (lambda (entry)
(when (and (stringp (car entry))
(let ((case-fold-search t))
(string-match "\\`content-type\\'"
(car entry))))
(cdr entry)))
header-info))))
(subtype (when (and (stringp content-type)
(string-match "\\` *[^/]*/\\([^ ;]*\\)"
content-type))
(downcase (match-string 1 content-type))))
(symbol-alist
'(("json" . json)
("atom+xml" . atom)
("plain" . plain)
("xml" . xml))))
(cdr (assoc subtype symbol-alist))))
(defun twittering-decode-response-body (header-info)
"Decode the current buffer according to the content-type in HEADER-INFO."
(let* ((content-type
;; According to RFC2616, field name of a HTTP header is
;; case-insensitive.
(car
(remove
nil
(mapcar (lambda (entry)
(when (and (stringp (car entry))
(let ((case-fold-search t))
(string-match "\\`content-type\\'"
(car entry))))
(cdr entry)))
header-info))))
(parameters (when (stringp content-type)
(cdr (split-string content-type ";"))))
(regexp "^[[:space:]]*charset=utf-8[[:space:]]*$")
(encoded-with-utf-8
(let ((case-fold-search t))
(remove nil
(mapcar (lambda (entry)
(string-match regexp entry))
parameters)))))
(when encoded-with-utf-8
(decode-coding-region (point-min) (point-max) 'utf-8))))
(defun twittering-send-http-request-internal (request additional-info sentinel &optional order table)
"Open a connection and return a subprocess object for the connection.
REQUEST must be an alist that has the same keys as that generated by
`twittering-make-http-request'.
SENTINEL is called as a function when the process changes state.
It gets three arguments: the process, a string describing the change, and
the connection-info, which is generated by `twittering-make-connection-info'
and also includes an alist ADDITIONAL-INFO.
How to perform the request is selected from TABLE according to the priority
order ORDER. ORDER and TABLE are directly sent to
`twittering-make-connection-info'.
If ORDER is nil, `twittering-connection-type-order' is used in place of ORDER.
If TABLE is nil, `twittering-connection-type-table' is used in place of TABLE.
"
(let* ((order (or order twittering-connection-type-order))
(table (or table twittering-connection-type-table))
(connection-info
(twittering-make-connection-info request additional-info
order table))
(func (cdr (assq 'send-http-request connection-info)))
(temp-buffer (generate-new-buffer "*twmode-http-buffer*"))
;; Bind `default-directory' to the temporary directory
;; because it is possible that the directory pointed by
;; `default-directory' has been already removed.
(default-directory temporary-file-directory))
(cond
((and func (functionp func))
(funcall func "*twmode-generic*" temp-buffer
connection-info
(when (and sentinel (functionp sentinel))
(lexical-let ((sentinel sentinel)
(connection-info connection-info))
(lambda (proc status)
(apply sentinel proc status connection-info nil))))))
(t
(error "No valid connection method is found")
nil))))
(defun twittering-send-http-request (request additional-info func &optional clean-up-func)
"Send a HTTP request and return a subprocess object for the connection.
REQUEST must be an alist that has the same keys as that generated by
`twittering-make-http-request'.
FUNC is called when a HTTP response has been received without errors.
It is called with the current buffer containing the HTTP response (without
HTTP headers). FUNC is called with four arguments: the process, a symbol
describing the status of the process, a connection-info generated by
`twittering-make-connection-info', and a header-info generated by
`twittering-get-response-header' and `twittering-make-header-info-alist'.
The connection-info also includes an alist ADDITIONAL-INFO.
If FUNC returns non-nil and `twittering-buffer-related-p' is non-nil, the
returned value is displayed as a message.
And also, if FUNC returns a string and it matches the regular expression
\"^\\\\(Failuare\\\\|Response\\\\): \", the returned value is displayed
as a message.
CLEAN-UP-FUNC is called whenever the sentinel of the subprocess for the
connection is called (as `set-process-sentinel').
It is called with three arguments: the process, a symbol describing the status
of the proess, and a connection-info generated by
`twittering-make-connection-info'.
They are the same as arguments for FUNC.
When a HTTP response has been received, FUNC is called in advance of
CLEAN-UP-FUNC. CLEAN-UP-FUNC can overwrite the message displayed by FUNC.
If the subprocess has exited, the buffer bound to it is automatically killed
after calling CLEAN-UP-FUNC.
The method to perform the request is determined from
`twittering-connection-type-table' according to the priority order
`twittering-connection-type-order'."
(lexical-let ((func func)
(clean-up-func clean-up-func))
(twittering-send-http-request-internal
request additional-info
(lambda (proc status-str connection-info)
(let ((status (cond
((string= status-str "urllib-finished") 'exit)
((processp proc) (process-status proc))
(t nil)))
(buffer (process-buffer proc))
(exit-status (cond
((string= status-str "urllib-finished") 0)
((processp proc) (process-exit-status proc))
(t 1)))
(command (process-command proc))
(pre-process-func
(cdr (assq 'pre-process-buffer connection-info)))
(mes nil))
(unwind-protect
(setq mes
(cond
((null status)
(format "Failure: process %s does not exist" proc))
((or (memq status '(run stop open listen connect))
(not (memq status '(exit signal closed failed))))
;; If the process is running, FUNC is not called.
nil)
((and command
(not (= 0 exit-status)))
;; If the process abnormally exited,
(format "Failure: %s exited abnormally (exit-status=%s)"
(car command) exit-status))
((not (buffer-live-p buffer))
(format "Failure: the buffer for %s is already killed"
proc))
(t
(when (functionp pre-process-func)
;; Pre-process buffer.
(funcall pre-process-func proc buffer connection-info))
(let* ((header (twittering-get-response-header buffer))
(header-info
(and header
(twittering-make-header-info-alist header))))
(with-current-buffer buffer
(goto-char (point-min))
(when (search-forward-regexp "\r?\n\r?\n" nil t)
;; delete HTTP headers.
(delete-region (point-min) (match-end 0)))
;; It may be necessary to decode the contents of
;; the buffer by UTF-8 because
;; `twittering-http-application-headers' specifies
;; utf-8 as one of acceptable charset.
;; For the present, only UTF-8 is taken into account.
(twittering-decode-response-body header-info)
(apply func proc status connection-info
header-info nil))))))
;; unwind-forms
(setq mes
(cond
((null mes)
nil)
((string-match "^\\(Failure\\|Response\\): " mes)
(let* ((request (cdr (assq 'request connection-info)))
(method (cdr (assq 'method request)))
(uri (cdr (assq 'uri request))))
(concat mes " for " method " " uri)))
((twittering-buffer-related-p)
mes)))
(when mes
;; CLEAN-UP-FUNC can overwrite a message from the return value
;; of FUNC.
(message "%s" mes))
(when (functionp clean-up-func)
(funcall clean-up-func proc status connection-info))
(when (and (memq status '(exit signal closed failed))
(buffer-live-p buffer)
(not twittering-debug-mode))
(kill-buffer buffer))))))))
;;;;
;;;; Basic HTTP functions with tls and Emacs builtins.
;;;;
(eval-when-compile (require 'tls nil t))
(defun twittering-start-http-session-native-tls-p ()
(when (and (not twittering-proxy-use)
(require 'tls nil t))
(unless twittering-tls-program
(let ((programs
(remove nil
(mapcar (lambda (cmd)
(when (string-match "\\`\\([^ ]+\\) " cmd)
(when (executable-find (match-string 1 cmd))
cmd)))
tls-program))))
(setq twittering-tls-program
(if twittering-allow-insecure-server-cert
(mapcar
(lambda (str)
(cond
((string-match "^\\([^ ]*/\\)?openssl s_client " str)
(concat (match-string 0 str) "-verify 0 "
(substring str (match-end 0))))
((string-match "^\\([^ ]*/\\)?gnutls-cli " str)
(concat (match-string 0 str) "--insecure "
(substring str (match-end 0))))
(t
str)))
programs)
programs))))
(not (null twittering-tls-program))))
;; TODO: proxy
(defun twittering-send-http-request-native (name buffer connection-info sentinel)
(let* ((request (cdr (assq 'request connection-info)))
(uri (cdr (assq 'uri connection-info)))
(method (cdr (assq 'method request)))
(scheme (cdr (assq 'scheme request)))
(host (cdr (assq 'host request)))
(port (cdr (assq 'port request)))
(path (cdr (assq 'path request)))
(query-string (cdr (assq 'query-string request)))
(post-body (cdr (assq 'post-body request)))
(use-proxy (cdr (assq 'use-proxy connection-info)))
(proxy-server (cdr (assq 'proxy-server connection-info)))
(proxy-port (cdr (assq 'proxy-port connection-info)))
(proxy-user (cdr (assq 'proxy-user connection-info)))
(proxy-password (cdr (assq 'proxy-password connection-info)))
(proxy-credentials
(when (and proxy-user proxy-password)
(concat "Basic "
(base64-encode-string
(concat proxy-user ":" proxy-password)))))
(header-list
(let ((original-header-list (cdr (assq 'header-list request))))
(if proxy-credentials
(cons
`("Proxy-Authorization" ,proxy-credentials)
original-header-list)
original-header-list)))
(use-ssl (cdr (assq 'use-ssl connection-info)))
(allow-insecure-server-cert
(cdr (assq 'allow-insecure-server-cert connection-info)))
(connect-host (or proxy-server host))
(connect-port (or proxy-port port))
(request-str
(format "%s %s HTTP/1.1\r\n%s\r\n\r\n%s\r\n"
method
(if use-proxy
;; As described in 5.1.2 of RFC2616, the
;; absolute URI is required here if the connection
;; uses a proxy.
uri
(concat path
(when query-string
(concat "?" query-string))))
(mapconcat (lambda (pair)
(format "%s: %s" (car pair) (cdr pair)))
header-list "\r\n")
(or post-body "")))
(coding-system-for-read 'binary)
(coding-system-for-write 'binary)
(tls-program twittering-tls-program)
(proc
(funcall (if use-ssl
'open-tls-stream
'open-network-stream)
"network-connection-process"
nil connect-host connect-port)))
(when proc
(set-process-buffer proc buffer)
(when (functionp sentinel)
(if (twittering-process-alive-p proc)
(set-process-sentinel proc sentinel)
(funcall sentinel proc "finished")))
(process-send-string proc request-str)
proc)))
(defun twittering-pre-process-buffer-native (proc buffer connection-info)
(let ((use-ssl (cdr (assq 'use-ssl connection-info)))
(args (process-command proc)))
(cond
((and use-ssl args
(car
(remove nil
(mapcar (lambda (cmd)
(string-match "^\\(.*/\\)?gnutls-cli\\b" cmd))
args))))
(with-current-buffer buffer
(save-excursion
(goto-char (point-max))
(when (search-backward-regexp
"- Peer has closed the GNUTLS connection\r?\n\\'")
(let ((beg (match-beginning 0))
(end (match-end 0)))
(delete-region beg end))))))
((and use-ssl args
(car
(remove nil
(mapcar
(lambda (cmd)
(string-match "^\\(.*/\\)?openssl s_client\\b" cmd))
args))))
(with-current-buffer buffer
(save-excursion
(goto-char (point-max))
(when (search-backward-regexp "closed\r?\n\\'")
(let ((beg (match-beginning 0))
(end (match-end 0)))
(delete-region beg end))))))
(t
nil))))
;;;;
;;;; Basic HTTP functions with curl
;;;;
(defun twittering-find-curl-program ()
"Returns an appropriate `curl' program pathname or nil if not found."
(or (executable-find "curl")
(let ((windows-p (memq system-type '(windows-nt cygwin)))
(curl.exe
(expand-file-name
"curl.exe"
(expand-file-name
"win-curl"
(file-name-directory (symbol-file 'twit))))))
(and windows-p
(file-exists-p curl.exe) curl.exe))))
(defun twittering-start-http-session-curl-p ()
"Return t if curl was installed, otherwise nil."
(unless twittering-curl-program
(setq twittering-curl-program (twittering-find-curl-program)))
(not (null twittering-curl-program)))
(defun twittering-start-http-session-curl-https-p ()
"Return t if curl was installed and the curl support HTTPS, otherwise nil."
(when (twittering-start-http-session-curl-p)
(unless twittering-curl-program-https-capability
(with-temp-buffer
(let ((coding-system-for-read 'iso-safe)
(coding-system-for-write 'iso-safe)
;; Bind `default-directory' to the temporary directory
;; because it is possible that the directory pointed by
;; `default-directory' has been already removed.
(default-directory temporary-file-directory))
(call-process twittering-curl-program
nil (current-buffer) nil
"--version")
(goto-char (point-min))
(setq twittering-curl-program-https-capability
(if (search-forward-regexp "^Protocols: .*https" nil t)
'capable
'incapable)))))
(eq twittering-curl-program-https-capability 'capable)))
(defun twittering-start-http-session-curl-http2-p ()
"Return t if the curl support HTTP2, otherwise nil."
(when (twittering-start-http-session-curl-p)
(unless twittering-curl-program-http2-capability
(with-temp-buffer
(let ((coding-system-for-read 'iso-safe)
(coding-system-for-write 'iso-safe)
;; Bind `default-directory' to the temporary directory
;; because it is possible that the directory pointed by
;; `default-directory' has been already removed.
(default-directory temporary-file-directory))
(call-process twittering-curl-program
nil (current-buffer) nil
"--version")
(goto-char (point-min))
(setq twittering-curl-program-http2-capability
(if (search-forward-regexp "^Features:.* HTTP2" nil t)
'capable
'incapable)))))
(eq twittering-curl-program-http2-capability 'capable)))
(defun twittering-send-http-request-curl (name buffer connection-info sentinel)
(let* ((request (cdr (assq 'request connection-info)))
(method (cdr (assq 'method request)))
(uri (cdr (assq 'uri request)))
(header-list (cdr (assq 'header-list request)))
(post-body (cdr (assq 'post-body request)))
(use-proxy (cdr (assq 'use-proxy connection-info)))
(proxy-server (cdr (assq 'proxy-server connection-info)))
(proxy-port (cdr (assq 'proxy-port connection-info)))
(proxy-user (cdr (assq 'proxy-user connection-info)))
(proxy-password (cdr (assq 'proxy-password connection-info)))
(use-ssl (cdr (assq 'use-ssl connection-info)))
(use-http2 (twittering-start-http-session-curl-http2-p))
(allow-insecure-server-cert
(cdr (assq 'allow-insecure-server-cert connection-info)))
(cacert-file-fullpath
(cdr (assq 'cacert-file-fullpath connection-info)))
(cacert-file-base-directory
(when cacert-file-fullpath
(file-name-directory cacert-file-fullpath)))
(cacert-file-body
(when cacert-file-fullpath
(file-name-nondirectory cacert-file-fullpath)))
(header-list
`(,@header-list
;; Make `curl' remove the HTTP header field "Expect" for
;; avoiding '417 Expectation Failed' HTTP response error.
;; The header field is automatically added for a HTTP request
;; exceeding 1024 byte. See
;; http://d.hatena.ne.jp/imait/20091228/1262004813 and
;; http://www.escafrace.co.jp/blog/09/10/16/1008
("Expect" . "")))
(curl-args
`("--include" "--silent" "--compressed"
,@(when use-http2 `("--http2"))
,@(apply 'append
(mapcar
(lambda (pair)
;; Do not overwrite internal headers `curl' would use.
;; Thanks to William Xu.
;; "cURL - How To Use"
;; http://curl.haxx.se/docs/manpage.html
(unless (string= (car pair) "Host")
`("-H" ,(format "%s: %s" (car pair) (cdr pair)))))
header-list))
,@(when use-ssl `("--cacert" ,cacert-file-body))
,@(when (and use-ssl allow-insecure-server-cert)
`("--insecure"))
,@(when (and use-proxy proxy-server proxy-port)
(append
`("-x" ,(format "%s:%s" proxy-server proxy-port))
(when (and proxy-user proxy-password)
`("-U" ,(format "%s:%s" proxy-user proxy-password)))))
,@(when (string= "POST" method)
`("-d" ,(or post-body "")))
,uri))
(coding-system-for-read 'binary)
(coding-system-for-write 'binary)
(default-directory
;; If `use-ssl' is non-nil, the `curl' process
;; is executed at the same directory as the temporary cert file.
;; Without changing directory, `curl' misses the cert file if
;; you use Emacs on Cygwin because the path on Emacs differs
;; from Windows.
;; With changing directory, `curl' on Windows can find the cert
;; file if you use Emacs on Cygwin.
(if use-ssl
cacert-file-base-directory
default-directory)))
(twittering-start-process-with-sentinel name buffer
twittering-curl-program
curl-args sentinel)))
(defun twittering-pre-process-buffer-curl (proc buffer connection-info)
(let ((use-ssl (cdr (assq 'use-ssl connection-info)))
(use-proxy (cdr (assq 'use-proxy connection-info))))
(when (and use-ssl use-proxy)
;; When using SSL via a proxy with CONNECT method,
;; omit a successful HTTP response and headers if they seem to be
;; sent from the proxy.
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(let ((first-regexp
;; successful HTTP response
"\\`HTTP/\\(1\\.[01]\\|2\\(\\.0\\)?\\) 2[0-9][0-9].*?\r?\n")
(next-regexp
;; following HTTP response
"^\\(\r?\n\\)HTTP/\\(1\\.[01]\\|2\\(\\.0\\)?\\) [0-9][0-9][0-9].*?\r?\n"))
(when (and (search-forward-regexp first-regexp nil t)
(search-forward-regexp next-regexp nil t))
(let ((beg (point-min))
(end (match-end 1)))
(delete-region beg end)))))))))
;;;;
;;;; Basic HTTP functions with wget
;;;;
(defun twittering-find-wget-program ()
"Returns an appropriate `wget' program pathname or nil if not found."
(executable-find "wget"))
(defun twittering-start-http-session-wget-p ()
"Return t if `wget' was installed, otherwise nil."
(unless twittering-wget-program
(setq twittering-wget-program (twittering-find-wget-program)))
(not (null twittering-wget-program)))
(defun twittering-send-http-request-wget (name buffer connection-info sentinel)
(let* ((request (cdr (assq 'request connection-info)))
(method (cdr (assq 'method request)))
(scheme (cdr (assq 'scheme request)))
(uri (cdr (assq 'uri request)))
(header-list (cdr (assq 'header-list request)))
(post-body (cdr (assq 'post-body request)))
(use-proxy (cdr (assq 'use-proxy connection-info)))
(proxy-server (cdr (assq 'proxy-server connection-info)))
(proxy-port (cdr (assq 'proxy-port connection-info)))
(proxy-user (cdr (assq 'proxy-user connection-info)))
(proxy-password (cdr (assq 'proxy-password connection-info)))
(use-ssl (cdr (assq 'use-ssl connection-info)))
(allow-insecure-server-cert
(cdr (assq 'allow-insecure-server-cert connection-info)))
(cacert-file-fullpath
(cdr (assq 'cacert-file-fullpath connection-info)))
(cacert-file-base-directory
(when cacert-file-fullpath
(file-name-directory cacert-file-fullpath)))
(cacert-file-body
(when cacert-file-fullpath
(file-name-nondirectory cacert-file-fullpath)))
(args
`("--save-headers"
"--quiet"
"--output-document=-"
,@(remove nil
(mapcar
(lambda (pair)
(unless (string= (car pair) "Host")
(format "--header=%s: %s" (car pair) (cdr pair))))
header-list))
,@(when use-ssl
`(,(format "--ca-certificate=%s" cacert-file-body)))
,@(when (and use-ssl allow-insecure-server-cert)
`("--no-check-certificate"))
,@(cond
((not use-proxy)
'("--no-proxy"))
((and use-proxy proxy-server proxy-port
proxy-user proxy-password)
`(,(format "--proxy-user=%s" proxy-user)
,(format "--proxy-password=%s" proxy-password)))
(t
nil))
,@(when (string= "POST" method)
`(,(concat "--post-data=" (or post-body ""))))
,uri))
(coding-system-for-read 'binary)
(coding-system-for-write 'binary)
(default-directory
;; If `use-ssl' is non-nil, the `wget' process
;; is executed at the same directory as the temporary cert file.
;; Without changing directory, `wget' misses the cert file if
;; you use Emacs on Cygwin because the path on Emacs differs
;; from Windows.
;; With changing directory, `wget' on Windows can find the cert
;; file if you use Emacs on Cygwin.
(if use-ssl
cacert-file-base-directory
default-directory))
(process-environment
`(,@(when (and use-proxy proxy-server proxy-port)
`(,(format "%s_proxy=%s://%s:%s/" scheme
scheme proxy-server proxy-port)))
,@process-environment)))
(twittering-start-process-with-sentinel name buffer
twittering-wget-program args
sentinel)))
(defun twittering-pre-process-buffer-wget (proc buffer connection-info)
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(when (search-forward-regexp "\\`[^\n]*?\r\r\n" (point-max) t)
;; When `wget.exe' writes HTTP response in text mode,
;; CRLF may be converted into CRCRLF.
(goto-char (point-min))
(while (search-forward "\r\n" nil t)
(replace-match "\n" nil t)))
(goto-char (point-max))
(when (search-backward-regexp "\nProcess [^\n]* finished\n\\'"
(point-min) t)
(replace-match "" nil t))
)))
;;;;
;;;; Basic HTTP functions with url library
;;;;
(defun twittering-start-http-session-urllib-p ()
"Return t if url library is available, otherwise nil."
(require 'url nil t))
(defun twittering-start-http-session-urllib-https-p ()
"Return t if url library can be used for HTTPS, otherwise nil."
(and (not twittering-proxy-use)
(require 'url nil t)
(cond
((<= 22 emacs-major-version)
;; On Emacs22 and later, `url' requires `tls'.
(twittering-start-http-session-native-tls-p))
((require 'ssl nil t)
;; On Emacs21, `url' requires `ssl'.
t)
((or (and (fboundp 'open-ssl-stream)
;; Since `url-gw' (required by `url') defines autoload of
;; `open-ssl-stream' from "ssl",
;; (fboundp 'open-ssl-stream) will be non-nil even if
;; "ssl" cannot be loaded and `open-ssl-stream' is
;; unavailable.
;; Here, the availability is confirmed by `documentation'.
(documentation 'open-ssl-stream))
;; On Emacs21, `url' requires `ssl' in order to use
;; `open-ssl-stream', which is included in `ssl.el'.
;; Even if `ssl' cannot be loaded, `open-tls-stream' can be
;; used as an alternative of the function.
(and (twittering-start-http-session-native-tls-p)
(defalias 'open-ssl-stream 'open-tls-stream)))
(provide 'ssl)
t)
(t
nil))))
(defun twittering-send-http-request-urllib (name buffer connection-info sentinel)
(let* ((request (cdr (assq 'request connection-info)))
(method (cdr (assq 'method request)))
(scheme (cdr (assq 'scheme request)))
(uri (cdr (assq 'uri request)))
(header-list (cdr (assq 'header-list request)))
(post-body (cdr (assq 'post-body request)))
(use-proxy (cdr (assq 'use-proxy connection-info)))
(proxy-server (cdr (assq 'proxy-server connection-info)))
(proxy-port (cdr (assq 'proxy-port connection-info)))
(proxy-user (cdr (assq 'proxy-user connection-info)))
(proxy-password (cdr (assq 'proxy-password connection-info)))
(proxy-credentials
(when (and proxy-user proxy-password)
(concat "Basic "
(base64-encode-string
(concat proxy-user ":" proxy-password)))))
(coding-system-for-read 'binary)
(coding-system-for-write 'binary)
(url-proxy-services
(when use-proxy
`((,scheme . ,(format "%s:%s" proxy-server proxy-port)))))
(url-request-method method)
(url-request-extra-headers
;; Remove some headers that should be configured by url library.
;; They may break redirections by url library because
;; `url-request-extra-headers' overwrites the new headers
;; that are adapted to redirected connection.
(apply 'append
(mapcar (lambda (pair)
(if (member (car pair)
'("Host" "Content-Length"))
nil
`(,pair)))
(if proxy-credentials
(cons
`("Proxy-Authorization" ,proxy-credentials)
header-list)
header-list))))
(url-request-data post-body)
(url-show-status twittering-url-show-status)
(url-http-attempt-keepalives nil)
(tls-program twittering-tls-program)
(coding-system-for-read 'binary)
(coding-system-for-write 'binary))
(lexical-let ((sentinel sentinel)
(buffer buffer))
(let ((result-buffer
(url-retrieve
uri
(lambda (&rest args)
(let ((proc url-http-process)
(url-buffer (current-buffer))
(status-str
(if (and (< emacs-major-version 22)
(boundp 'url-http-end-of-headers)
url-http-end-of-headers)
"urllib-finished"
"finished")))
;; Callback may be called multiple times.
;; (as filter and sentinel?)
(unless (local-variable-if-set-p 'twittering-retrieved)
(set (make-local-variable 'twittering-retrieved)
'not-completed)
(with-current-buffer buffer
(set-buffer-multibyte nil)
(insert-buffer-substring url-buffer))
(set-process-buffer proc buffer)
(unwind-protect
(apply sentinel proc status-str nil)
(set-process-buffer proc url-buffer)
(if (eq twittering-retrieved 'exited)
(url-mark-buffer-as-dead url-buffer)
(setq twittering-retrieved 'completed))))
(when (memq (process-status proc)
'(nil closed exit failed signal))
;; Mark `url-buffer' as dead when the process exited
;; and `sentinel' is completed.
;; If this `lambda' is evaluated via a filter, the
;; process may exit before it is finished to evaluate
;; `(apply sentinel ...)'. In the case, `buffer' should
;; not be killed. It should be killed after the
;; evaluation of `sentinel'.
(if (eq twittering-retrieved 'completed)
(url-mark-buffer-as-dead url-buffer)
(setq twittering-retrieved 'exited))))))))
(when (buffer-live-p result-buffer)
(with-current-buffer result-buffer
(set (make-local-variable 'url-show-status)
twittering-url-show-status)
;; Make `url-http-attempt-keepalives' buffer-local
;; in order to send the current value of the variable
;; to the sentinel invoked for HTTP redirection,
(make-local-variable 'url-http-attempt-keepalives))
(get-buffer-process result-buffer))))))
(defun twittering-pre-process-buffer-urllib (proc buffer connection-info)
(with-current-buffer buffer
(save-excursion
(goto-char (point-max))
(cond
((search-backward-regexp
"- Peer has closed the GNUTLS connection\r?\n\\'"
nil t)
(let ((beg (match-beginning 0))
(end (match-end 0)))
(delete-region beg end)))
((search-backward-regexp "closed\r?\n\\'" nil t)
(let ((beg (match-beginning 0))
(end (match-end 0)))
(delete-region beg end)))
(t nil)))))
;;;;
;;;; HTTP functions for twitter-like serivce
;;;;
(defun twittering-http-application-headers (&optional method headers)
"Return an assoc list of HTTP headers for twittering-mode."
(unless method
(setq method "GET"))
(let ((headers headers))
(push (cons "User-Agent" (twittering-user-agent)) headers)
(when (string= "GET" method)
(push (cons "Accept"
(concat
"text/xml"
",application/xml"
",application/xhtml+xml"
",application/html;q=0.9"
",text/plain;q=0.8"
",image/png,*/*;q=0.5"))
headers)
(push (cons "Accept-Charset" "utf-8;q=0.7,*;q=0.7")
headers))
(when (string= "POST" method)
(push (cons "Content-Type" "text/plain") headers))
headers
))
(defun twittering-add-application-header-to-http-request (request account-info)
"Make a new HTTP request based on REQUEST with the authorization header.
The authorization header is generated from ACCOUNT-INFO.
ACCOUNT-INFO must be an alist that includes the following keys;
\"screen_name\" and \"password\" if `twittering-auth-method' is 'basic,
\"screen_name\", \"oauth_token\" and \"oauth_token_secret\" if
`twittering-auth-method' is 'oauth or 'xauth."
(let* ((method (cdr (assq 'method request)))
(auth-str
(cond
((eq twittering-auth-method 'basic)
(twittering-make-basic-authentication-string account-info))
((memq twittering-auth-method '(oauth xauth))
(twittering-make-oauth-authentication-string account-info request))
(t
nil)))
(cookie-str (twittering-make-cookie-string request account-info))
(application-headers
`(,@(twittering-http-application-headers method)
("Authorization" . ,auth-str)
,@(when cookie-str
`(("Cookie" . ,cookie-str))))))
(mapcar (lambda (entry)
(if (eq (car entry) 'header-list)
`(header-list
. ,(append (cdr entry) application-headers))
entry))
request)))
(defun twittering-get-error-message (header-info connection-info &optional buffer)
"Return an error message generated from the arguments.
HEADER-INFO must be an alist generated by `twittering-get-response-header'.
CONNECTION-INFO must be an alist generated by
`twittering-make-connection-info'. It may include some additional information
which is added by `twittering-send-http-request'.
BUFFER must be nil or a HTTP response body, which includes error messages from
the server when the HTTP status code equals to 400 or 403.
If BUFFER is nil, the current buffer is used instead."
(let ((buffer (or buffer (current-buffer)))
(status-line (cdr (assq 'status-line header-info)))
(status-code (cdr (assq 'status-code header-info)))
(format
(twittering-get-content-subtype-symbol-from-header-info header-info)))
(cond
((and (buffer-live-p buffer)
(member status-code '("400" "401" "403" "404")))
;; Twitter returns an error message as a HTTP response body if
;; HTTP status is "400 Bad Request" or "403 Forbidden".
;; See "HTTP Response Codes and Errors | dev.twitter.com"
;; http://dev.twitter.com/pages/responses_errors .
;;
;; However, Twitter seems to return an error message even when
;; the HTTP status is "401 Unauthorized" or "404 Not Found".
(let* ((error-mes
(cond
((eq format 'xml)
(let ((xmltree
(with-current-buffer buffer
(twittering-xml-parse-region (point-min)
(point-max)))))
(car (cddr (assq 'error (or (assq 'errors xmltree)
(assq 'hash xmltree)))))))
((eq format 'json)
(let ((json-object (with-current-buffer buffer
(twittering-json-read))))
(cdr (assq 'error json-object))))
(t
;; ATOM is not supported.
nil))))
(if error-mes
(format "%s (%s)" status-line error-mes)
status-line)))
(t
status-line))))
(defun twittering-http-get (account-info-alist host method &optional parameters format additional-info sentinel clean-up-sentinel)
"Send a HTTP GET request with application headers.
ACCOUNT-INFO-ALIST is an alist used by
`twittering-add-application-header-to-http-request'.
The alist made by `((account-info . ,ACCOUNT-INFO-ALIST) ,@ADDITIONAL-INFO)'
is used as the argument `additional-info' of `twittering-send-http-request'.
HOST is hostname of remote side, api.twitter.com (or search.twitter.com).
METHOD must be one of Twitter API method classes
(statuses, users or direct_messages).
PARAMETERS is alist of URI parameters.
ex) ((\"mode\" . \"view\") (\"page\" . \"6\")) => <URI>?mode=view&page=6
FORMAT is a response data format (\"xml\", \"atom\", \"json\")"
(let* ((format (or format "xml"))
(sentinel
(lexical-let ((sentinel (or sentinel
'twittering-http-get-default-sentinel)))
(lambda (proc status connection-info header-info)
(twittering-update-server-info connection-info header-info)
(apply sentinel proc status connection-info header-info nil))))
(path (concat "/" method "." format))
(headers nil)
(port nil)
(post-body "")
(request
(twittering-add-application-header-to-http-request
(twittering-make-http-request "GET" headers host port path
parameters post-body
twittering-use-ssl)
account-info-alist))
(additional-info
`((account-info . ,account-info-alist)
,@additional-info)))
(twittering-send-http-request request additional-info
sentinel clean-up-sentinel)))
(defun twittering-http-get-default-sentinel (proc status connection-info header-info)
(let ((status-line (cdr (assq 'status-line header-info)))
(status-code (cdr (assq 'status-code header-info)))
(format
(twittering-get-content-subtype-symbol-from-header-info header-info)))
(case-string
status-code
(("200")
(debug-printf "connection-info=%s" connection-info)
(let* ((spec (cdr (assq 'timeline-spec connection-info)))
(spec-string (cdr (assq 'timeline-spec-string connection-info)))
(service-method (cdr (assq 'service-method connection-info)))
(statuses
(cond
((eq format 'json)
(let ((json-array (twittering-json-read)))
(cond
((null json-array)
nil)
((eq (car spec) 'search)
(cond
((memq service-method '(twitter statusnet))
(mapcar 'twittering-json-object-to-a-status-on-search
(cdr (assq 'results json-array))))
((eq service-method 'twitter-api-v1.1)
(mapcar 'twittering-json-object-to-a-status
(cdr (assq 'statuses json-array))))))
((twittering-timeline-spec-is-direct-messages-p spec)
(mapcar
'twittering-json-object-to-a-status-on-direct-messages
json-array))
(t
(mapcar 'twittering-json-object-to-a-status
json-array)))))
((eq format 'xml)
(let ((xmltree
(twittering-xml-parse-region (point-min) (point-max))))
(when xmltree
(twittering-xmltree-to-status xmltree))))
((eq format 'atom)
(let ((xmltree
(twittering-xml-parse-region (point-min) (point-max))))
(when xmltree
(twittering-atom-xmltree-to-status xmltree))))
(t
nil)))
(rendered-tweets nil))
(let ((updated-timeline-info
(twittering-add-statuses-to-timeline-data statuses spec))
(buffer (twittering-get-buffer-from-spec spec)))
;; FIXME: We should retrieve un-retrieved statuses until
;; statuses is nil. twitter server returns nil as
;; xmltree with HTTP status-code is "200" when we
;; retrieved all un-retrieved statuses.
(if twittering-notify-successful-http-get
(if updated-timeline-info
(concat
(format "Fetching %s. Success. " spec-string)
(mapconcat
(lambda (info)
(let ((spec-string (nth 0 info))
(num (nth 1 info)))
(format "%s: +%d" spec-string num)))
updated-timeline-info
", "))
(format "Fetching %s. Success. (No new tweets)"
spec-string))
nil))))
(("404")
;; The requested resource does not exist.
(let ((spec (cdr (assq 'timeline-spec connection-info)))
(spec-string (cdr (assq 'timeline-spec-string connection-info))))
;; Remove specs related to the invalid spec from history.
(mapc
(lambda (buffer)
(let ((other-spec (twittering-get-timeline-spec-for-buffer buffer))
(other-spec-string
(twittering-get-timeline-spec-string-for-buffer buffer)))
(when (twittering-timeline-spec-depending-on-p other-spec spec)
(twittering-remove-timeline-spec-string-from-history
other-spec-string))))
(twittering-get-buffer-list)))
(format "Response: %s"
(twittering-get-error-message header-info connection-info)))
(t
(format "Response: %s"
(twittering-get-error-message header-info connection-info))))))
(defun twittering-retrieve-single-tweet-sentinel (proc status connection-info header-info)
(let ((status-line (cdr (assq 'status-line header-info)))
(status-code (cdr (assq 'status-code header-info)))
(format
(twittering-get-content-subtype-symbol-from-header-info header-info)))
(case-string
status-code
(("200" "403" "404")
(debug-printf "connection-info=%s" connection-info)
(let* ((id (cdr (assq 'id connection-info)))
(user-screen-name (cdr (assq 'user-screen-name connection-info)))
(status
(cond
((string= status-code "403")
;; Forbidden. Maybe a protected tweet?
(twittering-make-alist-of-forbidden-tweet id
user-screen-name))
((string= status-code "404")
;; The requested resource does not exist.
(twittering-make-alist-of-non-existent-tweet id
user-screen-name))
((eq format 'json)
(let ((json-object (twittering-json-read)))
(twittering-json-object-to-a-status json-object)))
((eq format 'xml)
(let ((xmltree
(twittering-xml-parse-region (point-min) (point-max))))
(when xmltree
(car
(twittering-xmltree-to-status
`((statuses nil ,@xmltree)))))))
(t
nil))))
(when status
(twittering-add-statuses-to-timeline-data `(,status) '(:single))
(let ((buffer (cdr (assq 'buffer connection-info)))
(spec (cdr (assq 'timeline-spec connection-info)))
(prop
(cdr (assq 'property-to-be-redisplayed connection-info))))
(cond
((null prop)
;; The process has been invoked via `twittering-call-api' with
;; the command `retrieve-timeline', not the command
;; `retrieve-single-tweet' for rendering a replied tweet.
;; No special property that specifies regions being re-rendered
;; is given.
(let ((new-statuses `(,status))
(buffer (twittering-get-buffer-from-spec spec)))
(when (and new-statuses buffer)
(twittering-render-timeline buffer new-statuses t))))
((and buffer prop (buffer-live-p buffer))
(twittering-redisplay-status-on-each-buffer buffer prop)
(with-current-buffer buffer
(save-excursion
(let ((buffer-read-only nil))
(lexical-let ((prop prop))
(twittering-for-each-property-region
prop
(lambda (beg end value)
;; Remove the property required no longer.
(remove-text-properties beg end `(,prop nil))
(goto-char beg)
(twittering-render-replied-statuses)))))))))))
(cond
((string= status-code "403")
(format "You are not authorized to see this tweet (ID %s)." id))
((string= status-code "404")
(format "The tweet with ID %s does not exist." id))
(twittering-notify-successful-http-get
(format "Fetching %s. Success." id))
(t
nil))))
(t
(format "Response: %s"
(twittering-get-error-message header-info connection-info))))))
(defmacro twittering-http-get-list-sentinel-base (what)
`(let ((status-line (cdr (assq 'status-line header-info)))
(status-code (cdr (assq 'status-code header-info)))
(format
(twittering-get-content-subtype-symbol-from-header-info header-info))
(indexes nil)
(mes nil))
(case-string
status-code
(("200")
(cond
((eq format 'xml)
(let ((xmltree (twittering-xml-parse-region (point-min) (point-max))))
(when xmltree
(setq indexes
(mapcar
(lambda (c-node)
(caddr (assq ,what c-node)))
(remove nil
(mapcar
(lambda (node)
(and (consp node) (eq 'list (car node))
node))
(cdr-safe
(assq 'lists (assq 'lists_list xmltree))))
))
))))
((eq format 'json)
(let* ((json-object (twittering-json-read))
(json-list
(cond
((arrayp json-object)
;; GET lists/list in the Twitter REST API v1.1 returns
;; an array.
json-object)
(t
;; GET lists/subscriptions in the Twitter REST API v1.1
;; returns an alist.
(cdr (assq 'lists json-object))))))
(when json-object
(setq indexes
(mapcar (lambda (entry)
(cdr (assq ,what entry)))
json-list)))))
(t
(error "Format \"%s\" is not supported" format)
nil)))
(t
(setq mes (format "Response: %s"
(twittering-get-error-message header-info
connection-info)))))
(setq twittering-list-index-retrieved
(or indexes
mes
"")) ;; set "" explicitly if user does not have a list.
mes))
(defun twittering-http-get-list-index-sentinel (proc status connection-info header-info)
(twittering-http-get-list-sentinel-base 'slug))
(defun twittering-http-get-list-subscriptions-sentinel (proc status connection-info header-info)
(let ((result (twittering-http-get-list-sentinel-base 'full_name)))
(when (listp twittering-list-index-retrieved)
(setq twittering-list-index-retrieved
(mapcar (lambda (str)
(and (string-match "\\`@\\(.*\\)\\'" str)
(match-string 1 str)))
twittering-list-index-retrieved)))
result))
(defun twittering-http-post (account-info-alist host method &optional parameters format additional-info sentinel clean-up-sentinel)
"Send HTTP POST request to api.twitter.com (or search.twitter.com)
ACCOUNT-INFO-ALIST is an alist used by
`twittering-add-application-header-to-http-request'.
The alist made by `((account-info . ,ACCOUNT-INFO-ALIST) ,@ADDITIONAL-INFO)'
is used as the argument `additional-info' of `twittering-send-http-request'.
HOST is hostname of remote side, api.twitter.com (or search.twitter.com).
METHOD must be one of Twitter API method classes
(statuses, users or direct_messages).
PARAMETERS is alist of URI parameters.
ex) ((\"mode\" . \"view\") (\"page\" . \"6\")) => <URI>?mode=view&page=6
FORMAT is a response data format (\"xml\", \"atom\", \"json\")"
(let* ((format (or format "xml"))
(sentinel
(lexical-let ((sentinel (or sentinel
'twittering-http-post-default-sentinel)))
(lambda (proc status connection-info header-info)
(twittering-update-server-info connection-info header-info)
(apply sentinel proc status connection-info header-info nil))))
(path (concat "/" method "." format))
(headers nil)
(port nil)
(post-body "")
(request
(twittering-add-application-header-to-http-request
(twittering-make-http-request "POST" headers host port path
parameters post-body
twittering-use-ssl)
account-info-alist))
(additional-info `((account-info . ,account-info-alist)
,@additional-info)))
(twittering-send-http-request request additional-info
sentinel clean-up-sentinel)))
(defun twittering-http-post-default-sentinel (proc status connection-info header-info)
(let ((status-line (cdr (assq 'status-line header-info)))
(status-code (cdr (assq 'status-code header-info))))
(case-string
status-code
(("200")
"Success: Post.")
(t
(format "Response: %s"
(twittering-get-error-message header-info connection-info))))))
(defun twittering-http-post-destroy-status-sentinel (proc status connection-info header-info)
"A sentinel for deleting a status invoked via `twittering-call-api'."
(let ((status-line (cdr (assq 'status-line header-info)))
(status-code (cdr (assq 'status-code header-info)))
(format
(twittering-get-content-subtype-symbol-from-header-info header-info)))
(case-string
status-code
(("200")
(let* ((params
(cond
((eq format 'xml)
(let ((xml
(twittering-xml-parse-region (point-min) (point-max))))
`((id . ,(elt (assq 'id (assq 'status xml)) 2))
(text . ,(elt (assq 'text (assq 'status xml)) 2)))))
((eq format 'json)
(let ((json-object (twittering-json-read)))
`((id . ,(cdr (assq 'id_str json-object)))
(text . ,(cdr (assq 'text json-object))))))
(t
(error "Format \"%s\" is not supported" format)
nil)))
(id (cdr (assq 'id params)))
(text (cdr (assq 'text params))))
(cond
(id
(twittering-delete-status-from-data-table id)
(format "Deleting \"%s\". Success." text))
(t
"Failure: the response for deletion could not be parsed."))))
(t
(format "Response: %s"
(twittering-get-error-message header-info connection-info))))))
;;;;
;;;; OAuth
;;;;
(defun twittering-oauth-url-encode (str &optional coding-system)
"Encode string according to Percent-Encoding defined in RFC 3986."
(let ((coding-system (or (when (and coding-system
(coding-system-p coding-system))
coding-system)
'utf-8)))
(mapconcat
(lambda (c)
(cond
((or (and (<= ?A c) (<= c ?Z))
(and (<= ?a c) (<= c ?z))
(and (<= ?0 c) (<= c ?9))
(eq ?. c)
(eq ?- c)
(eq ?_ c)
(eq ?~ c))
(char-to-string c))
(t (format "%%%02X" c))))
(encode-coding-string str coding-system)
"")))
(defun twittering-oauth-unhex (c)
(cond
((and (<= ?0 c) (<= c ?9))
(- c ?0))
((and (<= ?A c) (<= c ?F))
(+ 10 (- c ?A)))
((and (<= ?a c) (<= c ?f))
(+ 10 (- c ?a)))
))
(defun twittering-oauth-url-decode (str &optional coding-system)
(let* ((coding-system (or (when (and coding-system
(coding-system-p coding-system))
coding-system)
'utf-8))
(substr-list (split-string str "%"))
(head (car substr-list))
(tail (cdr substr-list)))
(decode-coding-string
(concat
head
(mapconcat
(lambda (substr)
(if (string-match "\\`\\([0-9a-fA-F]\\)\\([0-9a-fA-F]\\)\\(.*\\)\\'"
substr)
(let* ((c1 (string-to-char (match-string 1 substr)))
(c0 (string-to-char (match-string 2 substr)))
(tail (match-string 3 substr))
(ch (+ (* 16 (twittering-oauth-unhex c1))
(twittering-oauth-unhex c0))))
(concat (char-to-string ch) tail))
substr))
tail
""))
coding-system)))
(defun twittering-oauth-make-signature-base-string (method base-url parameters)
;; "OAuth Core 1.0a"
;; http://oauth.net/core/1.0a/#anchor13
(let* ((sorted-parameters (copy-sequence parameters))
(sorted-parameters
(sort sorted-parameters
(lambda (entry1 entry2)
(string< (car entry1) (car entry2))))))
(concat
method
"&"
(twittering-oauth-url-encode base-url)
"&"
(mapconcat
(lambda (entry)
(let ((key (car entry))
(value (cdr entry)))
(concat (twittering-oauth-url-encode key)
"%3D"
(twittering-oauth-url-encode value))))
sorted-parameters
"%26"))))
(defun twittering-oauth-make-random-string (len)
(let* ((table
(concat
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"))
(n (length table))
(l 0)
(result (make-string len ?0)))
(while (< l len)
(aset result l (aref table (random n)))
(setq l (1+ l)))
result))
(defun twittering-sha1 (&rest args)
"Return the SHA1 (Secure Hash Algorithm) of an object.
This is equivalent to the function `sha1' except that
`coding-system-for-read' and `coding-system-for-write' are bound to the
symbol `binary'.
The function `sha1' uses an external program for large object. However,
the coding system for transferring data from/to the program is not fixed,
at least in the implementation distributed with GNU Emacs 21.4.1, 22.2.1
and 23.2.1.
Therefore, the result from the function `sha1' may depend on the current
coding system.
This function avoid the dependency by binding `coding-system-for-read' and
`coding-system-for-write' to the symbol `binary'."
(require 'sha1)
(let ((coding-system-for-read 'binary)
(coding-system-for-write 'binary)
;; Bind `default-directory' to the temporary directory
;; because it is possible that the directory pointed by
;; `default-directory' has been already removed.
(default-directory temporary-file-directory))
(apply 'sha1 args)))
;;;
;;; The below function is derived from `hmac-sha1' retrieved
;;; from http://www.emacswiki.org/emacs/HmacShaOne.
;;;
(defun twittering-hmac-sha1 (key message)
"Return an HMAC-SHA1 authentication code for KEY and MESSAGE.
KEY and MESSAGE must be unibyte strings. The result is a unibyte
string. Use the function `encode-hex-string' or the function
`base64-encode-string' to produce human-readable output.
See URL:<http://en.wikipedia.org/wiki/HMAC> for more information
on the HMAC-SHA1 algorithm.
The Emacs multibyte representation actually uses a series of
8-bit values under the hood, so we could have allowed multibyte
strings as arguments. However, internal 8-bit values don't
correspond to any external representation \(at least for major
version 22). This makes multibyte strings useless for generating
hashes.
Instead, callers must explicitly pick and use an encoding for
their multibyte data. Most callers will want to use UTF-8
encoding, which we can generate as follows:
(let ((unibyte-key (encode-coding-string key 'utf-8 t))
(unibyte-value (encode-coding-string value 'utf-8 t)))
(twittering-hmac-sha1 unibyte-key unibyte-value))
For keys and values that are already unibyte, the
`encode-coding-string' calls just return the same string."
;;; Return an HMAC-SHA1 authentication code for KEY and MESSAGE.
;;;
;;; KEY and MESSAGE must be unibyte strings. The result is a unibyte
;;; string. Use the function `encode-hex-string' or the function
;;; `base64-encode-string' to produce human-readable output.
;;;
;;; See URL:<http://en.wikipedia.org/wiki/HMAC> for more information
;;; on the HMAC-SHA1 algorithm.
;;;
;;; The Emacs multibyte representation actually uses a series of
;;; 8-bit values under the hood, so we could have allowed multibyte
;;; strings as arguments. However, internal 8-bit values don't
;;; correspond to any external representation \(at least for major
;;; version 22). This makes multibyte strings useless for generating
;;; hashes.
;;;
;;; Instead, callers must explicitly pick and use an encoding for
;;; their multibyte data. Most callers will want to use UTF-8
;;; encoding, which we can generate as follows:
;;;
;;; (let ((unibyte-key (encode-coding-string key 'utf-8 t))
;;; (unibyte-value (encode-coding-string value 'utf-8 t)))
;;; (hmac-sha1 unibyte-key unibyte-value))
;;;
;;; For keys and values that are already unibyte, the
;;; `encode-coding-string' calls just return the same string.
;;;
;;; Author: Derek Upham - sand (at) blarg.net
;;;
;;; Copyright: This code is in the public domain.
(require 'sha1)
(when (multibyte-string-p key)
(error "key must be unibyte"))
(when (multibyte-string-p message)
(error "message must be unibyte"))
;; The key block is always exactly the block size of the hash
;; algorithm. If the key is too small, we pad it with zeroes (or
;; instead, we initialize the key block with zeroes and copy the
;; key onto the nulls). If the key is too large, we run it
;; through the hash algorithm and use the hashed value (strange
;; but true).
(let ((+hmac-sha1-block-size-bytes+ 64)) ; SHA-1 uses 512-bit blocks
(when (< +hmac-sha1-block-size-bytes+ (length key))
(setq key (twittering-sha1 key nil nil t)))
(let ((key-block (make-vector +hmac-sha1-block-size-bytes+ 0)))
(dotimes (i (length key))
(aset key-block i (aref key i)))
(let ((opad (make-vector +hmac-sha1-block-size-bytes+ #x5c))
(ipad (make-vector +hmac-sha1-block-size-bytes+ #x36)))
(dotimes (i +hmac-sha1-block-size-bytes+)
(aset ipad i (logxor (aref ipad i) (aref key-block i)))
(aset opad i (logxor (aref opad i) (aref key-block i))))
(when (fboundp 'unibyte-string)
;; `concat' of Emacs23 (and later?) generates a multi-byte
;; string from a vector of characters with eight bit.
;; Since `opad' and `ipad' must be unibyte, we have to
;; convert them by using `unibyte-string'.
;; We cannot use `string-as-unibyte' here because it encodes
;; bytes with the manner of UTF-8.
(setq opad (apply 'unibyte-string (mapcar 'identity opad)))
(setq ipad (apply 'unibyte-string (mapcar 'identity ipad))))
(twittering-sha1 (concat opad
(twittering-sha1 (concat ipad message)
nil nil t))
nil nil t)))))
(defun twittering-oauth-auth-str (method base-url query-parameters oauth-parameters key)
"Generate the value for HTTP Authorization header on OAuth.
QUERY-PARAMETERS is an alist for query parameters, where name and value
must be encoded into the same as they will be sent."
(let* ((parameters (append query-parameters oauth-parameters))
(base-string
(twittering-oauth-make-signature-base-string method base-url parameters))
(key (if (multibyte-string-p key)
(string-make-unibyte key)
key))
(base-string (if (multibyte-string-p base-string)
(string-make-unibyte base-string)
base-string))
(signature
(base64-encode-string (twittering-hmac-sha1 key base-string))))
(concat
"OAuth "
(mapconcat
(lambda (entry)
(concat (car entry) "=\"" (cdr entry) "\""))
oauth-parameters
",")
",oauth_signature=\"" (twittering-oauth-url-encode signature) "\"")))
(defun twittering-oauth-auth-str-request-token (url query-parameters consumer-key consumer-secret &optional oauth-parameters)
(let ((key (concat consumer-secret "&"))
(oauth-params
(or oauth-parameters
`(("oauth_nonce" . ,(twittering-oauth-make-random-string 43))
("oauth_callback" . "oob")
("oauth_signature_method" . "HMAC-SHA1")
("oauth_timestamp" . ,(format-time-string "%s"))
("oauth_consumer_key" . ,consumer-key)
("oauth_version" . "1.0")))))
(twittering-oauth-auth-str "POST" url query-parameters oauth-params key)))
(defun twittering-oauth-auth-str-exchange-token (url query-parameters consumer-key consumer-secret request-token request-token-secret verifier &optional oauth-parameters)
(let ((key (concat consumer-secret "&" request-token-secret))
(oauth-params
(or oauth-parameters
`(("oauth_consumer_key" . ,consumer-key)
("oauth_nonce" . ,(twittering-oauth-make-random-string 43))
("oauth_signature_method" . "HMAC-SHA1")
("oauth_timestamp" . ,(format-time-string "%s"))
("oauth_version" . "1.0")
("oauth_token" . ,request-token)
("oauth_verifier" . ,verifier)))))
(twittering-oauth-auth-str "POST" url query-parameters oauth-params key)))
(defun twittering-oauth-auth-str-access (method url query-parameters consumer-key consumer-secret access-token access-token-secret &optional oauth-parameters)
"Generate a string for Authorization in HTTP header on OAuth.
METHOD means HTTP method such as \"GET\", \"POST\", etc. URL means a simple
URL without port number and query parameters.
QUERY-PARAMETERS means an alist of query parameters such as
'((\"status\" . \"test%20tweet\")
(\"in_reply_to_status_id\" . \"12345678\")),
where name and value must be encoded into the same as they will be sent.
CONSUMER-KEY and CONSUMER-SECRET specifies the consumer.
ACCESS-TOKEN and ACCESS-TOKEN-SECRET must be authorized before calling this
function."
(let ((key (concat consumer-secret "&" access-token-secret))
(oauth-params
(or oauth-parameters
`(("oauth_consumer_key" . ,consumer-key)
("oauth_nonce" . ,(twittering-oauth-make-random-string 43))
("oauth_signature_method" . "HMAC-SHA1")
("oauth_timestamp" . ,(format-time-string "%s"))
("oauth_version" . "1.0")
("oauth_token" . ,access-token)))))
(twittering-oauth-auth-str method url query-parameters oauth-params key)))
;; "Using xAuth | dev.twitter.com"
;; http://dev.twitter.com/pages/xauth
(defun twittering-xauth-auth-str-access-token (url query-parameters consumer-key consumer-secret username password &optional oauth-parameters)
(let ((key (concat consumer-secret "&"))
(oauth-params
(or oauth-parameters
`(("oauth_nonce" . ,(twittering-oauth-make-random-string 43))
("oauth_signature_method" . "HMAC-SHA1")
("oauth_timestamp" . ,(format-time-string "%s"))
("oauth_consumer_key" . ,consumer-key)
("oauth_version" . "1.0"))))
(query-params
(append query-parameters
`(("x_auth_mode" . "client_auth")
("x_auth_password"
. ,(twittering-oauth-url-encode password))
("x_auth_username"
. ,(twittering-oauth-url-encode username))))))
(twittering-oauth-auth-str "POST" url query-params oauth-params key)))
;; "OAuth Core 1.0a"
;; http://oauth.net/core/1.0a/#response_parameters
(defun twittering-oauth-make-response-alist (str)
(mapcar
(lambda (entry)
(let* ((pair (split-string entry "="))
(name-entry (car pair))
(value-entry (cadr pair))
(name (and name-entry (twittering-oauth-url-decode name-entry)))
(value (and value-entry
(twittering-oauth-url-decode value-entry))))
`(,name . ,value)))
(split-string str "&")))
(defun twittering-oauth-get-response-alist (buffer)
(with-current-buffer buffer
(goto-char (point-min))
(when (search-forward-regexp
"\\`\\(\\(HTTP/1\\.[01]\\|HTTP/2\\(?:\\.0\\)?\\) \\([0-9][0-9][0-9]\\)\\(.*?\\)\\)\r?\n"
nil t)
(let ((status-line (match-string 1))
(http-version (match-string 2))
(status-code (match-string 3))
(reason-phrase (match-string 4)))
(cond
((not (string-match "2[0-9][0-9]" status-code))
(message "Response: %s" status-line)
nil)
((search-forward-regexp "\r?\n\r?\n" nil t)
(let ((beg (match-end 0))
(end (point-max)))
(twittering-oauth-make-response-alist (buffer-substring beg end))))
(t
(message "Response: %s" status-line)
nil))))))
(defun twittering-oauth-get-token-alist-url (url auth-str post-body)
(let* ((url-request-method "POST")
(url-request-extra-headers
`(("Authorization" . ,auth-str)
("Accept-Charset" . "us-ascii")
("Content-Type" . "application/x-www-form-urlencoded")
("Content-Length" . ,(format "%d" (length post-body)))))
(url-request-data post-body)
(coding-system-for-read 'utf-8-unix))
(lexical-let ((result 'queried))
(let ((buffer
(url-retrieve
url
(lambda (&rest args)
(let* ((status (if (< 21 emacs-major-version)
(car args)
nil))
(callback-args (if (< 21 emacs-major-version)
(cdr args)
args))
(response-buffer (current-buffer)))
(setq result
(twittering-oauth-get-response-alist response-buffer))
)))))
(while (eq result 'queried)
(sleep-for 0.1))
(unless twittering-debug-mode
(kill-buffer buffer))
result))))
(defun twittering-oauth-get-token-alist (url auth-str &optional post-body)
(let ((request
(twittering-make-http-request-from-uri
"POST"
`(("Authorization" . ,auth-str)
("Accept-Charset" . "us-ascii")
("Content-Type" . "application/x-www-form-urlencoded"))
url post-body)))
(lexical-let ((result 'queried))
(let ((proc
(twittering-send-http-request
request nil
(lambda (proc status connection-info header-info)
(let ((status-line (cdr (assq 'status-line header-info)))
(status-code (cdr (assq 'status-code header-info))))
(case-string
status-code
(("200")
(when twittering-debug-mode
(let ((buffer (current-buffer)))
(with-current-buffer (twittering-debug-buffer)
(insert-buffer-substring buffer))))
(setq result
(twittering-oauth-make-response-alist
(buffer-string)))
nil)
(t
(setq result nil)
(format "Response: %s" status-line)))))
(lambda (proc status connection-info)
(when (and (not (twittering-process-alive-p proc))
(eq result 'queried))
(setq result nil))))))
(twittering-wait-while nil 0.1
(and (eq result 'queried)
(twittering-process-alive-p proc)))
(when (and (eq result 'queried)
(not (twittering-process-alive-p proc)))
;; If the process has been dead, wait a moment because
;; Emacs may be in the middle of evaluating the sentinel.
(twittering-wait-while 10 0.1
(eq result 'queried)
nil
(setq result nil)))
result))))
(defun twittering-oauth-get-request-token (url consumer-key consumer-secret)
(let ((auth-str
(twittering-oauth-auth-str-request-token
url nil consumer-key consumer-secret)))
(twittering-oauth-get-token-alist url auth-str)))
(defun twittering-oauth-exchange-request-token (url consumer-key consumer-secret request-token request-token-secret verifier)
(let ((auth-str
(twittering-oauth-auth-str-exchange-token
url nil
consumer-key consumer-secret
request-token request-token-secret verifier)))
(twittering-oauth-get-token-alist url auth-str)))
(defun twittering-oauth-get-access-token (request-token-url authorize-url-func access-token-url consumer-key consumer-secret consumer-name)
"Return an alist of authorized access token.
The function retrieves a request token from the site specified by
REQUEST-TOKEN-URL. Then, The function asks a WWW browser to authorize the
token by calling `browse-url'. The URL for authorization is calculated by
calling AUTHORIZE-URL-FUNC with the request token as an argument.
AUTHORIZE-URL-FUNC is called as `(funcal AUTHORIZE-URL-FUNC request-token)',
where the request-token is a string.
After calling `browse-url', the function waits for user to input the PIN code
that is displayed in the browser. The request token is authorized by the
PIN code, and then it is exchanged for the access token on the site