Created
January 28, 2018 21:55
-
-
Save thejsa/ae3c11b4427cb204bdce648f4f477324 to your computer and use it in GitHub Desktop.
Quick and dirty Flipnote Hatena server (with query string sessions + basic authentication)
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
RewriteEngine On | |
RewriteCond %{REQUEST_FILENAME} !-f | |
RewriteRule . /index.php [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
<html> | |
<head> | |
<meta name="uppertitle" content="{{ pageTitle|default('kaeru:memo') }}"> | |
{% block pageHeadCode %}{% endblock %} | |
</head> | |
<body> | |
{% block pageContent %}{% endblock %} | |
</body> | |
</html> |
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
{ | |
"require": { | |
"klein/klein": "^2.1", | |
"twig/twig": "^2.4" | |
} | |
} |
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
<?php | |
/* | |
Quick and dirty Flipnote Hatena server core | |
Features authentication via proxy basic authentication, and sessions | |
with IDs passed through query strings since Hatena shut down their | |
auth server. | |
Move .html files to a views/ subdirectory. | |
Requires class.ugomenu.php by @jaames, <https://github.com/Sudomemo/sudomemo-utils/blob/master/php/class.ugomenu.php> | |
Database schema: | |
CREATE TABLE `users` ( | |
`fsid` varchar(16) NOT NULL, | |
`username` varchar(16) NOT NULL, | |
`password` varchar(255) NOT NULL, | |
`bio` text, | |
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP | |
) ENGINE=InnoDB DEFAULT CHARSET=latin1; | |
Copyright (c) 2018, thejsa <https://github.com/thejsa> | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions are met: | |
* Redistributions of source code must retain the above copyright notice, this | |
list of conditions and the following disclaimer. | |
* Redistributions in binary form must reproduce the above copyright notice, | |
this list of conditions and the following disclaimer in the documentation | |
and/or other materials provided with the distribution. | |
* Neither the name of the copyright holder nor the names of its | |
contributors may be used to endorse or promote products derived from | |
this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
require './vendor/autoload.php'; // Composer -- modules used: klein/klein ; twig/twig | |
require 'class.ugomenu.php'; // Sudomemo/sudomemo-utils php/class.ugomenu.php | |
ini_set('display_errors', '1'); | |
error_reporting(E_ALL & ~E_NOTICE); | |
$klein = new \Klein\Klein(); | |
$klein->respond(function ($request, $response, $service, $app) use ($klein) { | |
// Start session | |
if(isset($_REQUEST['sessionID'])) session_id($_REQUEST['sessionID']); | |
session_start(); | |
if(isset(apache_request_headers()['Proxy-Authorization'])) { | |
$service->sharedData()->set('credentials', explode(':', base64_decode(explode(' ', apache_request_headers()['Proxy-Authorization'])[1]))); | |
} else { | |
$service->sharedData()->set('credentials', null); | |
} | |
// Error handler | |
$klein->onError(function ($klein, $err_msg) { | |
error_log($err_msg); | |
$klein->service()->flash($err_msg); | |
}); | |
// Twig templating engine | |
$app->register('twig', function () { | |
$loader = new Twig_Loader_Filesystem('./views/'); | |
$twig = new Twig_Environment($loader); | |
$twig->addGlobal('session', $_SESSION); | |
return $twig; | |
}); | |
// Database | |
$app->register('db', function () { | |
return new mysqli('127.0.0.1', 'username', 'password', 'database'); // changeme | |
}); | |
}); | |
$klein->onHttpError(function ($code, $router) { | |
switch($code) { | |
case 404: | |
$router->response()->body('Not found - URI: ' . $router->request()->uri()); | |
break; | |
case 405: | |
$router->response()->body( | |
'You can\'t do that!' | |
); | |
break; | |
default: | |
$router->response()->body( | |
'An HTTP ' . $code . ' error occured :\'(' | |
); | |
break; | |
} | |
}); | |
$klein->with('http://flipnote.hatena.com/ds/v2-[a:region]', function () use ($klein) { | |
$klein->respond('/', function ($request, $response, $service, $app) { | |
return 'Hello, world! I don\'t know what you\'re expecting to see here. ~ jsa 2018-01-27'; | |
}); | |
$klein->respond('/auth', function ($request, $response, $service, $app) { | |
// auth response | |
$response->code(401); // glitches the DSi or smth? | |
$response->header('X-DSi-New-Notices', '1'); | |
$response->header('X-DSi-Unread-Notices', '1'); | |
$response->header('X-DSi-SID', 'temporary'); | |
$response->header('X-DSi-Dialog-Type', '0'); | |
// return mb_convert_encoding('Hello! Please press the button again', 'utf-16le'); | |
// return 'bloop'; | |
}); | |
$klein->respond('/[en|fr|es|it|nl|jp:language]/[:confirm]?/[:file].txt', function ($request, $response, $service, $app) { | |
// | |
$response->header('Content-Type', 'text/plain; charset=UTF-8'); | |
return mb_convert_encoding('I am '.$request->file.'.txt', 'utf-16le'); | |
}); | |
$klein->respond('/eula_list.tsv', function ($request, $response, $service, $app) { | |
// | |
$response->header('Content-Type', 'text/tab-separated-values'); | |
return "RQBuAGcAbABpAHMAaAA=\ten"; | |
}); | |
$klein->respond('/index.ugo', function ($request, $response, $service, $app) { | |
if(!is_array($service->sharedData()->get('credentials'))) { | |
// create account flow | |
$ugomenu = new ugomenu(); | |
$ugomenu->setType('index'); | |
$ugomenu->addItem(array( | |
'url' => 'http://flipnote.hatena.com/ds/v2-eu/begin.html?sessionID='.session_id(), | |
'label' => 'Get started', | |
'icon' => '104' | |
)); | |
return $ugomenu->getUGO(); | |
} | |
$stmt = $app->db->prepare('SELECT * FROM `users` WHERE `username` = ?'); | |
$stmt->bind_param('s', $service->sharedData()->get('credentials')[0]); | |
if(!$stmt): | |
error_log($app->db->error); | |
die('database error: '.$app->db->error); | |
endif; | |
if(!$stmt->execute()) { | |
error_log('failed to execute $stmt on login'); | |
die('database error: failed to execute stmt on login'); | |
} | |
$user = $stmt->get_result()->fetch_all(MYSQLI_ASSOC)[0]; | |
if(!$user) { | |
// create account flow | |
$ugomenu = new ugomenu(); | |
$ugomenu->setType('index'); | |
$ugomenu->addItem(array( | |
'url' => 'http://flipnote.hatena.com/ds/v2-eu/account/link.html?sessionID='.session_id(), | |
'label' => 'Setup account', | |
'icon' => 104 | |
)); | |
return $ugomenu->getUGO(); | |
} | |
$_SESSION['user'] = $user; | |
$ugomenu = new ugomenu(); | |
$ugomenu->setType('index'); | |
$ugomenu->addItem(array( | |
'url' => 'http://flipnote.hatena.com/ds/v2-eu/user/'.$user['fsid'].'.html?sessionID='.session_id(), | |
'label' => 'Hello, ' . $user['username'] . '!', | |
'icon' => 104 | |
)); | |
return $ugomenu->getUGO(); | |
}); | |
$klein->with('/user.[html]?', function () use ($klein) { | |
$klein->respond('GET', '/?', function ($request, $response, $service, $app) { | |
return $app->twig->render('user.html', array ( | |
'user' => $_SESSION['user'] | |
)); | |
}); | |
$klein->respond('GET', '/[:id].[html]?', function ($request, $response, $service, $app) { | |
$identifier = $request->id; | |
$stmt = $app->db->prepare('SELECT * FROM `users` WHERE `fsid` = ? OR `username` LIKE ? LIMIT 1'); | |
$stmt->bind_param('ss', $identifier, $identifier); | |
if(!$stmt): | |
error_log($app->db->error); | |
die($app->db->error); | |
endif; | |
if(!$stmt->execute()){ | |
error_log('failed to execute $stmt: ' . $app->db->error); | |
die('failed to execute $stmt'); | |
} | |
$user = $stmt->get_result()->fetch_all(MYSQLI_ASSOC)[0]; | |
if(!$user) throw Klein\Exceptions\HttpException::createFromCode(404); | |
return $app->twig->render('user.html', array ( | |
'user' => $user | |
)); | |
}); | |
}); | |
$klein->respond('/hello.html', function ($request, $response, $service, $app) { | |
// | |
$response->header('Content-Type', 'text/html; charset=UTF-8'); | |
$proxyAuth = explode(':', base64_decode(explode(' ', apache_request_headers()['Proxy-Authorization'])[1])); // [0] username [1] password | |
return $app->twig->render('user.html', array ( | |
'user' => array ( | |
'username' => $proxyAuth[0], | |
'password' => $proxyAuth[1] | |
) | |
)); | |
}); | |
$klein->respond('/store.kbd', function ($request, $response, $service, $app) { | |
// | |
$response->header('Content-Type', 'text/html; charset=UTF-8'); | |
$proxyAuth = explode(':', base64_decode(explode(' ', apache_request_headers()['Proxy-Authorization'])[1])); // [0] username [1] password | |
return '<html><head><title>hello</title></head><body><p>Hello '.$proxyAuth[0].'! The password you entered was: '.$proxyAuth[1].'</p><br><p>Session ID: '.session_id().'</body></html>'; | |
}); | |
}); | |
$klein->dispatch(); |
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
{% set pageTitle = user.username ~ '\'s profile' %} | |
{% extends '_layout.html' %} | |
{% block pageContent %} | |
<p>User name: {{ user.username }}</p> | |
<p>User FSID: {{ user.fsid }}</p> | |
<p>User Bio: {{ user.bio }}</p> | |
{% if session.user.fsid == user.fsid %}<p>(This is you!)</p>{% endif %} | |
{% endblock %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment