Last active
July 24, 2017 12:48
-
-
Save bonny/6649837 to your computer and use it in GitHub Desktop.
PHP script to list and retrieve notes from the OS X Simple Notes app. Nice to have since import/sync seems buggy in first version of the app (It deleted two months of notes for me anyway, because of a crash during import.) How to:
Find the file "Simplenote.storedata.xml" that is located perhaps here or in your time machine or Arq or similar:
~/L…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<meta charset="utf-8"> | |
<title>SimpleNote Restorer</title> | |
<style> | |
body { | |
font-family: sans-serif; | |
background-color: #f5f5f5; | |
color: #111; | |
font-size: 14px; | |
} | |
li { | |
margin-bottom: .5em; | |
} | |
.notes-list { | |
border-collapse: collapse; | |
font-size: 1em; | |
} | |
.notes-list th, | |
.notes-list td { | |
text-align: left; | |
vertical-align: top; | |
border-bottom: 1px solid #d5d5d5; | |
padding: 2em; | |
font-size: 1em; | |
} | |
.notes-list tr:nth-child(odd) td { | |
} | |
.notes-list .note-is-deleted-1 td { | |
text-decoration: line-through; | |
color: #666; | |
font-size: .75em; | |
} | |
.notes-list .content { | |
} | |
</style> | |
<?php | |
/** | |
* Class to list and retrieve notes from the OS X Simple Notes app | |
* Nice to have since import/sync seems buggy in first version of the app | |
* (It deleted two months of notes for me anyway) | |
* | |
* How to: | |
* Find the file "Simplenote.storedata.xml" that is located here (your location may vary) | |
* ~/Library/Containers/com.automattic.SimplenoteMac/Data/Library/Simplenote/Simplenote.storedata | |
* Copy the file to the folder where this script is located. Start a webserver and run this script in it. | |
* | |
*/ | |
error_reporting(E_ALL); | |
ini_set('display_errors', '1'); | |
class Simplenote_Restore { | |
public $simplenote_xml_file = "Simplenote.storedata.xml"; | |
public $arr_notes; | |
/** | |
* Create a table output of all notes | |
*/ | |
function list_notes() { | |
$this->load_notes(); | |
// Output notes in table | |
echo "<p>Creating table with all notes ...</p>"; | |
printf(' | |
<table class="notes-list"> | |
<tr> | |
<th>deleted</th> | |
<th>modificationdate</th> | |
<th>creationdate</th> | |
<th>content</th> | |
</tr> | |
'); | |
$loopnum = 0; | |
foreach ($this->arr_notes as $one_note) { | |
// info is stored in children of note | |
// check child note attribute for useful info, like | |
// modificationdate | |
// deleted | |
// creationdate | |
// content | |
$out_mod_date = ""; | |
$out_cre_date = ""; | |
$out_deleted = ""; | |
$out_content = ""; | |
foreach ($one_note->children() as $one_child) { | |
$child_attributes = $one_child->attributes(); | |
switch ( (string) $child_attributes["name"] ) { | |
case "modificationdate": | |
$out_mod_date = sprintf('%s', $this->get_date_from_nsdate($one_child)); | |
break; | |
case "creationdate": | |
$out_cre_date = sprintf('%s', $this->get_date_from_nsdate($one_child)); | |
break; | |
case "deleted": | |
$out_deleted = sprintf('%s', $one_child); | |
break; | |
case "content": | |
$out_content = sprintf('%s', $one_child); | |
break; | |
} | |
} | |
printf(' | |
<tr class="note-is-deleted-%3$s"> | |
<td>%3$s</td> | |
<td nowrap>%1$s</td> | |
<td nowrap>%2$s</td> | |
<td class="content">%4$s</td> | |
</tr>', $out_mod_date, $out_cre_date, $out_deleted, nl2br(($out_content))); | |
$loopnum++; | |
#if ($loopnum > 10) break; | |
} | |
print '</table>'; | |
echo "<p>Done. All found notes are not in the table above.</p>"; | |
} | |
/* | |
dates are stored in NSDate format, thank's to stackoverflow for info: | |
http://stackoverflow.com/questions/11297704/strange-date-format-in-xml-convert-to-ruby-datetime-object | |
*/ | |
static function get_date_from_nsdate($date) { | |
list($date, $day) = explode(".", $date); | |
// date is not calced exactly, but it's good enough for me | |
$date = $date + 978307783; | |
return date("Y-m-d H:i" , $date ); | |
} | |
function restore_notes() { | |
$this->load_notes(); | |
// Output notes in table | |
$foldername = dirname(__FILE__) . "/restored_notes_" . time() . ""; | |
echo "<p>Creating folder for notes with name <code>$foldername</code> ...</p>"; | |
if (file_exists($foldername)) | |
die("<p>Error: could not create folder. Perhaps it already exists after all?"); | |
mkdir($foldername); | |
$loopnum = 0; | |
foreach ($this->arr_notes as $one_note) { | |
// info is stored in children of note | |
// check child note attribute for useful info, like | |
// modificationdate | |
// deleted | |
// creationdate | |
// content | |
$out_mod_date = ""; | |
$out_cre_date = ""; | |
$out_deleted = ""; | |
$out_content = ""; | |
foreach ($one_note->children() as $one_child) { | |
$child_attributes = $one_child->attributes(); | |
switch ( (string) $child_attributes["name"] ) { | |
case "modificationdate": | |
$out_mod_date = sprintf('%s', $this->get_date_from_nsdate($one_child)); | |
break; | |
case "creationdate": | |
$out_cre_date = sprintf('%s', $this->get_date_from_nsdate($one_child)); | |
break; | |
case "deleted": | |
$out_deleted = sprintf('%s', $one_child); | |
break; | |
case "content": | |
$out_content = sprintf('%s', $one_child); | |
break; | |
} | |
} | |
if ($out_deleted) | |
continue; | |
// Create and write to file | |
$lines = explode("\n", trim($out_content)); | |
$first_line = isset($lines[0]) ? $lines[0] : date() . rand(0.1000); | |
$filename = $first_line . ".txt"; | |
$filename_with_path = $foldername . "/" . $filename; | |
echo "<hr>"; | |
printf('<p>Creating file with name<br><code>%1$s</code></p>', $filename); | |
file_put_contents($filename_with_path, $out_content); | |
touch($filename_with_path, strtotime($out_mod_date)); | |
$loopnum++; | |
#if ($loopnum > 10) break; | |
} | |
echo "<p>Done. All found notes are not in the created folder.</p>"; | |
} | |
function load_notes() { | |
$file = dirname(__FILE__) . "/" . $this->simplenote_xml_file; | |
if (!file_exists($file)) | |
die("Could not find file " . htmlspecialchars($file)); | |
if (!is_readable($file)) | |
die("Could not read file " . htmlspecialchars($file)); | |
printf('<p>Reading file <code>%s</code> ...', $file); | |
$xml = simplexml_load_file($file); | |
if (false === $xml) | |
die("simplexml_load_file() could not load the file. Is it valid XML?"); | |
// Both notes and tags are stored in objet | |
$objects = $xml->object; | |
#echo "<p>Found {$objects->count()} objects."; | |
// Filter out notes | |
$notes = array(); | |
foreach ($objects as $one_object) { | |
#if ($one_object->) | |
$attributes = $one_object->attributes(); | |
if ("NOTE" === (string) $attributes["type"]) { | |
$notes[] = $one_object; | |
} | |
} | |
echo "<p>Found " . count($notes) . " notes.</p>"; | |
// order notes by date | |
usort($notes, function($a, $b) { | |
$modificationdate_a = 0; | |
$modificationdate_b = 0; | |
$childs = $a->children(); | |
foreach ($childs as $child) { | |
$atts = $child->attributes(); | |
if ( (string) $atts["name"] === "modificationdate" ) { | |
$modificationdate_a = $child; | |
} | |
} | |
$childs = $b->children(); | |
foreach ($childs as $child) { | |
$atts = $child->attributes(); | |
if ( (string) $atts["name"] === "modificationdate" ) { | |
$modificationdate_b = $child; | |
} | |
} | |
#echo $modificationdate_a; | |
#echo "<br>".$modificationdate_b;exit; | |
#if ($modificationdate_a == $modificationdate_b) | |
# return 0; | |
$modificationdate_a = Simplenote_Restore::get_date_from_nsdate($modificationdate_a); | |
$modificationdate_b = Simplenote_Restore::get_date_from_nsdate($modificationdate_b); | |
return ($modificationdate_a < $modificationdate_b) ? 1 : -1; | |
}); | |
$this->arr_notes = $notes; | |
} | |
} | |
?> | |
<h1>SimpleNote restorer</h1> | |
<ul> | |
<li> | |
<a href="?action=list_notes">List all notes</a> | |
<br>Just gives you a list of all notes, including date and short info. Nothing will be (over)written anywhere. | |
</li> | |
<li> | |
<a href="?action=restore_notes">Restore all notes</a> | |
<br>Creates a new, unique, folder and restores all notes there. Each note gets it's own file. Things should not be overwritten, but don't take my word for it. | |
</li> | |
</ul> | |
<hr> | |
<?php | |
$action = $_GET["action"]; | |
$valid_actions = array("list_notes", "restore_notes"); | |
if (!$action) | |
die("Select an action to get started."); | |
if ( ! in_array($action, $valid_actions) ) | |
die("That's not a valid action."); | |
printf('<p>Doing action <strong>%1$s</strong>:</p>', $action); | |
$restorer = new Simplenote_Restore; | |
$restorer->$action(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment