Download
(defgroup sms nil
"Major mode for composing and sending SMS text messages."
:prefix "sms-"
:group 'applications)
(defcustom sms-mode-hook nil
"Hook to be run when `sms-mode' is started."
:type '(repeat symbol)
:group 'sms)
(defcustom sms-max-length 160
"Maximum length of text message."
:type 'integer
:group 'sms)
(put 'sms-mode 'font-lock-defaults '(message-font-lock-keywords t))
(defcustom sms-phone-number-separators " \t-"
"The phone number separators that may appear in a phone number.
A phone number such as 12345678, will be clear if written as:
123 456 78, or 123-456-78."
:type 'regexp
:group 'sms)
(defcustom sms-send-text-message 'sms-send-text-message-android
"The function used to send the contents of the buffer as a text message.
This should be a function that takes two arguments.
The first argument should be a string containing a phone number,
and the second argument a string containing the text message to be sent."
:type 'symbol
:group 'sms)
(defun sms-create-buffer (recipients)
"Create a new buffer in sms-mode for writing text messages.
The list of recipients will be obtained from the recipients argument,
and displayed at the top of the buffer.
The newly created buffer will be named `*SMS*', or if there is already a buffer with that name
then a suffix of the form `<N>' will be added.
The newly created buffer will be returned by this function."
(let ((smsbuffer (generate-new-buffer "*SMS*"))
toheader
(breakline "--text follows this line--\n"))
(with-current-buffer smsbuffer
(setq toheader (concat "To: " recipients "\n"))
(goto-char (point-min))
(insert (concat toheader breakline))
(sms-mode))
smsbuffer))
(defun sms-legal-phone-numberp (num)
"Return t if NUM is a legal phone number.
A legal phone number have an optional leading +, followed by some numbers"
(string-match "^+?[0-9]+$" num))
(defun sms-get-TO-header-list (&optional buffer)
"Get the content in *SMS* buffer BUFFER's To: header list as a string."
(or buffer (setq buffer (current-buffer)))
(when (sms-bufferp buffer)
(with-current-buffer buffer
(mail-fetch-field "to"))))
(defun sms-extract-recipients-list ()
"Extract the recipients from current *SMS* buffer's header.
Return a list of strings.
This function must be called from within a *SMS* buffer"
(unless (sms-bufferp (current-buffer))
(error "sms-extract-recipients-list not called in a *SMS* buffer"))
(let* ((recipients (split-string (sms-get-TO-header-list) ","))
(phone-nums nil))
(dolist (recipient recipients)
(let ((recipient-num-list nil))
(if (string-match "<" recipient)
(setq recipient-num-list (sms-extract-angle-bracketed-numbers recipient))
(setq recipient-num-list (sms-extract-plain-numbers recipient)))
(dolist (num recipient-num-list)
(unless (string= num "")
(push num phone-nums)))))
(nreverse phone-nums)))
(defun sms-extract-angle-bracketed-numbers (recipient)
"Extract phone numbers from recipient string.
Where each phone number is surrounded by angle brackets <>."
(let ((phone-nums nil)
(regexp "<[0-9]+>")
(begin-idx nil)
(end-idx 0))
(while (setq beg-idx (string-match regexp recipient end-idx))
(setq end-idx (match-end 0))
(push (substring recipient (1+ beg-idx) (1- end-idx)) phone-nums))
(nreverse phone-nums)))
(defun sms-extract-plain-numbers (recipient)
"Extract user added phone number from RECIPIENT string."
(let ((num (sms-string-remove-number-separators recipient)))
(list num)))
(defun sms-string-remove-number-separators (str)
"Return a copy of STR with all separators in `sms-phone-number-separators' removed."
(remove-if #'(lambda (c) (string-match (char-to-string c) sms-phone-number-separators)) str))
(defun sms-send-buffer-as-text-message (recipients)
"Send contents of current buffer as SMS text message to RECIPIENTS."
(interactive (list (sms-extract-recipients-list)))
(let* ((buffercontents (buffer-string))
(bufferlength (length buffercontents))
(numrecipients (length recipients)))
(if (> bufferlength sms-max-length)
(message (concat "Message is " (int-to-string (- bufferlength sms-max-length)) " characters too long!"))
(dolist (num recipients)
(funcall sms-send-text-message num buffercontents))
(kill-buffer nil))))
(defun sms-number-stringp (elem)
"Return t if ELEM is a string which only contains numbers, otherwise nil."
(unless (stringp elem)
(error "%s is not string" elem))
(dotimes (idx (length elem) t)
(let ((c (elt elem idx)))
(unless (and (<= c ?9)
(>= c ?0))
(error "Phone number %s contains non-number character" elem)))))
(defun sms-bufferp (&optional buffer)
"Return t if BUFFER is a sms-buffer, otherwise nil.
If BUFFER is not provided, current buffer will be used"
(interactive)
(or buffer (setq buffer (current-buffer)))
(with-current-buffer buffer
(eq major-mode 'sms-mode)))
(defun sms-send-text-message-android (number text)
"Send TEXT to NUMBER as SMS text message on Android phone."
(interactive (list (read-string "Phone number: ") (read-string "Message: ")))
(shell-command
(concat
"export AP_PORT=$(netstat -napt|sed -n 's/^tcp.*127.0.0.1:\\([0-9]*\\).*LISTEN.*ase$/\\1/gp');/root/scripts/sendtext.py "
(remove-if (lambda (c) (or (char-equal c ?\t) (char-equal c ?\s))) number)
" " text)))
(defvar sms-mode-map
(let ((sms-mode-map (make-keymap)))
(define-key sms-mode-map (kbd "C-c C-c") 'sms-send-buffer-as-text-message)
(define-key sms-mode-map (kbd "C-c C-k") (lambda nil (interactive) (kill-buffer (current-buffer))))
(define-key sms-mode-map (kbd "<tab>") (lambda nil (interactive) (if (featurep 'bbdb-sms) (bbdb-sms-complete-name))))
sms-mode-map)
"Keymap for SMS major mode.")
(add-to-list 'auto-mode-alist '("\\.sms\\'" . sms-mode))
(defun sms-mode ()
"Major mode for editing SMS text messages.
\\{sms-mode-map}"
(interactive)
(kill-all-local-variables)
(set (make-local-variable 'font-lock-defaults)
'(message-font-lock-keywords t))
(use-local-map sms-mode-map)
(setq major-mode 'sms-mode)
(setq mode-name "SMS")
(run-hooks 'sms-mode-hook))
(provide 'sms)