Skip to content

Instantly share code, notes, and snippets.

@bonny
Last active July 24, 2017 12:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bonny/6649837 to your computer and use it in GitHub Desktop.
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…
<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