; CLisp script for SBCL to export PWman to 1password
; Converts items to 1pass using 1password cli to import rather than an intermediate format; assumes an existing authenticated session
; See README for usage instructions

; Load quicklisp
(load "~/.sbclrc")
; Load libraries
; Had parsing errors with xmls so use cxml instead
(ql:quickload "cxml")
(ql:quickload "cl-ppcre")

; "Escape" certain characters.
(defun escape-quotes
	(text)
	(cl-ppcre:regex-replace-all "\"" (cl-ppcre:regex-replace-all "'" text "\\u0027" :preserve-case t) "\\u0022" :preserve-case t))

; Just assume two template types
; Obtained from `op get template <category>`
(defun template-secure-note
	(content)
	(concatenate 'string "{\"notesPlain\":\"" (escape-quotes content) "\",\"sections\":[]}"))

(defun template-login
	(username password content)
	(concatenate 'string "{\"notesPlain\":\"" (escape-quotes content) "\",\"sections\":[],\"passwordHistory\":[],\"fields\":[{\"value\":\"" username "\",\"name\":\"username\",\"type\":\"T\",\"designation\":\"username\"},{\"value\":\"" password "\",\"name\":\"password\",\"type\":\"P\",\"designation\":\"password\"}]}"))

(defun send-to-op
	(op-category pwman-category name host username passwd launch)
	(progn
		; Case would be better if I can figure that out
		(defparameter template
			(if (string= op-category "Login")
				(template-login username passwd launch)
				; Need to do this and not multiple ifs in progn as otherwise template picks up NIL from progmn
				; Need to combine fields for Secure notes
				(template-secure-note (concatenate 'string "host: " host "; user: " username "; password: " passwd "; launch: " launch))))
		(defparameter extproc (sb-ext:run-program "sh" (list "-c" (concatenate 'string "echo '" template "' | op encode")) :search :environment :output :stream))
		(defparameter encoded-item (read-line (sb-ext:process-output extproc)))
		; Create item
		; Just going to send blanks, etc if that's what some fields are. It doesn't seem to matter.
		; Need to only send url if it's a Login item
		(defparameter defaultargs (list  "create" "item" op-category encoded-item (concatenate 'string "--title=" name) (concatenate 'string "--tags=" pwman-category)))
		(defparameter args
			(if (string= op-category "Login")
				; Append, because need at end
				(append defaultargs (list (concatenate 'string "--url=" host)))
				defaultargs))
		(defparameter cproc (sb-ext:run-program "op" args :search :environment :output :stream))
		(defparameter output (read-line (sb-ext:process-output cproc)))
		(print output)))

; decrpyt the file
; Nice idea, but can't find a way of reading input in a concealed fashion so instead just decrypt the file manually before running this script
;(sb-ext:run-program "gpg" (list "-d" "--password" some-password-we-get-from-input (concatenate 'string (sb-unix::posix-getenv "HOME") "/.pwman.db") ">" ".pwman.db.decrypt") :search :environment)
; read file
(defparameter *pwman* (cxml:parse-file  (concatenate 'string (sb-unix::posix-getenv "HOME") "/.pwman.db.decrypt") (cxml-dom:make-dom-builder)))
(defparameter *categories* (dom:child-nodes (dom:item (dom:child-nodes (dom:document-element  *pwman* )) 0 )))

; Kept for ref
;(dom:do-node-list (category *categories*) (print (dom:get-attribute category "name")))

(dom:do-node-list (category *categories*)
	(progn
		(defparameter category-name (dom:get-attribute category "name"))
		(defparameter pwlists (dom:child-nodes category))
		; Kept for ref - prints category
		;(print category-name)
		(dom:do-node-list (pwlist pwlists)
			(progn
				; Rather than loop through at this point, call things explicitly so can then build the op command
				(defparameter pwitems (dom:child-nodes pwlist))
				; Kept for ref - prints Name
				;(print (dom:tag-name (dom:item pwitems 0)))
				;(print (dom:data (dom:item (dom:child-nodes (dom:item pwitems 0)) 0)))
				; Need to check for NIL and only then set paremter. E.g:
				(defparameter name
					(if (dom:item (dom:child-nodes (dom:item pwitems 0)) 0)
						(dom:data (dom:item (dom:child-nodes (dom:item pwitems 0)) 0))
						"")))
				(defparameter host
					(if (dom:item (dom:child-nodes (dom:item pwitems 1)) 0)
						(dom:data (dom:item (dom:child-nodes (dom:item pwitems 1)) 0))
						""))
				(defparameter username
					(if (dom:item (dom:child-nodes (dom:item pwitems 2)) 0)
						(dom:data (dom:item (dom:child-nodes (dom:item pwitems 2)) 0))
						""))
				(defparameter passwd
					(if (dom:item (dom:child-nodes (dom:item pwitems 3)) 0)
						(dom:data (dom:item (dom:child-nodes (dom:item pwitems 3)) 0))
						""))
				(defparameter launch
					(if (dom:item (dom:child-nodes (dom:item pwitems 4)) 0)
						(dom:data (dom:item (dom:child-nodes (dom:item pwitems 4)) 0))
						""))
				(if (string= category-name "SECURE-NOTES")
					(progn
						(print (concatenate 'string "Creating Secure Note for " name " in " category-name))
						(send-to-op "Secure Note" category-name name host username passwd launch))
					(progn
						(if (string/= category-name "INACTIVE")
							(progn
								(print (concatenate 'string "Creating Login for " name " in " category-name))
								(send-to-op "Login" category-name name host username passwd launch))))))))

; Only right at end do we want to delete file
(delete-file (concatenate 'string (sb-unix::posix-getenv "HOME") "/.pwman.db.decrypt"))
