使用 Flickr API 在 Markdown-mode 中插入圖片

拿 Markdown 寫文章很方便,只需要把心力放在「你要寫什麼」,不用浪費時間跟 wysiwyg 編輯器和排版瞎耗(而且連 pastebin 都省了)。

為了更方便地在 Emacs 裡寫 Markdown,先前有用過 url 來自動插入網頁連結的 <title> 。但寫 blog 一直有個讓我很感頭痛的事情,就是貼照片。不知為何不管是 Flickr 還是 Picasaweb,對於「取得照片的 raw link」這件事都做得非常不方便,而且常常界面一改版,連 raw link 在哪都找不到。

Flickr API

要在 Emacs 上使用 HTTP GET,請看這一篇

後來發現可以利用 Flickr 提供的 API 來自動抓取圖片的 raw link,且出乎意料地相當簡單,申請一個 API key 後,只要用單純的 GET 就可以拿到你要的東西。

我想稍微看看應該就知道怎麼用了,總之來個範例最快,把這行貼到瀏覽器裡試試:

1
https://www.flickr.com/services/rest/?method=flickr.photos.getSizes&photo_id=11638695064&api_key=你的 API key

已知問題

Private photo 是無法只靠這樣就抓到 raw link 的,好像是需要 OAuth。(不過一般也不會貼 private photo 吧?好像沒有太大必要做這個(耍懶中))總之以後再看看要怎麼做。

應用:結合 Emacs 的 url.el

拿 Flickr 搭配 Emacs 和它內建的 url.el,只需要按 C-c i f 後,貼上:

1
http://www.flickr.com/photos/41522078@N05/11529799266/

就會跳出一個 minibuffer,問你要多大尺寸的 raw link (可以用 tab completion):

選好後,如果目前 major-mode 是 html-mode(或者強迫使用M-x flickr-insert-html)就會吐給你:

1
<a href="http://www.flickr.com/photos/41522078@N05/11529799266/"><img src="https://farm8.staticflickr.com/7420/11529799266_4e391575b0_z.jpg" alt="" class=""></img></a>

Org-mode 則會吐出:

1
[[https://farm8.staticflickr.com/7420/11529799266_4e391575b0_z.jpg]]

Markdown-mode 則會吐出:

1
![](https://farm8.staticflickr.com/7420/11529799266_4e391575b0_z.jpg)

我們在連續貼圖時,通常不會一直改圖片大小,而是希望所有圖片尺寸同個尺寸。這時你可以設定變數 flickr-default-size的值,例如 ,就不會每次都問你要什麼尺寸。當你要的尺寸不存在時,才會再主動問你要什麼尺寸。

  • 可以使用M-x flickr-set-default-size 來互動式地更改 flickr-default-size的值。改成 nil 代表沒有指定。

安裝

  1. 首先 申請一個 Flickr 的 API Key ,選擇非商業使用(Non-Commercial)。
  2. 以下為完整原始碼。務必記得設定你的 API key(setq flickr-api-key "YOUR_API_KEY")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
;; Flickr
(require 'xml)
(require 'url)

(defvar flickr-api-key nil
"This variable is used by `flickr-insert-raw-link-with-html-tag'.
You can get a Flickr API:
http://www.flickr.com/services/apps/create/apply/
Then (setq flickr-api-key \"YOUR_API_KEY\")")

(defvar flickr-default-size nil
"This variable is used by `flickr-insert-raw-link-with-html-tag'.

For example, you can set like this (setq flickr-default-size \"medium640\")
If the copy of \"medium640\" of the photo exists, apply it directly;
if not exist, it will ask for your size choice.

When nil, it ask you for raw image size everytime.
")

(defun flickr-set-default-size ()
"Set variable `flickr-default-size' interactively."
(interactive)
(setq flickr-default-size
(completing-read "Input size: "
'(nil large large1600 large2048 largesquare medium medium640 medium800 original small small320 square thumbnail) nil nil nil)))

(defun flickr-insert-html ()
"Insert the raw link of a Flickr page ,with HTML tags attached.
For example, enter:

http://www.flickr.com/photos/41522078@N05/11529752404/

And select size you like (tab completion is available), it will insert:

<a href=\"http://www.flickr.com/photos/41522078@N05/11529799266/\"><img src=\"https://farm8.staticflickr.com/7420/11529799266_4e391575b0_z.jpg\" alt=\"\" class=\"\"></img></a>

With one C-u prefix, ignore `flickr-default-size' and always ask for size.

Variable `flickr-api-key' is required. Please get one first:
http://www.flickr.com/services/apps/create/apply/
And (setq flickr-api-key \"YOUR_API_KEY\")"
(interactive)
(let* ((major-mode 'html-mode))
(flickr-insert-auto-format)
))

(defun flickr-insert-auto-format ()
"Insert the raw link of a Flickr page, formatted according to current mode.
Input like: http://www.flickr.com/photos/41522078@N05/11529752404/
For example:
- HTML: <a href=\"http://www.flickr.com/photos/41522078@N05/11529752404/\"><img src=\"https://farm8.staticflickr.com/7420/11529799266_4e391575b0_z.jpg\" alt=\"\" class=\"\"></img></a>
- Markdown: ![](https://farm8.staticflickr.com/7420/11529799266_4e391575b0_z.jpg)
- Org: [[https://farm8.staticflickr.com/7420/11529799266_4e391575b0_z.jpg]]"
(interactive)
(let* ((raw (flickr-get-raw-link-interactively))
(major-mode (if (member major-mode '(markdown-mode org-mode html-mode))
major-mode
(intern (concat (completing-read "Select a format: " '("org" "markdown" "html") nil t "" ) "-mode")))))
(save-excursion
(cond ((eq major-mode 'markdown-mode)
(insert (format "![](%s)" (cdr raw))))
((eq major-mode 'org-mode)
(insert (format "- [[%s]]" (cdr raw))))
((eq major-mode 'html-mode)
(insert (format "<a href=\"%s\"><img src=\"%s\" alt=\"\" class=\"\"></img></a>" (car raw) (cdr raw))))))
(when (eq (current-column) 0)
(end-of-line)
(newline))
))

(defun flickr-process-whole-buffer ()
(interactive)

)

(defun flickr-get-raw-link-interactively (&optional input)
"This is NOT an interactive function. Instead, it will automatically
decide if interactive interface needed.
Output is a list like (INPUT . RAWLINK)."
(if (null flickr-api-key)
(message "You have to set Flickr API key first. C-h v flickr-api-key for more details.")
(let* ((input (if input
input
(read-from-minibuffer "Flickr url: ")))
(size-list (flickr-parse-xml (flickr-retrieve-xml input)))
(specify-size flickr-default-size))
(when (or (equal current-prefix-arg '(4))
(null specify-size)
(not (assq specify-size size-list)))
(setq specify-size (intern (completing-read "Select size: " size-list nil t nil))))
(cons input (cdr (assq specify-size size-list))))))


(defun flickr-parse-xml (flickr-xml)
"Input should be a sizes list parsed from XML. Like this:
((size ((label . \"Square\") (width . \"75\") (height . \"75\")
(source . \"https://farm8.staticflickr.com/7440/13963740818_37ffef157b_s.jpg\")
(url . \"https://www.flickr.com/photos/41522078@N05/13963740818/sizes/sq/\")
(media . \"photo\")))...)
And output is a pairs list for sizes and raw-link:
((original . \"https://farm8.staticflickr.com/7440/13963740818_17c4821702_o.png\")
(large2048 . \"https://farm8.staticflickr.com/7440/13963740818_16f04a43ef_k.jpg\")...)"
(let* ((sizes (car flickr-xml))
(attrs (xml-node-attributes sizes))
(size (xml-get-children sizes 'size))
fin)
(mapcar (lambda (x)
(push (cons (intern (replace-regexp-in-string " " "" (downcase (cdr (assq 'label (cadr x))))))
(cdr (assq 'source (cadr x)))) fin))
size)
fin))

(defun flickr-retrieve-xml (flickr-url)
"Input should be a flickr url, output is a raw XML string retrieve with Flickr API"
(interactive)
(string-match "https?://www.flickr.com/photos/[0-9A-z@]*/\\([0-9]+\\)/" flickr-url)
(switch-to-buffer
(url-retrieve-synchronously
(format "https://www.flickr.com/services/rest/?method=flickr.photos.getSizes&photo_id=%s&api_key=%s" (match-string 1 flickr-url) flickr-api-key)))
(goto-char (point-min))
(re-search-forward "<sizes" nil :no-error)(left-char 6)
(setq fin (xml-parse-region (point) (point-max)))
(kill-buffer)
fin)

(define-key markdown-mode-map (kbd "C-c i f") 'flickr-insert-html)
(define-key org-mode-map (kbd "C-c i f") 'flickr-insert-auto-format)
(define-key html-mode-map (kbd "C-c i f") 'flickr-insert-auto-format)

(provide 'flickr)

實作細節

以下為純筆記。

關聯列表(Assoc-lists)

  • 把取得的可用尺寸名稱(e.g. “Large 1024”)抓出來,並把空格都用 replace-regexp-in-string 去掉。
  • downcase 把所有字母改成小寫(方便輸入也避免混淆)。
  • 使用 list 把 size 和對應的 raw links 組成清單:

    1
    2
    (list 'a 'apple)
    ;; (a apple)
  • 再用push 把一對對的 lists 組成assoc-lists

1
2
3
4
5
(setq fruit '())        ;'()表示空表,就是 nil
(push '(a apple) fruit)
;;((a apple))
(push '(b banana) fruit)
;;((b banana) (a apple))
  • 抽出 assoc-lists 的範例如下:
1
2
3
4
5
(assoc 'a '((a apple) (b banana) (c cat)))
;; (a apple)

(cadr (assoc 'a '((a apple) (b banana) (c cat)))) ;(cadr ...) 意義等同 (car (cdr ...))
;; apple

Tab Completion

範例如下:

1
2
3
4
(completing-read
"Complete a foo: "
'(("foobar1" 1) ("barfoo" 2) ("foobaz" 3) ("foobar2" 4))
nil t "fo")

不過這個當然還是沒有 Logdown 或 Github 的編輯器那樣方便,只要把圖片拖上去即可自動上傳,又可以用 markdown,那設計真的太帥了。不過經過這篇文章的設定,一樣可以開心的使用 markdown 貼 Flickr 圖片。

說到網路相簿,其實目前我覺得界面做得最簡單明瞭又好用的是 imgur.com,直接一鍵拷貝圖片的 raw link,但 imgur 上的圖只要半年沒人看就會砍掉…

參考資料

筆記


- [2014-01-07 火 20:58] 開始做。5 小時 55 分鐘
- [2014-01-08 水 03:55] 開始寫這篇。
- [2014-01-08 水 17:32] 寫完惹 deploy 啊 orz。 加上 C-u prefix 和 default-size。都在不務正業…我好懷念九份金瓜石…我好想漫步在小徑上 Q_Q 6 小時 14 分鐘
- 合計 12 小時 09 分鐘