diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9b68413
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# picoblog
+picoblog is a super simple microblog which was written to be a part of [Saisho](https://0xff.nu/saisho).
+It was converted to be standalone after I figured people might want to use it without using Saisho.
+
+See `blog.php` for example usage, or use it as is (note: it's VERY basic).
+
+## What it has
+- Markdown support (using modified Slimdown): Bold, Italic, Marked, Removed, Link, Image, Inline Code.
+- Support for tags and IDs.
+- Using either twtxt format or picoblog format (which is the same as twtxt except it also has unique IDs).
+
+## What it does't have
+- Literally everything else. The goal here (aligned with Saisho), is to keep everything simple.
+
+[Home](https://0xff.nu/picoblog)
+
+[Bug Reports](https://todo.sr.ht/~hxii/picoblog)
\ No newline at end of file
diff --git a/Slimdown.php b/Slimdown.php
new file mode 100644
index 0000000..063b648
--- /dev/null
+++ b/Slimdown.php
@@ -0,0 +1,42 @@
+
+ * Website: https://gist.github.com/jbroadway/2836900
+ * License: MIT
+ */
+class Slimdown {
+ public static $rules = array (
+ '/(? '\1', // links
+ '/(\*\*|__)(.*?)\1/' => '\2', // bold
+ '/(\*|_)(.*?)\1/' => '\2', // emphasis
+ '/\~\~(.*?)\~\~/' => '\1', // del
+ '/\:\"(.*?)\"\:/' => '\1
', // quote
+ '/`(.*?)`/' => '\1
', // inline code
+ '/(?:!\[([^\[]+)\]\(([^\)]+)\))/' => '',
+ '/==(.*?)==/' => '\1',
+ );
+
+ /**
+ * Add a rule.
+ */
+ public static function add_rule ($regex, $replacement) {
+ self::$rules[$regex] = $replacement;
+ }
+
+ /**
+ * Render some Markdown into HTML.
+ */
+ public static function render ($text) {
+ $text = "\n" . $text . "\n";
+ foreach (self::$rules as $regex => $replacement) {
+ if (is_callable ( $replacement)) {
+ $text = preg_replace_callback ($regex, $replacement, $text);
+ } else {
+ $text = preg_replace ($regex, $replacement, $text);
+ }
+ }
+ return trim ($text);
+ }
+}
diff --git a/blog.php b/blog.php
new file mode 100644
index 0000000..3c91450
--- /dev/null
+++ b/blog.php
@@ -0,0 +1,38 @@
+parseQuery();
+$entries = ($query) ? $mb->getEntries($query) : $mb->getEntries('all');
+
+?>
+
+
+
+
+
+
+
+ picoblog @ = $_SERVER['HTTP_HOST'] ?>
+
+
+
+ picoblog
+ Currently viewing ' . implode('', $query) . '. Back to list?';
+ }
+ ?>
+
+
+ = $mb->renderEntries($entries, '- {entry}
'); ?>
+
+
+
+
\ No newline at end of file
diff --git a/blog.txt b/blog.txt
new file mode 100644
index 0000000..a890835
--- /dev/null
+++ b/blog.txt
@@ -0,0 +1 @@
+2020-10-01T08:05:33Z ac7c64 Welcome to picoblog. For updates and such, visit [https://0xff.nu/picoblog](https://0xff.nu/picoblog).
\ No newline at end of file
diff --git a/picoblog.php b/picoblog.php
new file mode 100644
index 0000000..dc17da4
--- /dev/null
+++ b/picoblog.php
@@ -0,0 +1,154 @@
+sourcefile = $sourcefile;
+ $this->format = $format;
+ $this->readSource();
+ }
+
+ /**
+ * Check for and parse query string from $_SERVER['QUERY_STRING']).
+ * Used to get entries by ID or tag.
+ *
+ * @return array|boolean
+ */
+ public function parseQuery()
+ {
+ if (isset($_SERVER['QUERY_STRING'])) {
+ parse_str($_SERVER['QUERY_STRING'], $return);
+ return $return;
+ }
+ return false;
+ }
+
+ /**
+ * Read source file
+ *
+ * @return boolean true if successful, false if not
+ */
+ private function readSource()
+ {
+ if (is_file($this->sourcefile) && is_readable($this->sourcefile)) {
+ $this->rawentries = file($this->sourcefile);
+ if (!empty($this->rawentries)) {
+ return true;
+ }
+ }
+ throw new \Exception("{$this->sourcefile} is empty! Aborting.");
+ return false;
+ }
+
+ /**
+ * Parse entries from source file and replace tags with links
+ *
+ * @param array $entries array of raw entries
+ * @return void
+ */
+ private function parseEntries(array $entries, bool $parseTags = true)
+ {
+ switch ($this->format) {
+ case 'twtxt':
+ $pattern = '/^(?[0-9-T:Z]+)\t(?.*)/';
+ break;
+ case 'picoblog':
+ $pattern = '/^(?[0-9-T:Z]+)\t(?[a-zA-Z0-9]{6})\t(?.*)/';
+ break;
+ }
+ foreach ($entries as $i => $entry) {
+ preg_match($pattern, $entry, $matches);
+ if (!$matches) continue;
+ $id = (!empty($matches['id'])) ? $matches['id'] : $i;
+ $parsedEntries[$id] = [
+ 'date' => $matches['date'],
+ 'entry' => ($parseTags) ? preg_replace('/#(\w+)?/', '#${1}', $matches['entry']) : $matches['entry'],
+ ];
+ }
+ return $parsedEntries;
+ }
+
+ /**
+ * Returns a filtered list of raw entries
+ *
+ * @param string|array $search entry filter. can be 'all', 'newest', 'oldest', 'random' or an ID/Tag.
+ * For ID, we're looking for ['id'=>'IDHERE']. For tag, we're looking for ['tag'=>'tagname']
+ * @return boolean|array
+ */
+ public function getEntries($search)
+ {
+ switch ($search) {
+ case '':
+ return false;
+ case 'all':
+ return $this->rawentries;
+ case 'newest':
+ return [reset($this->rawentries)];
+ case 'oldest':
+ return [end($this->rawentries)];
+ case 'random':
+ return [$this->rawentries[array_rand($this->rawentries, 1)]];
+ default:
+ if (isset($search['id'])) {
+ $filter = array_filter($this->rawentries, function ($entry) use ($search) {
+ preg_match("/\b$search[id]\b/i", $entry, $match);
+ return $match;
+ });
+ return $filter;
+ } elseif (isset($search['tag'])) {
+ $filter = array_filter($this->rawentries, function ($entry) use ($search) {
+ preg_match("/#\b$search[tag]\b/i", $entry, $match);
+ return $match;
+ });
+ return $filter;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Render Markdown in given entries and output as HTML
+ *
+ * @param array $entries array of parsed entries to render
+ * @param string $entryWrap tne entry wrapper, e.g. {entry}
+ * @param bool $parseTags should #tags be parsed to links?
+ * @return string entries in HTML
+ */
+ public function renderEntries(array $entries, string $entryWrap = '{entry}', bool $parseTags = true)
+ {
+ if (!$entries) return false;
+ $entries = $this->parseEntries($entries, $parseTags);
+ require_once('Slimdown.php');
+ $html = '';
+ foreach ($entries as $id => $entry) {
+ $text = \Slimdown::render($entry['entry']);
+ $date = $entry['date'];
+ $text = "[{$id}] " . $text;
+ $html .= str_replace('{entry}', $text, $entryWrap);
+ }
+ return $html;
+ }
+}
\ No newline at end of file