Files
LittleBeast/util.php

473 lines
15 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;
}