Using org2blog to publish Org-mode subtrees

| emacs, geek

This patch modifies punchagan's org2blog to allow you to publish an Org subtree with M-x org2blog-post-subtree. It posts a draft by default, and publishes the post if you call it with C-u M-x org2blog-post-subtree. It gets the posting date from SCHEDULED, DEADLINE, active or inactive timestamps, or the Post Date property, and lets you use tags as categories or use a separate Categories property. It inherits tags from parent headings, too. It picks up the title from the subtree heading or uses the Title property.

Patch

diff --git a/org2blog.el b/org2blog.el
index dc88291..b95caba 100644
--- a/org2blog.el
+++ b/org2blog.el
@@ -77,6 +77,11 @@
   :group 'org2blog 
   :type 'string)
 
+(defcustom org2blog-use-tags-as-categories nil
+  "Non-nil means assign :tags: to WordPress categories instead."
+  :group 'org2blog
+  :type 'boolean)
+
 (defvar org2blog-categories-list nil 
   "List of weblog categories")
 
@@ -433,4 +438,143 @@
        (goto-char current-pos)
        (command-execute (lookup-key org-mode-map (kbd "C-c t")))))))
 
+(defun org2blog-create-categories (categories)
+  "Create unknown CATEGORIES."
+  (mapcar
+   (lambda (cat)
+     (if (and (not (member cat org2blog-categories-list))
+              (y-or-n-p (format "Create %s category? " cat)))
+         (wp-new-category org2blog-server-xmlrpc-url
+                          org2blog-server-userid
+                          (org2blog-password)
+                          org2blog-server-blogid
+                          cat)))
+   categories))
+
+(defun org2blog-password ()
+  "Get password or prompt if needed."
+  (or org2blog-server-pass
+      (setq org2blog-server-pass (read-passwd "Weblog password? "))))
+
+(defun org2blog-upload-images-insert-links (&optional beg end)
+  "Upload images and replace with links in the region specified by BEG to END."
+  (interactive "r")
+  (let ((re 
+        (concat "\\[\\[\\(.*\\)" 
+                (substring (org-image-file-name-regexp) 0 -2)
+                "\\]\\]"))
+       file-all-urls file-name file-web-url blog-pass)
+    (save-excursion
+      (save-restriction
+        (narrow-to-region (or beg (point-min))
+                          (or end (point-max)))
+        (goto-char (point-min))
+        (while (re-search-forward re nil t 1)
+          (setq file-name (concat 
+                           (match-string-no-properties 1)
+                           "."
+                           (match-string-no-properties 2)))
+          (unless (save-match-data (string-match org-link-types-re file-name))
+            (save-match-data 
+              (if (assoc file-name file-all-urls)
+                  (setq file-web-url (cdr (assoc file-name file-all-urls)))
+                (setq file-web-url
+                      (cdr (assoc "url" 
+                                  (metaweblog-upload-image org2blog-server-xmlrpc-url
+                                                           org2blog-server-userid
+                                                           (org2blog-password)
+                                                           org2blog-server-weblog-id
+                                                           (get-image-properties file-name))))
+                      file-all-urls (append file-all-urls (list (cons 
+                                                                 file-name file-web-url))))))
+            (replace-match (concat "[[" file-web-url "]]") t t nil 0)))))
+    file-all-urls))
+
+(defun org2blog-post-subtree (&optional publish)
+  "Post the current entry as a draft. Publish if PUBLISH is non-nil."
+  (interactive "P")
+  (let ((post (org2blog-parse-subtree))
+        post-id)
+    (org2blog-create-categories (cdr (assoc "categories" post)))
+    (setq post-id (cdr (assoc "post-id" post)))
+    (save-excursion 
+      (org2blog-upload-images-insert-links (org-back-to-heading) (org-end-of-subtree)))
+    (if post-id
+        (metaweblog-edit-post org2blog-server-xmlrpc-url
+                             org2blog-server-userid
+                              (org2blog-password)
+                             post-id
+                              post
+                             publish)
+      (setq post-id
+            (metaweblog-new-post
+             org2blog-server-xmlrpc-url
+             org2blog-server-userid
+             (org2blog-password)
+             org2blog-server-blogid
+             post
+             publish))
+      (org-entry-put (point) "Post ID" post-id)
+      (message (if publish
+                   "Published (%s): %s"
+                 "Draft (%s): %s")
+               post-id
+               (cdr (assoc "title" post))))))
+
+(defun org2blog-parse-subtree ()
+  "Parse the current subtree as a blog entry."
+  (let (html-text
+        (post-title (or (org-entry-get (point) "Title")
+                        (org-get-heading t)))
+        (post-id (org-entry-get (point) "Post ID"))
+        ;; Set post-date to the Post Date property or look for timestamp
+        (post-date (or (org-entry-get (point) "Post Date")
+                       (org-entry-get (point) "SCHEDULED")
+                       (org-entry-get (point) "DEADLINE")                       
+                       (org-entry-get (point) "TIMESTAMP_IA")
+                       (org-entry-get (point) "TIMESTAMP")))
+        (tags (org-get-tags-at (point) nil))
+        (categories (org-split-string (or (org-entry-get (point) "CATEGORIES") "") ":")))
+    ;; Convert post date to ISO timestamp
+    (setq post-date
+          (format-time-string "%Y%m%dT%T"
+                              (if post-date
+                                  (apply 'encode-time (org-parse-time-string post-date))
+                                (current-time))
+                              t))
+    (if org2blog-use-tags-as-categories
+        (setq categories tags
+              tags nil))
+    (save-excursion
+      (setq html-text
+            (org-export-region-as-html
+             (and (org-back-to-heading) (line-end-position))
+             (org-end-of-subtree)
+             t 'string))
+      (setq html-text
+            (with-temp-buffer
+              (insert html-text)
+              (goto-char (point-min))
+              ;; Fix newlines
+             (let (start-pos end-pos)
+                (setq start-pos (point-min))
+               (goto-char start-pos)
+                (while (re-search-forward "<\\(pre\\|blockquote\\).*?>" nil t 1)
+                  (setq end-pos (match-beginning 0))
+                  (replace-string "\n" " " nil start-pos end-pos)
+                  (re-search-forward (concat "</" (match-string-no-properties 1) ">") nil t 1)
+                  (setq start-pos (match-end 0))
+                  (goto-char start-pos))
+               (setq end-pos (point-max))
+               (replace-string "\n" " " nil start-pos end-pos))
+              ;; Copy the text
+              (buffer-substring-no-properties (point-min) (point-max)))))
+    (list
+     (cons "date" post-date)
+     (cons "title" post-title)
+     (cons "tags" tags)
+     (cons "categories" categories)
+     (cons "post-id" post-id)
+     (cons "description" html-text))))
+
 (provide 'org2blog)

I like using one big Org file for all of my notes so that I can search and categorize things easily.

Here is the sample code from my ~/.emacs:

(add-to-list 'load-path "~/elisp/org2blog")
(require 'org2blog)
(setq org2blog-server-url "http://sachachua.com/blog/xmlrpc.php"
      org2blog-server-user "admin"
      org2blog-server-weblog-id ""
      org2blog-use-tags-as-categories t)
(org2blog-login)

Then I can go to the entry and call M-x org2blog-post-subtree to post a draft or C-u M-x org2blog-post-subtree to publish it.

Note that the code uses whatever heading level you're on, so if you're under a sub-heading of the post you want to publish, use C-c C-u outline-up-heading to go up headings until you're at the right level.

You can get the modified source code from http://github.com/sachac/org2blog . I've also sent a pull request upstream.

You can view 11 comments or e-mail me at sacha@sachachua.com.

11 comments

Luiz Gavioli

2010-07-20T19:48:07Z

I've tried to use org2blog to post an article to my blog but I'm behind a proxy! Is there a way to use org2blog through proxies?

org2blog uses xml-rpc, which uses url, so you should be able to customize url-proxy-services. Hope that helps!

punchagan accepted the patch upstream, and someone else contributed bugfixes. Pull from the main repository instead! =)

@Luiz
I am behind a proxy too! The whole development of org2blog was done behind a proxy. :-)

@Sacha
Thanks for the patch.

I don't know if I've got the times right. When I publish my subtree, the posts seem to have the right time in Wordpress, but they stay Scheduled instead of Published. Editing the post and fiddling with the time does the right thing, though. Hmm...

It's a problem with the timezone of your blog and the machine you post from.

I use the format-time-string call without passing the UNIVERSAL argument and it works fine.
When the UNIVERSAL argument is set, my posts are shown to be edited 6 hours ago, which is the time I'm ahead of UTC.

Hope that helps.

Luiz Gavioli

2010-07-21T19:34:37Z

Thank you for the help! url-proxy-services really worked!

flaviosouza

2010-07-31T21:37:02Z

sacha / punchagan

I have the same problem with time, how do I fix it with format-time-string ?

help me....

flaviosouza: Try updating, the latest version should be okay. Or remove the t argument from format-time-string, I think?

flaviosouza

2010-08-01T03:05:02Z

Thanks,

I found out format-time-string in the org2blog.el. It is working now.

I did a quick post in my blog. This is the google translated version:

http://translate.google.com...

Thanks! this looks great, I'll be sure to try it over the weekend!