austin----- / weibo.emacs

Sina weibo client in Emacs
https://github.com/austin-----/weibo.emacs
105 stars 28 forks source link

如何发表带图片的原创微博 #16

Open naihe2010 opened 12 years ago

naihe2010 commented 12 years ago

按P打开编辑新微博的小窗口以后,只能输入文本,以后是否准备支持插入图片?

austin----- commented 12 years ago

@naihe2010 你好,谢谢你的建议。暂时还不能发布图片,以后可以加入支持。

xuchunyang commented 9 years ago

@austin----- 发歹带图片的API在 http://open.weibo.com/wiki/2/statuses/upload ,参数中有个pic

pic true    binary  要上传的图片,仅支持JPEG、GIF、PNG格式,图片大小小于5M。

其类型是 binary,这个参数应该如何处理(1、ELisp 如何读图片文件;2、如何设置url-request-data)?

并且还要求使用 multipart/form-data 编码方式,是不是就是把 Content-Type 设置成 multipart/form-data

目前相关的实现如下(这里的参数都是 string 类型的):


(defun weibo-send-url (url args)
  (let ((url-request-method "POST")
        (url-request-extra-headers
         `(("Content-Type" . "application/x-www-form-urlencoded")
           ("Authorization" . ,(format "OAuth2 %s" (url-hexify-string (weibo-get-token))))))
        (url-request-data
         (mapconcat (lambda (arg)
                      (concat (url-hexify-string (car arg))
                              "="
                              (url-hexify-string (cdr arg))))
                    args
                    "&")))
    (cl-flet ((message (&rest args) nil))
      (url-retrieve-synchronously url))))
austin----- commented 9 years ago

(with-temp-buffer (insert-file-content "path") (buffer-substring-no-properties (point-min) (point-max))) 可以得到文件内容

austin----- commented 9 years ago

实现的时候需要构建一个multipart/form-data的请求。如果没有现成的elisp函数可用的话,需要了解一下multipart的格式。这里有详细的说明:http://tinyurl.com/2e45t2n

xuchunyang commented 9 years ago

我看了你给的链接,其中给的一个示例(user agent 应该发给 server 的数据):

   Content-Type: multipart/form-data; boundary=AaB03x

   --AaB03x
   Content-Disposition: form-data; name="submit-name"

   Larry
   --AaB03x
   Content-Disposition: form-data; name="files"; filename="file1.txt"
   Content-Type: text/plain

   ... contents of file1.txt ...
   --AaB03x--

(1) 但是这里有多个 Content-Type,不知道怎么构造?

(2) 我的失败的尝试如下,是不是应该把 binary (pic)和 string (status)都塞到 url-request-data

(let ((url-request-method "POST")
      (url-request-extra-headers
       `(("Content-Type" . "multipart/form-data; boundary=AaB03x")  ; <== (1)
         ("Authorization" . ,(format
                              "OAuth2 %s"
                              (url-hexify-string (weibo-get-token))))))
      (url-request-data
       (url-hexify-string
        (concat "status=a sample tweet"
                "&"
                "pic=" img-binary))))   ; <== (2)
  (cl-flet ((message (&rest args) nil))
    (url-retrieve-synchronously
     "https://upload.api.weibo.com/2/statuses/upload.json")))

(setq img-binary
      (with-temp-buffer
        (insert-file-literally "~/Desktop/a.jpeg")
        (buffer-substring-no-properties (point-min)
                                        (point-max))))
austin----- commented 9 years ago

multipart的意思就是,每一个query的参数都是一个独立的content-disposition。一般参数就参照例子中submit-name去构造。文件(binary)参照files的例子构造,区别是在这里的话,binary的名字应该是pic,type是image/jpeg,image/png或者image/gif。然后img-binary就直接concat到这个disposition里面。

建议可以先构造好request,然后用fiddler之类的工具先试试是否可以被weibo接受。request应该类似于这样:

Content-Type: multipart/form-data; boundary=AaB03x

--AaB03x
Content-Disposition: form-data; name="status"

a sample tweet
--AaB03x
Content-Disposition: form-data; name="files"; filename="file_to_upload.jpg"
Content-Type: image/jpeg

... contents of <img-binary> ...
--AaB03x--
xuchunyang commented 9 years ago

还是不知道如何构造(url-request-extra-headersurl-request-data应该怎么填充),不清楚有没有人能帮忙?

用 curl 很容易模拟这个请求:

➜  ~  curl -X POST  -H "Authorization: _Token_" \
>      -F "status=Sample tweet with picture." \
>      -F "pic=@/users/xcy/Desktop/a.jpeg"  \
>      https://api.weibo.com/2/statuses/upload.json
austin----- commented 9 years ago

用wireshark抓一下包,就能看到curl发送的request了。

xuchunyang commented 9 years ago

一次成功的 HTTP 请求是这样的:

POST /2/statuses/upload.json HTTP/1.1
Host: api.weibo.com
Authorization: OAuth2 2.00GnoVkCoL1sjD1772f1d78awzXvWE
Cache-Control: no-cache

----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="status"

Sample tweet with picture
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="pic"; filename="a.jpg"
Content-Type: image/jpeg

----WebKitFormBoundaryE19zNvXGzXaLvS5C

从 Chrome Developer Tools 拷贝下来的完整 curl 指令:

curl 'https://api.weibo.com/2/statuses/upload.json' -H 'origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm' -H 'accept-encoding: gzip, deflate' -H 'accept-language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2' -H 'authorization: OAuth2 2.00GnoVkCoL1sjD1772f1d78awzXvWE' -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundaryUhF5gxr7J2MvOooO' -H 'accept: */*' -H 'cache-control: no-cache' -H 'cookie: SINAGLOBAL=7074588378891.349.1404558680176; __utma=15428400.471598242.1423718540.1423718540.1423718540.1; __utmz=15428400.1423718540.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); myuid=2519560532; wvr=6; _s_tentry=login.sina.com.cn; Apache=9431151158642.023.1427114663568; ULV=1427114664336:165:38:5:9431151158642.023.1427114663568:1427106607630; SUS=SID-2519560532-1427205957-GZ-74zji-26f5fd39b461f21a66f5b6803b7c841f; SUE=es%3D0a000cff1f95041bb7d7ce91f4951916%26ev%3Dv1%26es2%3Ded4ef021ad50d11fea1ed5057961f9ca%26rs0%3Di1ZUtqBy4Br1BAo00MqwUfI%252BmuNIpxZSf6jc%252BQD7bZEzXw94iGCxtmK34Yt%252BiksucyZ1siMcuW%252FQSn7q8z4szEzQIGnNsYYgRU5ObHSRx3X%252Bv4mKoiEWd4mpwWYeBkfMk1yyhlPHsZ%252BdevWvP47kM1KOe37eVi%252F0wxRuc80xWEQ%253D%26rv%3D0; SUP=cv%3D1%26bt%3D1427205957%26et%3D1427292357%26d%3Dc909%26i%3D841f%26us%3D1%26vf%3D0%26vt%3D0%26ac%3D2%26st%3D0%26uid%3D2519560532%26name%3Dxuchunyang56%2540gmail.com%26nick%3Dskin%26fmp%3D%26lcp%3D; SUB=_2A254FR8VDeTxGeRL6lsU9i7JyD6IHXVbY3fdrDV8PUNbvtBeLUHtkW8yLF1Zsyy6Rct073FRgXUWX_HrvA..; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9W5gC63qiNlFxcfBGDdL7Ane5JpX5KMt; SUHB=0QS4KHgr_32aeY; ALF=1458741957; SSOLoginState=1427205957; UOR=www.infzm.com,widget.weibo.com,www.infzm.com' -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36' --data-binary $'------WebKitFormBoundaryUhF5gxr7J2MvOooO\r\nContent-Disposition: form-data; name="status"\r\n\r\nSample tweet with picture\r\n------WebKitFormBoundaryUhF5gxr7J2MvOooO\r\nContent-Disposition: form-data; name="pic"; filename="a.jpg"\r\nContent-Type: image/jpeg\r\n\r\n\r\n------WebKitFormBoundaryUhF5gxr7J2MvOooO--\r\n' --compressed

我的失败的尝试(估计是url-request-data有问题):

(setq image-data
      (with-temp-buffer
        (insert-file-contents-literally "~/Desktop/a.jpg")
        (buffer-substring-no-properties (point-min)
                                        (point-max))))

(let ((url-request-method "POST")
      (url-request-extra-headers
       `(("Content-Type" .
          "multipart/form-data; boundary=---")
         ("Authorization" .
          ,(format "OAuth2 %s" (url-hexify-string (weibo-get-token))))))
      (url-request-data
       (concat "---\r\nContent-Disposition: form-data; name=\"status\"\r\n\r\n"
               "This is a test\r\n"
               "---\r\n"
               "Content-Disposition: form-data; name=\"pic\"; filename=\"a.jpg\"\r\n"
               "Content-Type: image/jpeg\r\n\r\n"
               image-data
               "\r\n"
               "---")))
  (url-retrieve-synchronously "https://api.weibo.com/2/statuses/upload.json"))

如果你知道如何操作,请直接把 elisp 代码贴出来。

austin----- commented 9 years ago

boundary可能有问题: 定义boundary的时候:multipart/form-data; boundary= 这里的string可以为一个长一些的random字符串。 在request-data中使用的时候,还要在这个字符串前再加两个"-"字符。最后一个boundary还要在最后额外加两个"-":

(setq image-data
      (with-temp-buffer
        (insert-file-contents-literally "~/Desktop/a.jpg")
        (buffer-substring-no-properties (point-min)
                                        (point-max))))

(let ((url-request-method "POST")
      (url-request-extra-headers
       `(("Content-Type" .
          "multipart/form-data; boundary=--WebKitFormBoundaryE19zNvXGzXaLvS5C")
         ("Authorization" .
          ,(format "OAuth2 %s" (url-hexify-string (weibo-get-token))))))
      (url-request-data
       (concat "----WebKitFormBoundaryE19zNvXGzXaLvS5C\r\nContent-Disposition: form-data; name=\"status\"\r\n\r\n"
               "This is a test\r\n"
               "----WebKitFormBoundaryE19zNvXGzXaLvS5C\r\n"
               "Content-Disposition: form-data; name=\"pic\"; filename=\"a.jpg\"\r\n"
               "Content-Type: image/jpeg\r\n\r\n"
               image-data
               "\r\n"
               "----WebKitFormBoundaryE19zNvXGzXaLvS5C--")))
  (url-retrieve-synchronously "https://api.weibo.com/2/statuses/upload.json"))