480 lines
16 KiB
PHP
480 lines
16 KiB
PHP
<?php
|
|
/*************************************************************
|
|
# 076 License
|
|
|
|
Copyright (c) テクニカル諏訪子
|
|
|
|
Permission is hereby granted to any person obtaining a copy of the software
|
|
Little Beast (the "Software") to use, modify, merge, copy, publish, distribute,
|
|
sublicense, and/or sell copies of the Software, subject to the following conditions:
|
|
|
|
1. **Origin Attribution**:
|
|
- You must not misrepresent the origin of the Software; you must not claim
|
|
you created the original Software.
|
|
- If the Software is used in a product, you must either:
|
|
a. Provide clear attribution in the product's documentation, user interface,
|
|
or other visible areas, **OR**
|
|
b. Pay the original developers a fee they specify in writing.
|
|
2. **Usage Restriction**:
|
|
- The Software, or any derivative works, dependencies, or libraries
|
|
incorporating it, must not be used for censorship or to suppress freedom of
|
|
speech, expression, or creativity. Prohibited uses include, but are not
|
|
limited to:
|
|
- Censorship of so-called "hate speech", visuals, non-mainstream opinions,
|
|
ideas, or objective reality.
|
|
- Tools or systems designed to restrict access to information or
|
|
artistic works.
|
|
3. **Notice Preservation**:
|
|
- This license and the above copyright notice must remain intact in all copies
|
|
of the source code.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
if (!CURL_ENABLED) define('ACTIVITYPUB_ENABLED', false);
|
|
|
|
enum LogType {
|
|
case ActivityPub;
|
|
case Auth;
|
|
case Mailer;
|
|
case Image;
|
|
}
|
|
|
|
class Result {
|
|
public bool $isSuccess;
|
|
public ?string $message;
|
|
public array $data;
|
|
|
|
public function __construct(bool $isSuccess, ?string $message = null, array $data = []) {
|
|
$this->isSuccess = $isSuccess;
|
|
$this->message = $message;
|
|
$this->data = $data;
|
|
}
|
|
|
|
public static function Success(?string $message = null, array $data = []): self {
|
|
return new self(true, $message, $data);
|
|
}
|
|
|
|
public static function Error(string $message, array $data = []): self {
|
|
return new self(false, $message, $data);
|
|
}
|
|
}
|
|
|
|
class Roles {
|
|
// 例: if ($user->role & (Roles::STAFF | Roles::NINTENDONDA))
|
|
public const int BANNED = 0;
|
|
public const int MEMBER = 1 << 0; // 1
|
|
public const int NINTENDONDA = 1 << 1; // 2
|
|
public const int PLAYSTATIONNDA = 1 << 2; // 4
|
|
public const int FULLNDA = 1 << 3; // 8 (NINTENDONDA + PLAYSTATIONNDA)
|
|
public const int SUBSCRIBER = 1 << 4; // 16
|
|
public const int NSUBSCRIBER = 1 << 5; // 32 (NINTENDNDA + SUBSCRIBER)
|
|
public const int PSUBSCRIBER = 1 << 6; // 64 (PLAYSTATIONNDA + SUBSCRIBER)
|
|
public const int FSUBSCRIBER = 1 << 7; // 128 (NINTENDNDA + PLAYSTATIONNDA + SUBSCRIBER)
|
|
public const int STAFF = 1 << 8; // 256
|
|
public const int ADMIN = 1 << 9; // 512
|
|
}
|
|
|
|
class Gender {
|
|
public const int UNKNOWN = -1; // 不明
|
|
public const int MALE = 0; // 男性
|
|
public const int FEMALE = 1; // 女性
|
|
}
|
|
|
|
$colorPalette = [
|
|
'ultradark' => [
|
|
'black' => '#020102',
|
|
'white' => '#b3b1b3',
|
|
'grey' => '#5c535c',
|
|
'yellow' => '#8d8b0d',
|
|
'orange' => '#724e0b',
|
|
'green' => '#1e6907',
|
|
'purple' => '#410a5a',
|
|
'lime' => '#198d5b',
|
|
'pink' => '#9e0ea3',
|
|
'cyan' => '#1e8c9b',
|
|
'red' => '#861623',
|
|
'blue' => '#164a85',
|
|
],
|
|
'dark' => [
|
|
'black' => '#120f12',
|
|
'white' => '#cfcbcf',
|
|
'grey' => '#746c75',
|
|
'yellow' => '#b8b515',
|
|
'orange' => '#ac7718',
|
|
'green' => '#2c980c',
|
|
'purple' => '#550f75',
|
|
'lime' => '#10c074',
|
|
'pink' => '#c016c6',
|
|
'cyan' => '#1cbcd0',
|
|
'red' => '#bc1729',
|
|
'blue' => '#1a6ecf',
|
|
],
|
|
'medium' => [
|
|
'black' => '#232023',
|
|
'white' => '#f6f6f6',
|
|
'grey' => '#988f98',
|
|
'yellow' => '#f1ed25',
|
|
'orange' => '#f7a717',
|
|
'green' => '#2de12c',
|
|
'purple' => '#b421f8',
|
|
'lime' => '#20f398',
|
|
'pink' => '#f545f5',
|
|
'cyan' => '#29d3ff',
|
|
'red' => '#ee4030',
|
|
'blue' => '#2687f7',
|
|
],
|
|
'light' => [
|
|
'black' => '#443b44',
|
|
'white' => '#fcfcfc',
|
|
'grey' => '#bdb4bd',
|
|
'yellow' => '#ecea71',
|
|
'orange' => '#f8c56a',
|
|
'green' => '#6cf344',
|
|
'purple' => '#ae6bdb',
|
|
'lime' => '#88ecc1',
|
|
'pink' => '#ea79d8',
|
|
'cyan' => '#8ae5ff',
|
|
'red' => '#f35869',
|
|
'blue' => '#6aa6eb',
|
|
],
|
|
'ultralight' => [
|
|
'black' => '#574d57',
|
|
'white' => '#ffffff',
|
|
'grey' => '#d6cfd6',
|
|
'yellow' => '#f5f4cf',
|
|
'orange' => '#f3d6a3',
|
|
'green' => '#baf3a8',
|
|
'purple' => '#d2bae2',
|
|
'lime' => '#b6e9d3',
|
|
'pink' => '#e6b9de',
|
|
'cyan' => '#c2e8f3',
|
|
'red' => '#ecb0b7',
|
|
'blue' => '#bbd4f0',
|
|
],
|
|
];
|
|
|
|
function uuid(): string {
|
|
$data = random_bytes(16);
|
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
|
}
|
|
|
|
function kys(mixed $arg): void {
|
|
if (!DEBUG_MODE) return;
|
|
if (gettype($arg) == 'boolean') $arg = $arg === true ? 'true' : 'false';
|
|
if (gettype($arg) == 'array' || gettype($arg) == 'object') {
|
|
foreach ($arg as $a) { if (gettype($a) == 'boolean') $a = $a === true ? 'true' : 'false'; }
|
|
}
|
|
echo '<style>html { color: #fcfcfc; background-color: #232023; } body { margin: 0; }</style>';
|
|
echo '<header style="padding: 10px 0; display: flex; justify-content: space-evenly; background-color: #550f75; margin-bottom: 20px; position: sticky; top: 0;"><div><b>K</b>ILL</div> <div><b>Y</b>OUR</div> <div><b>S</b>ELF</div></header>';
|
|
echo '<pre>';
|
|
print_r($arg);
|
|
echo '<pre>';
|
|
die();
|
|
}
|
|
|
|
function ffs(): void {
|
|
if (!DEBUG_MODE) return;
|
|
echo '<style>html { color: #fcfcfc; background-color: #232023; } body { margin: 0; }</style>';
|
|
echo '<header style="padding: 10px 0; display: flex; justify-content: space-evenly; background-color: #b61729; margin-bottom: 20px; position: sticky; top: 0;"><div><b>F</b>OR</div> <div><b>F</b>UCKS</div> <div><b>S</b>AKE</div></header>';
|
|
|
|
$stack = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 50);
|
|
$st = [];
|
|
unset($stack[0]);
|
|
$i = 0;
|
|
|
|
echo '<pre>';
|
|
print_r($stack);
|
|
echo '<pre>';
|
|
foreach ($stack as $s) {
|
|
if (isset($s['file'])) $st[$i]['file'] = $s['file'].(isset($s['line']) ? ':'.$s['line'] : '');
|
|
else $st[$i]['file'] = '';
|
|
if (isset($s['function'])) $st[$i]['func'] = (isset($s['class']) ? $s['class'].(isset($s['type']) ? $s['type'] : '::') : '').$s['function'];
|
|
else $st[$i]['func'] = '';
|
|
if (isset($s['object'])) $st[$i]['objs'] = $s['object'];
|
|
else $st[$i]['objs'] = new \stdClass;
|
|
if (isset($s['args'])) $st[$i]['args'] = $s['args'];
|
|
else $st[$i]['args'] = [];
|
|
$i++;
|
|
}
|
|
unset($stack[$i]);
|
|
|
|
foreach ($st as $s) {
|
|
echo '<div>';
|
|
echo '<b>ファイル:</b>';
|
|
echo $s['file'].'<br />';
|
|
echo '<b>関数:</b>';
|
|
echo $s['func'].'<br />';
|
|
echo '<b>オブジェクト:</b>';
|
|
echo print_r($s['objs']).'<br />';
|
|
echo '<b>その他:</b>';
|
|
echo print_r($s['args']).'<br />';
|
|
echo '</div>';
|
|
}
|
|
die();
|
|
}
|
|
|
|
function base58btc_encode(string $bin): string {
|
|
$a = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
$base = 58;
|
|
$num = \gmp_import($bin, 1, GMP_LSW_FIRST | GMP_NATIVE_ENDIAN);
|
|
$res = '';
|
|
|
|
if (\gmp_cmp($num, 0) == 0) return '1';
|
|
|
|
while (\gmp_cmp($num, 0) > 0) {
|
|
$mod = \gmp_intval(\gmp_mod($num, $base));
|
|
$res = $a[$mod].$res;
|
|
$num = \gmp_div_q($num, $base);
|
|
}
|
|
|
|
$bytes = str_split($bin);
|
|
foreach ($bytes as $byte) {
|
|
if (ord($byte) === 0) {
|
|
$res = '1'.$res;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $res;
|
|
}
|
|
|
|
function logger(LogType $section, mixed $arg): void {
|
|
if (!LOGGING_ENABLED) return;
|
|
$success = false;
|
|
$logfile = ROOT.'/log/';
|
|
|
|
switch ($section) {
|
|
case LogType::ActivityPub:
|
|
$logfile .= 'ap_log.text';
|
|
$success = true;
|
|
break;
|
|
case LogType::Auth:
|
|
$logfile .= 'auth_log.text';
|
|
$success = true;
|
|
break;
|
|
case LogType::Mailer:
|
|
$logfile .= 'mail_log.text';
|
|
$success = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ($success) file_put_contents($logfile, $arg."\n", FILE_APPEND);
|
|
}
|
|
|
|
function to_money($amount, $lang) {
|
|
$amount = floatval($amount);
|
|
|
|
switch (strtolower($lang)) {
|
|
case 'ja':
|
|
if ($amount >= 100000000) {
|
|
$oku = $amount / 100000000;
|
|
return $oku.'億円';
|
|
} else if ($amount >= 10000) {
|
|
$man = $amount / 10000;
|
|
return $man.'万円';
|
|
}
|
|
return number_format($amount, 0).'円';
|
|
case 'en':
|
|
if ($amount >= 1000000000) {
|
|
$billion = $amount / 1000000000;
|
|
return '¥ '.$billion.' billion';
|
|
} else if ($amount >= 1000000) {
|
|
$million = $amount / 1000000;
|
|
return '¥ '.$million.' million';
|
|
} else if ($amount >= 1000) {
|
|
$thousand = $amount / 1000;
|
|
return '¥ '.$thousand.' thousand';
|
|
}
|
|
return '¥ '.number_format($amount, 0);
|
|
default:
|
|
return '¥ '.number_format($amount, 0);
|
|
}
|
|
}
|
|
|
|
function randstr(): string {
|
|
$len = random_int(1, 20);
|
|
return bin2hex(random_bytes($len));
|
|
}
|
|
|
|
function assert_exists(mixed $assertion, Throwable|string|null $description = null): bool {
|
|
if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
|
|
if (isset($assertion)) return true;
|
|
assert(false, $description ?? 'Assertion failed: value is not null');
|
|
return false;
|
|
}
|
|
|
|
function assert_null(mixed $assertion, Throwable|string|null $description = null): bool {
|
|
if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
|
|
if (is_null($assertion)) return true;
|
|
assert(false, $description ?? 'Assertion failed: value is not null');
|
|
return false;
|
|
}
|
|
|
|
function assert_not_null(mixed $assertion, Throwable|string|null $description = null): bool {
|
|
if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
|
|
if (!is_null($assertion)) return true;
|
|
assert(false, $description ?? 'Assertion failed: value is null');
|
|
return false;
|
|
}
|
|
|
|
function assert_unless_success(Result $assertion, Throwable|string|null $description = null): bool {
|
|
if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
|
|
if ($assertion->isSuccess) return true;
|
|
assert(false, $description ?? $assertion->message ?? 'Assertion failed: Result is not successful');
|
|
return false;
|
|
}
|
|
|
|
function getcookie(string $name): string|null {
|
|
return $_COOKIE[$name] ?? null;
|
|
}
|
|
|
|
function namecolor(\stdClass $userData): string {
|
|
$ban = "#888888";
|
|
$male = "#97ACEF";
|
|
$female = "#F185C9";
|
|
$ungender = "#7C60B0";
|
|
|
|
$gender = 'color: '.($userData->gender === Gender::MALE ? $male : ($userData->gender === Gender::FEMALE ? $female : $ungender)).';';
|
|
$style = $userData->namecolor ?: ($userData->role !== Roles::BANNED ? $gender : 'color: '.$ban.';');
|
|
|
|
$showname = $userData->displayname ?: $userData->username;
|
|
|
|
$color = "<span style=\"{$style}\">{$showname}</span>";
|
|
if ($userData->role & (Roles::ADMIN | Roles::STAFF))
|
|
$color .= "<span style=\"font-size: x-small; background: #10c074; border: 1px solid #fcfcfc; border-radius: 10px; padding: 0 0.5em;\">✓</span>";
|
|
$suffix = $userData->gender === Gender::MALE ? 'くん' : ($userData->gender === Gender::FEMALE ? 'ちゃん' : 'さん');
|
|
|
|
return $color.$suffix;
|
|
}
|
|
|
|
function make_csrf_token(?bool $force = false): string {
|
|
if (null !== getcookie('csrf_token') && !$force) return getcookie('csrf_token');
|
|
$token = bin2hex(random_bytes(32));
|
|
setcookie('csrf_token', $token, [
|
|
'expires' => time() + 300, // 5分
|
|
'path' => '/',
|
|
'domain' => $_SERVER['SERVER_NAME'],
|
|
'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
|
|
'httponly' => true,
|
|
'samesite' => 'Strict'
|
|
]);
|
|
return $token;
|
|
}
|
|
|
|
function verify_csrf_token(string $token): bool {
|
|
return null !== getcookie('csrf_token') && hash_equals(getcookie('csrf_token'), $token);
|
|
}
|
|
|
|
function count_special_chars(string $str): int {
|
|
$count = 0;
|
|
$len = strlen($str);
|
|
|
|
for ($i = 0; $i < $len; ++$i) {
|
|
if (str_contains('!', $str[$i])) ++$count;
|
|
if (str_contains('"', $str[$i])) ++$count;
|
|
if (str_contains('#', $str[$i])) ++$count;
|
|
if (str_contains('$', $str[$i])) ++$count;
|
|
if (str_contains('%', $str[$i])) ++$count;
|
|
if (str_contains('&', $str[$i])) ++$count;
|
|
if (str_contains('\'', $str[$i])) ++$count;
|
|
if (str_contains('(', $str[$i])) ++$count;
|
|
if (str_contains(')', $str[$i])) ++$count;
|
|
if (str_contains('-', $str[$i])) ++$count;
|
|
if (str_contains('=', $str[$i])) ++$count;
|
|
if (str_contains('^', $str[$i])) ++$count;
|
|
if (str_contains('~', $str[$i])) ++$count;
|
|
if (str_contains('\\', $str[$i])) ++$count;
|
|
if (str_contains('|', $str[$i])) ++$count;
|
|
if (str_contains('[', $str[$i])) ++$count;
|
|
if (str_contains(']', $str[$i])) ++$count;
|
|
if (str_contains(':', $str[$i])) ++$count;
|
|
if (str_contains('@', $str[$i])) ++$count;
|
|
if (str_contains('`', $str[$i])) ++$count;
|
|
if (str_contains('*', $str[$i])) ++$count;
|
|
if (str_contains('{', $str[$i])) ++$count;
|
|
if (str_contains('}', $str[$i])) ++$count;
|
|
if (str_contains(';', $str[$i])) ++$count;
|
|
if (str_contains('+', $str[$i])) ++$count;
|
|
if (str_contains(',', $str[$i])) ++$count;
|
|
if (str_contains('<', $str[$i])) ++$count;
|
|
if (str_contains('.', $str[$i])) ++$count;
|
|
if (str_contains('>', $str[$i])) ++$count;
|
|
if (str_contains('/', $str[$i])) ++$count;
|
|
if (str_contains('?', $str[$i])) ++$count;
|
|
if (str_contains('_', $str[$i])) ++$count;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
function countmatch(string $str): bool {
|
|
$len = strlen($str);
|
|
|
|
$numUpper = preg_match_all('/[A-Z]/', $str) ?? 0;
|
|
$numLower = preg_match_all('/[a-z]/', $str) ?? 0;
|
|
$numDigit = preg_match_all('/[0-9]/', $str) ?? 0;
|
|
$numSymbol = count_special_chars($str);
|
|
$sum = $numUpper + $numLower + $numDigit + $numSymbol;
|
|
|
|
return $len == $sum;
|
|
}
|
|
|
|
function getImageInfo(string $url): \Std\Lib\Image {
|
|
$img = new \Std\Lib\Image($url);
|
|
return $img;
|
|
}
|
|
|
|
function align(int $val): int {
|
|
if ($val <= 0) return 1;
|
|
if ($val === 1) return 1;
|
|
|
|
$res = 0;
|
|
$lower = 1;
|
|
|
|
while ($lower * 2 <= $val) $lower *= 2;
|
|
$upper = $lower * 2;
|
|
|
|
if (($val - $lower) <= ($upper - $val)) return $lower;
|
|
return $upper;
|
|
}
|
|
|
|
function align_up(int $val): int {
|
|
if ($val <= 0) return 1;
|
|
if ($val === 1) return 1;
|
|
|
|
$res = 0;
|
|
$lower = 1;
|
|
|
|
while ($lower * 2 <= $val) $lower *= 2;
|
|
$upper = $lower * 2;
|
|
|
|
return $upper;
|
|
}
|
|
|
|
function align_down(int $val): int {
|
|
if ($val <= 0) return 1;
|
|
if ($val === 1) return 1;
|
|
|
|
$res = 0;
|
|
$lower = 1;
|
|
|
|
while ($lower * 2 <= $val) $lower *= 2;
|
|
return $lower;
|
|
}
|
|
|
|
// PHP 8.3と8.4の場合
|
|
if (!function_exists('array_last')) {
|
|
function array_last(array $array): mixed {
|
|
return $array === [] ? null : $array[array_key_last($array)];
|
|
}
|
|
} |