Edit or delete old posts.
authorSteve Youngs <steve@sxemacs.org>
Thu, 30 Jun 2011 04:25:29 +0000 (14:25 +1000)
committerSteve Youngs <steve@sxemacs.org>
Thu, 30 Jun 2011 04:25:29 +0000 (14:25 +1000)
This patchset adds the ability to edit (including delete) Livejournal
entries that have already been posted to Livejournal.

There are three caveats to this:

  1) For it to work, `lj-archive-posts' must be t (now the default).

  2) You can't edit/delete a post that wasn't originally posted from
     SXEmacs/LJ (that has the editing/deletion features)

  3) Post date/time cannot be altered.  You either get it right when
     you post the entry the first time, or you edit the date from the
     Livejournal site.

Yes, I know that is a little sucky, but hopefully that won't be the
case for long.  I am working on ways to eliminate at least the 2nd
caveat.

* lj.el (lj-post-proc-parser): Save the itemid as well.

* lj.el (lj-item-id): New.  Stores the itemid from the just posted
journal entry.

* lj.el (lj-archive-post): Put the itemid and permalink at the top
of the achived post.

* lj.el (lj-stringify-id): New.  Turns an itemid into a string.

* lj.el (lj-delete-post-internal): New.

* lj.el (lj-delete-post-proc-parser): New.

* lj.el (lj-header-regexp): Add X-LJ-URL, X-LJ-ItemID

* lj.el (lj-header-itemid): New face.

* lj.el (lj-header-url): New face.

* lj.el (lj-font-lock-keywords): Add X-LJ-URL and X-LJ-ItemID
headers.

* lj.el (lj-goto-x-lj-url): New.

* lj.el (lj-goto-x-lj-itemid): New.

* lj.el (lj-edit-old-post): New.

* lj.el (lj-construct-url): Update to allow for editing old posts.

* lj.el (lj-archive-post): Handle editing old posts.

* lj.el (lj-generate-new-buffer): Lose some whitespace being
injected into the created buffer.

* lj.el (lj-delete-old-post): New.

* lj.el (lj-archive-posts): Change default to t.

* lj.el (lj-version): Bump.

Signed-off-by: Steve Youngs <steve@sxemacs.org>
lj.el

diff --git a/lj.el b/lj.el
index 7395007..6ab5286 100644 (file)
--- a/lj.el
+++ b/lj.el
 ;;    There's nothing hard or overly complicated here.  Take a look at
 ;;    describe-mode (`C-h m') which will show you the keybindings
 ;;    available.  All of the "lj-mode specific" interactive commands
-;;    have a binding.  There are 3 "global" commands that don't...
+;;    have a binding.  There are 5 "global" commands that don't...
 ;;
 ;;         #'lj
 ;;         #'lj-blog-buffer
 ;;         #'lj-blog-region
+;;         #'lj-edit-old-post
+;;         #'lj-delete-old-post
 ;;
 ;;    The only reason they don't have keybindings is that I think it'd
 ;;    be bad form on my part to set global keys for you.  Assign them
 ;;
 
 ;;; Version:
-(defconst lj-version 1.29
+(defconst lj-version 1.30
   "Version number of SXEmacs/LJ.")
 
 ;;; Code:
@@ -327,7 +329,7 @@ This function should return a formatted string, or nil."
   :type 'function
   :group 'lj)
 
-(defcustom lj-archive-posts nil
+(defcustom lj-archive-posts t
   "*Keep an archive copy of LJ posts when non-nil."
   :type 'boolean
   :group 'lj)
@@ -673,6 +675,12 @@ Returns a list suitable for passing to `encode-time' or `encode-btime'."
 (copy-face 'bold 'lj-separator)
 (set-face-foreground 'lj-separator "red")
 
+(make-face 'lj-header-itemid "Face used for LJ ItemID header content.")
+(set-face-parent 'lj-header-itemid 'lj-separator)
+
+(make-face 'lj-header-url "Face used for LJ URL header content.")
+(set-face-parent 'lj-header-url 'lj-separator)
+
 ;; compatibility hoohar
 (unless (featurep 'sxemacs)
   (fset #'defregexp #'defvar))
@@ -684,7 +692,8 @@ Returns a list suitable for passing to `encode-time' or `encode-btime'."
 
 (defregexp lj-header-regexp
   (let ((headers '("Subject" "FCC" "BCC" "Security" "Community"
-                  "Location" "Mood" "Music" "Userpic" "Tags")))
+                  "Location" "Mood" "Music" "Userpic" "Tags"
+                  "X-LJ-URL" "X-LJ-ItemID")))
     (concat (regexp-opt headers t) ":"))
   "Regular expression matching LJ headers.")
 
@@ -701,6 +710,8 @@ Returns a list suitable for passing to `encode-time' or `encode-btime'."
      ("^Location: \\(.*$\\)" 1 lj-header-location)
      ("^Userpic: \\(.*$\\)" 1 lj-header-userpic)
      ("^Tags: \\(.*$\\)" 1 lj-header-tags)
+     ("^X-LJ-URL: \\(.*$\\)" 1 lj-header-url)
+     ("^X-LJ-ItemID: \\(.*$\\)" 1 lj-header-itemid)
      (,(regexp-quote lj-header-separator) 0 lj-separator))
    hm--html-font-lock-keywords
    html-font-lock-keywords)
@@ -1365,7 +1376,7 @@ Argument BUF is the process buffer used."
 (defun lj-construct-url (subject body user
                                &optional security tags community
                                auto-format no-comments mood location
-                               music pickw date backdated)
+                               music pickw date backdated itemid)
   "Construct a URL to use for posting to LiveJournal.
 
 Argument SUBJECT, a string, which is the title of the post.
@@ -1422,8 +1433,9 @@ set which will prevent the post from showing up on friends pages."
         (ltime (or lj-last-entry-btime (and (lj-get-last-entry-btime)
                                             lj-last-entry-btime)))
         url)
-    ;; save custom date in case something goes wrong
-    (if date
+    ;; save custom date in case something goes wrong (not for edits)
+    (if (and date 
+            (not (zerop (length itemid))))
        (setq lj-last-user-set-time date)
       (setq lj-last-user-set-time nil))
     (setq subject (lj-hexify-string subject t))
@@ -1468,13 +1480,16 @@ set which will prevent the post from showing up on friends pages."
     (if (> (length location) 0)
        (setq location (lj-hexify-string location t))
       (setq location nil))
-    ;; maybe force opt_backdated
-    (when (> ltime ctime)
+    ;; maybe force opt_backdated (not touching for edits)
+    (when (and (> ltime ctime)
+              (not (zerop (length itemid))))
       (setq backdated t))
     ;; the final url
     (setq url (concat
               lj-base-url
-              "?mode=postevent"
+              (format "?mode=%sevent" (if (zerop (length itemid))
+                                          "post"
+                                        "edit"))
               "&user=" user
               "&auth_method=cookie"
               (format "&ver=%d" (if (lj-utf-emacs-p) 1 0))
@@ -1486,11 +1501,10 @@ set which will prevent the post from showing up on friends pages."
                 (format "&prop_taglist=%s" tags))
               (when community
                 (format "&usejournal=%s" community))
-              "&year=" year
-              "&mon=" month
-              "&day=" day
-              "&hour=" hour
-              "&min=" minute
+              (when (zerop (length itemid)) ; leave date alone when editing
+                (format
+                 "&year=%s&mon=%s&day=%s&hour=%s&min=%s"
+                 year month day hour minute))
               (when mood
                 (if (integerp mood)
                     (format "&prop_current_moodid=%d" mood)
@@ -1507,6 +1521,8 @@ set which will prevent the post from showing up on friends pages."
               "&prop_opt_preformatted=" (if auto-format "0" "1")
               "&prop_opt_nocomments=" (if no-comments "1" "0")
               "&prop_useragent=" (lj-hexify-string lj-useragent)
+              (unless (zerop (length itemid))
+                (format "&itemid=%s" itemid))
               "&event=" body))
     url))
 
@@ -1768,7 +1784,7 @@ The filenames are of the format... `ljp-YYYYMMDDHHMM'."
   (rename-buffer "*LJ-Post*" 'unique)
   (when (lj-utf-emacs-p)
     (set-buffer-file-coding-system 'utf-8))
-  (insert " \n")
+  (insert "\n")
   (make-extent (point-min) (point-at-eol))
   (insert "Subject: \n")
   (when lj-archive-posts
@@ -1924,6 +1940,16 @@ The header is created if it doesn't exist unless NOCREATE is non-nil."
        (insert "Tags: \n")
        (backward-char 1))))
 
+(defun lj-goto-x-lj-itemid (&optional nocreate)
+  "Move to the X-LJ-ItemID header."
+  (goto-char (point-min))
+  (re-search-forward "^X-LJ-ItemID: " nil 'missing))
+
+(defun lj-goto-x-lj-url (&optional nocreate)
+  "Move to the X-LJ-URL header."
+  (goto-char (point-min))
+  (re-search-forward "^X-LJ-URL: " nil 'missing))
+
 (defun lj-goto-body ()
   "Move to the body of an LJ post buffer."
   (interactive)
@@ -2452,26 +2478,35 @@ it should give you a rough idea."
 (defvar lj-last-url "No URL yet, got nothing to blog about?"
   "The URL to your last posted blog entry on LiveJournal.")
 
+(defvar lj-item-id ""
+  "The itemid of the last post.")
+
 (defun lj-post-proc-parser (buf)
   "Process parser for `lj-post'.
 Argument BUF is the process buffer used."
-  (let ((url "^url\n\\(.*$\\)"))
+  (let ((url "^url\n\\(.*$\\)")
+       (itemid "^itemid\n\\(.*$\\)"))
     (with-current-buffer buf
       (when (lj-proc-success)
        (setq lj-last-user-set-time nil)
        (goto-char (point-min))
-       (if (re-search-forward url nil t)
-           (setq lj-last-url (match-string 1))
-         (setq lj-last-url "NO URL RETURNED FROM LiveJournal"))
+       (save-excursion
+         (if (re-search-forward url nil t)
+             (setq lj-last-url (match-string 1))
+           (setq lj-last-url "NO URL RETURNED FROM LiveJournal")))
+       (save-excursion
+         (and (re-search-forward itemid nil t)
+              (setq lj-item-id (match-string 1))))
        (kill-buffer nil)))))
 
 (defun lj-archive-post (archive)
   "Archive the current post to ARCHIVE."
   (let ((buf (current-buffer)))
     (with-current-buffer (find-file-noselect archive)
-      (insert-buffer buf)
-      (goto-char (point-max))
-      (insert (format "\n\n<a href\"%s\">View Online</a>\n" lj-last-url))
+      (when (zerop (length (lj-header-content "x-lj-itemid")))
+       (insert (format "\nX-LJ-URL: %s\n" lj-last-url)
+               (format "X-LJ-ItemID: %s" lj-item-id))
+       (insert-buffer buf))
       (save-buffer)
       (kill-buffer nil))))
 
@@ -2542,6 +2577,36 @@ Argument BUF is the process buffer used."
                     "&howmany=1")))
     (lj-http-post url cookies #'lj-last-entry-proc-parser)))
 
+(defun lj-delete-post-proc-parser (buf)
+  "Process the output from `lj-delete-post'.
+
+Argument BUF is the process buffer that was used."
+  (with-current-buffer buf
+    (when (lj-proc-success)
+      (message "Your post has been successfully removed from LiveJournal.")
+      (kill-buffer nil))))
+
+(defun lj-stringify-id (id)
+  "Returns a string version of the number, ID."
+  (if (stringp id)
+      id
+    (and (numberp id)
+        (number-to-string id))))
+
+(defun lj-delete-post-internal (itemid)
+  "Delete the post with ITEMID."
+  (let ((cookies (or lj-cookies
+                    (error "No LJ cookies found")))
+       (url (concat lj-base-url
+                    "?mode=editevent"
+                    "&user=" lj-user-id
+                    "&auth_method=cookie"
+                    (format "&ver=%d" (if (lj-utf-emacs-p) 1 0))
+                    "&itemid=" (lj-stringify-id itemid)
+                    "&prop_useragent=" (lj-hexify-string lj-useragent)
+                    "&event=")))
+    (lj-http-post url cookies #'lj-delete-post-proc-parser)))
+
 (defun lj-set-date/time ()
   "Return an internal time value to use as post date/time.
 
@@ -2667,6 +2732,7 @@ With two prefix args, also set a \"date out of order\" flag."
        (location (lj-header-content "location"))
        (music (lj-header-content "music"))
        (pickw (lj-header-content "userpic"))
+       (itemid (lj-header-content "x-lj-itemid"))
        (cookies (or lj-cookies
                     (error "No LJ cookies found")))
        (backdated nil)
@@ -2688,7 +2754,8 @@ With two prefix args, also set a \"date out of order\" flag."
                     (length music)
                     (length location))
     (setq url (lj-construct-url subject body user security tags comm nil nil
-                               mood location music pickw date backdated))
+                               mood location music pickw date backdated
+                               itemid))
     ;; lets save the draft out to disc just in case something goes wrong
     (save-buffer)
     (lj-http-post url cookies #'lj-post-proc-parser)
@@ -2700,7 +2767,10 @@ With two prefix args, also set a \"date out of order\" flag."
 ;;    (and lj-twitter-flag
 ;;      (lj-twitter-update-status lj-twitter-username lj-twitter-password
 ;;                                subject lj-last-url))
-    (delete-file draftid)
+    ;; If there is a itemid don't delete the draft because it is our
+    ;; archive copy
+    (when (zerop (length itemid))
+      (delete-file draftid))
     (run-hooks 'lj-after-post-hook)))
 
 ;; keep track of the date of the last entry for backdating purposes
@@ -2916,6 +2986,59 @@ to HTML."
     (lj-goto-body)
     (lj-mode)))
 
+(defun lj-edit-old-post (post)
+  "Edit an already posted LJ entry."
+  (interactive (list
+               (read-file-name "Edit Post: "
+                               lj-archive-directory "" t)))
+  (if (or (zerop (length post))
+         (not (file-readable-p (expand-file-name post))))
+      (error 'invalid-argument post)
+    (switch-to-buffer (find-file-noselect (expand-file-name post)))
+    (rename-buffer "*LJ-EDIT*" 'unique)
+    (and (zerop (length (lj-header-content "x-lj-itemid")))
+        (error "ItemID missing, CANNOT edit this post from SXEmacs/LJ"))
+    (goto-char (point-min))
+    (make-extent (point) (point-at-eol))
+    (lj-update-userpic-glyph
+     (expand-file-name (lj-header-content "userpic")
+                      lj-userpic-directory))
+    (re-search-forward lj-header-separator nil t)
+    (forward-line -1)
+    (set-extent-property
+     (make-extent (point-at-bol) (1+ (point-at-eol))) 'invisible t)
+    (lj-goto-body)
+    (lj-mode)))
+
+(defun lj-delete-old-post (post)
+  "Delete a post from Livejournal."
+  (interactive (list
+               (read-file-name "Delete Old Post: "
+                               lj-archive-directory "" t)))
+  (if (or (zerop (length post))
+         (not (file-readable-p (expand-file-name post))))
+      (error 'invalid-argument post)
+    (switch-to-buffer (find-file-noselect (expand-file-name post)))
+    (rename-buffer "*LJ-EDIT*" 'unique)
+    (let ((itemid (lj-header-content "x-lj-itemid")))
+      (and (zerop (length itemid))
+          (error "ItemID missing, CANNOT delete this post from SXEmacs/LJ"))
+      (goto-char (point-min))
+      (make-extent (point) (point-at-eol))
+      (lj-update-userpic-glyph
+       (expand-file-name (lj-header-content "userpic")
+                        lj-userpic-directory))
+      (re-search-forward lj-header-separator nil t)
+      (forward-line -1)
+      (set-extent-property
+       (make-extent (point-at-bol) (1+ (point-at-eol))) 'invisible t)
+      (lj-goto-body)
+      (lj-mode)
+      (and (y-or-n-p "Are you sure you want to delete this post? ")
+          (progn
+            (delete-file post)
+            (lj-delete-post-internal itemid))))))
+
 (provide 'lj)
 
 ;; On-load actions