コミットを比較

...

11 コミット

作成者 SHA1 メッセージ 日付
たかし 2d6f04d684 ksort 2023-10-22 06:42:05 +00:00
たかし 3778101278 fix bug user error 2023-10-19 15:52:12 +00:00
たかし 41ce8b4e9f Add post validation 2023-10-03 14:42:11 +00:00
たかし 549af48942 shinjitai 2023-10-01 11:32:13 +00:00
たかし 585037bcbe Add res redirect 2023-10-01 11:28:47 +00:00
たかし 730de9bfbb Add res anchor link 2023-10-01 10:28:58 +00:00
たかし 0cf538a7cb Improve reply UI 2023-09-30 14:16:30 +00:00
たかし 54e62c3bc7 Add nitter-like theme 2023-09-30 08:49:17 +00:00
たかし 4e56eb746f Fix default theme 2023-09-22 13:11:51 +00:00
たかし d831b416d0 Fix username of tl. 2023-09-19 12:23:27 +00:00
たかし f804c23d75 Add special user (tl). 2023-09-19 12:15:08 +00:00
13個のファイルの変更407行の追加26行の削除

ファイルの表示

@ -109,6 +109,14 @@ function output_csrf_token_hidden() {
// String
function anchor_to_link($s, $thread_id) {
return preg_replace(
'/>>([1-9]+[0-9]{0,10})/',
'<a href="' . sitebase('post/?id=') . $thread_id . '&amp;res=\1">&gt;&gt;\1</a>',
$s
);
}
function url_to_link($s) {
return preg_replace(
'/((http|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:\/~+#-]*[\w@?^=%&amp;\/~+#-])?)/',

ファイルの表示

@ -114,6 +114,29 @@ function get_post_metadata($file, $deleted = false) {
return compact('id', 'is_guest', 'userid', 'detail_url', 'delete_url', 'user_url', 'datetime', 'time', 'thread_id', 'thread_title', 'thread_url', 'is_mine', 'is_future');
}
function load_res_num($id) {
$filepath = filepath_by_post_id($id);
if ($filepath === null) { return 1; }
$file = basename($filepath);
$meta = get_post_metadata($file);
$thread_id = $meta['thread_id'];
$meta = null;
if ($thread_id <= '') { return 1; }
$pattern = "2*Z*.th{$thread_id}.txt";
$list = glob(POST_DIR . $pattern);
$search = ".id{$id}.";
$i = 0;
foreach ($list as $i => $filepath) {
if (strpos(basename($filepath), $search) > 0) {
return $i + 2;
}
}
return 1; // fallback
}
function load_post($filepath) {
$files = null;
@ -121,7 +144,6 @@ function load_post($filepath) {
$deleted = $detail['deleted'];
$title = $detail['title'];
$body_raw = $detail['body'];
$body = url_to_link(nl2br(htmlspecialchars($body_raw), false));
$attachment_id = $detail['attachment_id'];
$detail = null;
@ -139,6 +161,16 @@ function load_post($filepath) {
$username = GUESTNAME;
}
$body = url_to_link(
nl2br(
anchor_to_link(
htmlspecialchars($body_raw),
$metadata['thread_id'] ?? $metadata['id']
),
false
)
);
$attachments = [];
if ($attachment_id > '') {
$attachments[] = [
@ -154,13 +186,8 @@ function load_post($filepath) {
}
function load_post_by_id($id) {
$files = glob(POST_DIR . '2*Z*.id' . $id . '.*.txt');
if (sizeof($files) !== 1) {
$files = null;
return null;
}
$filepath = $files[0];
$filepath = filepath_by_post_id($id);
if ($filepath === null) { return null; }
return load_post($filepath);
}
@ -177,7 +204,7 @@ function search_post($options = []) {
$options = null;
$pattern = '2*Z*.txt';
if ($key === 'userid' && $value > '') {
if ($key === 'userid' && $value > '' && $value !== 'tl') {
$pattern = "2*Z*.us{$value}.*.txt";
}
elseif ($key == 'thread' && $value >= 0) {
@ -492,6 +519,14 @@ function count_post_today() {
return $size;
}
function filepath_by_post_id($id) {
$files = glob(POST_DIR . '2*Z*.id' . $id . '.*.txt');
if (sizeof($files) !== 1) {
return null;
}
return $files[0];
}
function load_post_title_by_id($id) {
global $post_title_cache;

ファイルの表示

@ -37,6 +37,12 @@ function load_users() {
return $users_cache;
}
function load_users_with_special() {
$users = load_users();
$users['tl'] = ['id' => 'tl', 'username_raw' => SITENAME, 'username' => SITENAME, 'trip' => ''];
return $users;
}
function update_user($user) {
// 注意:ここを変更したら、必ず、ユーザーが消えない事をテストすること。
@ -124,6 +130,8 @@ function add_user($user) {
// 将来的に「-」より前の行にユーザーごとの設定値などを記録する予定。
function load_profile($id) {
if ($id === 'tl') { return ['bio' => '']; }
$profile = @file_get_contents(PROFILE_DIR . $id . '.txt') ?? '';
if ($profile <= '') { return null; }

2
public/.well-known/index.php ノーマルファイル
ファイルの表示

@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../../activitypub/webfinger.php';

ファイルの表示

@ -18,6 +18,32 @@ function do_get() {
$post = load_post_by_id($id);
if (!$post) { return on_error(400, ['書き込みが存在しない']); }
if (isset($_GET['res'])) {
// TODO:to data-post (or common?)
$res = $_GET['res'] ?? '';
if ((int)$res <= 0 || (((int)$res) . '') !== $res) { return on_error(400, ['返信番号が不正']); }
$redirect_id = null;
if ($res === '1') {
$redirect_id = $id;
}
elseif ((int)$res >= 2) {
$result = search_post(['key' => 'thread', 'value' => $id]);
foreach ($result['post_list'] as $i => $res_post) {
if ($i + 2 === (int)$res) {
$redirect_id = $res_post['id'];
break;
}
}
$result = null;
$res_post = null;
}
if ($redirect_id === null) { return on_error(404, ['返信が存在しない']); }
http_response_code(301);
header('Location: ' . sitebase('post/?id=' . $redirect_id));
return;
}
$view['post'] = $post;
$thread_id = $post['thread_id'];
@ -32,6 +58,7 @@ function do_get() {
$view['total'] = $total;
$view['reply_list'] = $result['post_list'];
}
$view['res_num'] = load_res_num($id);
$view['thread_size_over'] = ($total + 1 >= THREAD_SIZE);
$result = null;
$view['form'] = ['thread_id' => $is_reply ? $thread_id : $id];
@ -74,6 +101,9 @@ function do_post() {
$body = sanitize_post_body($body);
$error_body = validate_post_body($body);
}
if (!$error_body && $thread_id !== NULL && !(ENABLE_IMAGE && $has_file)) {
$error_body = validate_post_body_with_anchor($body);
}
$errors = array_merge($error_title, $error_body);
if ($errors) { return on_error(400, $errors); }

ファイルの表示

@ -100,7 +100,6 @@ form ul li
label
{
display: block;
font-size: 80%;
}
button, textarea, input
@ -113,12 +112,12 @@ button, textarea, input
textarea, input
{
background: #2b2b2b;
background: #12110c;
box-sizing: border-box;
border-color: #000;
border-color: #4b4b4b;
color: #ccc;
width: 100%;
padding: 0 0.25em;
padding: 0.25em;
}
input[type='checkbox']
@ -130,15 +129,15 @@ input[type='checkbox']
input[type='file']
{
background: none;
background: #12110c;
color: #ccc;
font-size: 70%;
padding: 0.25em;
padding: 0.4em;
}
input[type='file']:hover
{
background: #4d4d4d;
background: #2b2b2b;
color: #ccc;
}

282
public/theme/nitter.css ノーマルファイル
ファイルの表示

@ -0,0 +1,282 @@
/** theme: nitter-linke **/
/** bibis-version: 0.9.3 **/
/*
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without any warranty.
*/
*
{
border: none;
font-size: 100%;
margin: 0;
padding: 0;
vertical-align: top;
}
body
{
background: #0F0F0F;
color: #F8F8F2;
line-height: 1.5;
margin: 1em;
word-break: break-all;
}
h1, h2, h3, h4, h5, h6, dl, ul, ol, p
{
background: #161616;
color: #F8F8F2;
}
h2, dl, ul
{
border-top: thin solid #3E3E35;
}
h1, h2, button, label, b
{
font-weight: 600;
}
h1, h2, ul
{
padding: 0.25em 1em;
}
h1
{
text-align: center;
}
ul
{
list-style: none;
}
dd ul
{
border: thin solid #888889;
margin: 0.5em 0 0;
}
dl
{
display: block; /*dillo*/
padding: 0.5em 1em 0;
}
dt
{
/*color: rgb(205, 205, 200);*/
font-weight: normal;
}
dd
{
clear: both; /* see: .post-time */
padding-bottom: 0.5em;
}
p
{
padding: 0 1em 0.5em;
}
form ul
{
padding-top: 0.5em;
}
form ul li
{
padding: 0 0 0.5em;
}
label
{
display: block;
}
button, textarea, input
{
background: #121212;
color: #F8F8F2;
font-family: inherit;
border: thin solid rgb(153, 69, 62);
font-size: 100%;
line-height: 1.5;
}
button:hover, textarea:hover, input:hover
{
border-color: #FF6360;
}
textarea, input
{
box-sizing: border-box;
width: 100%;
padding: 0.25em;
}
input[type='checkbox']
{
padding: 0;
vertical-align: baseline;
width: auto;
}
input[type='file']
{
background: #12110c;
color: #ccc;
font-size: 70%;
padding: 0.4em;
}
textarea
{
height: 6em;
}
button
{
background: none;
cursor: pointer;
color: #FF6360;
padding: 0.25em 1.5em;
text-align: center;
}
a
{
background: none;
color: #FF6C60;
text-decoration: none;
}
a:hover
{
text-decoration: underline;
}
/* class */
.spooftime-text
{
font-style: italic;
font-weight: 400;
}
:checked ~ .spooftime-text
{
background: #0ff;
color: #000;
font-style: normal;
font-weight: 600;
}
.form-li-submit
{
text-align: right;
}
.post-time
{
float: right;
}
a.post-time
{
color: #FF6C60;
}
.post-time.is-future
{
background: #0ff;
color: #000;
}
.pager
{
height: 3em;
line-height: 2;
padding: 1em;
}
.pager-after a,
.pager-before a
{
display: block;
}
.pager-after
{
float: left;
}
.pager-after a
{
padding: 0.5em 1em;
}
.pager-after a:hover
{
}
.pager-before
{
float: right;
}
.pager-before a
{
background: #222;
color: #FF6C60;
padding: 0.5em 1.5em 0.5em 2em;
}
.pager-before a:hover
{
background: #282828;
color: #FF6C60;
}
/* for modern browsers, and netsurf */
@media (min-width: 880.1px)
{
body
{
left: -125px;
margin: 1em auto;
position: relative;
width: 600px;
}
.menu
{
background: none;
border: none;
color: #ccc;
margin: 0.75em 0 0 600px;
position: fixed;
top: 0;
}
.menu li
{
width: 200px;
}
.menu a
{
display: block;
line-height: 2;
}
}

ファイルの表示

@ -3,7 +3,7 @@ require_once(__DIR__ . '/../../require.php');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$user_list = load_users();
ksort($user_list);
ksort($user_list, SORT_STRING | SORT_FLAG_CASE);
$view['user_list'] = $user_list;
output_html($view, ['header.php', 'user-list.php']);

ファイルの表示

@ -2,10 +2,10 @@
require_once(__DIR__ . '/../../require.php');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$id = $_GET['id'] ?? '';
if (!strlen($id) || validate_register_id($id)) { return on_error(400, ['不正なリクエスト。']); }
$id = '' . ($_GET['id'] ?? '');
if ($id !== 'tl' && (!strlen($id) || validate_register_id($id))) { return on_error(400, ['不正なリクエスト。']); }
$users = load_users();
$users = load_users_with_special();
if (!isset($users[$id])) { return on_error(400, ['存在しない ID']); }
$view['user'] = $users[$id];

ファイルの表示

@ -6,7 +6,7 @@ function validate_register_id($s) {
$len = mb_strlen(mbtrim($s));
if ($len < 3) { return ['ID は必須、3文字以上。']; }
if ($len > 12) { return ['ID は 12文字以内。']; }
if (!preg_match('/^[a-zA-Z0-9]+$/', $s)) { return ['ID は半角英のみ。']; }
if (!preg_match('/^[a-zA-Z0-9]+$/', $s)) { return ['ID は半角英のみ。']; }
return [];
}
@ -64,6 +64,13 @@ function validate_post_body($s) {
return [];
}
function validate_post_body_with_anchor($s) {
// アンカーのみ(返信時のデフォルト)は不許可
$s = preg_replace('/^>>[0-9]+/', '', $s);
if (mbtrim($s) === '') { return ['アンカー「>>○○」1つだけの返信は不可。']; }
return [];
}
function sanitize_post_body($s) {
return sanitize_multiline($s);
}

ファイルの表示

@ -62,8 +62,8 @@ function view_post($post, $options = []) {
<dt>件名:<b><?= $title ?></b></dt>
<?php endif; ?>
<dt><?= $res_num > 0 ? "{$res_num} " : '' ?><?= $html_user ?> <?= $html_time ?></dt>
<?php if ($link_to_thread && $thread_id > ''): ?>
<dd>RE: <a href="<?= $thread_url ?>"><?= $thread_title ?></a></dd>
<?php if ($link_to_thread && $thread_id > '' && !$is_single): ?>
<dd>RE:<a href="<?= $thread_url ?>"><?= $thread_title ?></a></dd>
<?php endif; ?>
<dd><?= $body ?>
<?php if (ENABLE_ATTACHMENT && $html_attachment_items > ''): ?>

ファイルの表示

@ -1,5 +1,9 @@
<?php
// Post form
$value = '';
if (($view['res_num'] ?? 0) >= 2) {
$value = '>>' . $view['res_num'] . PHP_EOL;
}
?>
<?php if (ENABLE_POST): ?>
<?php if (isset($view['form']['thread_id']) && $view['form']['thread_id'] > ''): ?>
@ -37,7 +41,7 @@ $view['form'] = $view['form'] ?? [];
<?php if (!(isset($view['form']['thread_id']) && $view['form']['thread_id'] > '')): ?>
<li><label for="TITLE">件名 (省略可)・Title (Optional)</label> <input type="text" id="TITLE" name="title">
<?php endif; ?>
<li><label for="BODY">本文 (500文字以内)・Text (500)</label> <textarea id="BODY" name="body" cols="40" rows="5"></textarea>
<li><label for="BODY">本文 (500文字以内)・Text (500)</label> <textarea id="BODY" name="body" cols="40" rows="5"><?= htmlspecialchars($value) ?></textarea>
<?php if (ENABLE_SPOOF_TIME): ?>
<li><label><input type="checkbox" name="spooftime" value="1"> <b class="spooftime-text">ランダム予約投稿(3H~27H遅らせる)</b></label></li>
<?php endif; ?>

ファイルの表示

@ -3,7 +3,9 @@
require __DIR__ . '/post-common.php';
$post = $view['post'] ?? [];
$title = $post['title'] ?? '';
$title = ($post['title']) ?? '';
$thread_title = ($post['thread_title']) ?? '';
$thread_url = ($post['thread_url']) ?? '';
$reply_list = $view['reply_list'] ?? [];
$total = sizeof($reply_list) + 1;
$total_view = $total > 1 ? " ($total)" : '';
@ -12,9 +14,13 @@ $total_view = $total > 1 ? " ($total)" : '';
<h2>件名:<?= htmlspecialchars("$title{$total_view}") ?></h2>
<?php elseif ($post['thread_id'] <= ''): ?>
<h2><?= htmlspecialchars('無題#' . mb_substr($post['id'], 0, 7) . $total_view) ?></h2>
<?php elseif ($thread_title > ''): ?>
<h2>RE:<a href="<?= $thread_url ?>"><?= htmlspecialchars($thread_title) ?></a></h2>
<?php elseif ($post['thread_id'] <= ''): ?>
<h2>RE:<a href="<?= $thread_url ?>"><?= htmlspecialchars('無題#' . mb_substr($post['thread_id'], 0, 7)) ?></a></h2>
<?php endif; ?>
<?php
view_post($post, ['res_num' => $post['thread_id'] > '' ? 0 : 1, 'is_single' => true, 'link_to_thread' => true]);
view_post($post, ['res_num' => $post['thread_id'] > '' ? 0 : 1, 'is_single' => true, 'link_to_thread' => true, 'res_num' => $view['res_num'] ?? 1]);
?>
<?php
foreach ($reply_list as $i => $reply) {