= 2 && (($value[0] === '"' && substr($value, -1) === '"') || ($value[0] === "'" && substr($value, -1) === "'")) ) { $value = substr($value, 1, -1); } $env[$name] = $value; } break; } } return $env[$key] ?? $fallback; } function webImFirstEnv(array $keys, string $fallback = ''): string { foreach ($keys as $key) { $value = webImEnv($key); if ($value !== '') { return $value; } } return $fallback; } function webImMysqlDsn(string $host, string $port, string $database): string { return sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $host, $port, $database); } define('IM_ADMIN_URL', webImEnv('JC_WEB_IM_ADMIN_URL', 'http://127.0.0.1:18080')); define('IM_ADMIN_SECRET', webImFirstEnv(['JC_WEB_IM_ADMIN_SECRET', 'SecretKey'], '123456')); define('IM_DB_DSN', webImMysqlDsn( webImEnv('JC_WEB_IM_DB_HOST', webImEnv('DB_HOST', '127.0.0.1')), webImEnv('JC_WEB_IM_DB_PORT', webImEnv('DB_PORT', '3306')), webImEnv('JC_WEB_IM_DB_DATABASE', webImEnv('DB_DATABASE', 'wfchat')) )); define('IM_DB_USER', webImEnv('JC_WEB_IM_DB_USER', webImEnv('DB_USERNAME', 'winchen'))); define('IM_DB_PASSWORD', webImEnv('JC_WEB_IM_DB_PASSWORD', webImEnv('DB_PASSWORD', 'winchen@0904'))); define('IM_APP_DB_DSN', webImMysqlDsn( webImFirstEnv(['JC_WEB_IM_APP_DB_HOST', 'DB_HOST_2', 'DB_HOST2_2'], webImEnv('DB_HOST', '127.0.0.1')), webImFirstEnv(['JC_WEB_IM_APP_DB_PORT', 'DB_PORT_2', 'DB_PORT2_2'], webImEnv('DB_PORT', '3306')), webImFirstEnv(['JC_WEB_IM_APP_DB_DATABASE', 'DB_DATABASE_2', 'DB_DATABASE2_2'], webImEnv('DB_DATABASE', 'appdata')) )); define('IM_APP_DB_USER', webImFirstEnv(['JC_WEB_IM_APP_DB_USER', 'DB_USERNAME_2', 'DB_USERNAME2_2'], webImEnv('DB_USERNAME', 'winchen'))); define('IM_APP_DB_PASSWORD', webImFirstEnv(['JC_WEB_IM_APP_DB_PASSWORD', 'DB_PASSWORD_2', 'DB_PASSWORD2_2'], webImEnv('DB_PASSWORD', 'winchen@0904'))); define('IM_DB_TIMEZONE', webImEnv('JC_WEB_IM_DB_TIMEZONE', 'Asia/Bangkok')); function respond(int $code, string $message, array $result = []): void { echo json_encode([ 'code' => $code, 'message' => $message, 'result' => $result, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function readJsonPayload(): array { $payload = json_decode(file_get_contents('php://input') ?: '[]', true); return is_array($payload) ? $payload : []; } function imAdminPost(string $path, array $payload): array { $nonce = (string)random_int(1, 1000000); $timestamp = (string)((int)floor(microtime(true) * 1000)); $sign = sha1($nonce . '|' . IM_ADMIN_SECRET . '|' . $timestamp); $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $curl = curl_init(IM_ADMIN_URL . $path); if ($curl === false) { throw new RuntimeException('IM admin client init failed'); } curl_setopt_array($curl, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0, CURLOPT_CONNECTTIMEOUT_MS => 1000, CURLOPT_TIMEOUT_MS => 4000, CURLOPT_FRESH_CONNECT => true, CURLOPT_FORBID_REUSE => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json; charset=utf-8', 'Connection: close', 'nonce: ' . $nonce, 'timestamp: ' . $timestamp, 'sign: ' . $sign, ], ]); $responseBody = curl_exec($curl); $curlError = curl_error($curl); curl_close($curl); if ($responseBody === false || $responseBody === '') { throw new RuntimeException($curlError ?: 'IM admin request failed'); } $data = json_decode($responseBody, true); if (!is_array($data)) { throw new RuntimeException('Invalid IM admin response'); } return $data; } function requireImSuccess(array $response, string $fallbackMessage): array { if (($response['code'] ?? -1) !== 0) { throw new RuntimeException((string)($response['msg'] ?? $response['message'] ?? $fallbackMessage)); } return is_array($response['result'] ?? null) ? $response['result'] : []; } function imDb(): PDO { static $pdo = null; if ($pdo instanceof PDO) { return $pdo; } $pdo = new PDO(IM_DB_DSN, IM_DB_USER, IM_DB_PASSWORD, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); return $pdo; } function imAppDb(): PDO { static $pdo = null; if ($pdo instanceof PDO) { return $pdo; } $pdo = new PDO(IM_APP_DB_DSN, IM_APP_DB_USER, IM_APP_DB_PASSWORD, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); return $pdo; } function ensureWebImAppTables(): void { static $ensured = false; if ($ensured) { return; } $ensured = true; $db = imAppDb(); $db->exec( "create table if not exists `t_web_groups` ( `id` bigint unsigned not null auto_increment, `group_id` varchar(64) not null, `category` varchar(20) not null default 'group', `name` varchar(128) not null, `portrait` varchar(1024) not null default '', `owner_uid` varchar(64) not null, `join_mode` varchar(20) not null default 'open', `announcement` text null, `native_group_id` varchar(64) not null default '', `native_status` varchar(32) not null default 'pending', `native_error` varchar(255) not null default '', `created_by` varchar(64) not null, `created_at_ms` bigint not null, `updated_at_ms` bigint not null, `deleted` tinyint not null default 0, primary key (`id`), unique key `uk_t_web_groups_group_id` (`group_id`), key `idx_t_web_groups_owner` (`owner_uid`), key `idx_t_web_groups_category` (`category`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); try { $db->exec("alter table `t_web_groups` add column `welcome_text` text null after `announcement`"); } catch (Throwable) { } $db->exec( "create table if not exists `t_web_group_members` ( `id` bigint unsigned not null auto_increment, `group_id` varchar(64) not null, `user_id` varchar(64) not null, `role` varchar(20) not null default 'member', `alias` varchar(128) not null default '', `joined_at_ms` bigint not null, `updated_at_ms` bigint not null, `status` tinyint not null default 0, primary key (`id`), unique key `uk_t_web_group_members_gid_uid` (`group_id`, `user_id`), key `idx_t_web_group_members_user` (`user_id`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); $db->exec( "create table if not exists `t_web_group_messages` ( `id` bigint unsigned not null auto_increment, `message_uid` varchar(64) not null, `group_id` varchar(64) not null, `sender_uid` varchar(64) not null, `text` text not null, `created_at_ms` bigint not null, `edited_at_ms` bigint not null default 0, `recalled` tinyint not null default 0, primary key (`id`), unique key `uk_t_web_group_messages_uid` (`message_uid`), key `idx_t_web_group_messages_group_time` (`group_id`, `created_at_ms`), key `idx_t_web_group_messages_sender` (`sender_uid`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); $db->exec( "create table if not exists `t_web_message_attachments` ( `id` bigint unsigned not null auto_increment, `message_uid` varchar(64) not null, `conversation_type` varchar(16) not null default 'single', `target_id` varchar(128) not null default '', `sender_uid` varchar(64) not null default '', `attachment_type` varchar(16) not null, `url` varchar(1024) not null, `name` varchar(255) not null default '', `mime` varchar(128) not null default '', `size_bytes` bigint not null default 0, `created_at_ms` bigint not null, primary key (`id`), unique key `uk_t_web_message_attachments_uid` (`message_uid`), key `idx_t_web_message_attachments_target` (`conversation_type`, `target_id`, `created_at_ms`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); $db->exec( "create table if not exists `t_web_group_reads` ( `id` bigint unsigned not null auto_increment, `group_id` varchar(64) not null, `user_id` varchar(64) not null, `read_at_ms` bigint not null default 0, `updated_at_ms` bigint not null, primary key (`id`), unique key `uk_t_web_group_reads_gid_uid` (`group_id`, `user_id`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); $db->exec( "create table if not exists `t_web_group_mute_rules` ( `id` bigint unsigned not null auto_increment, `group_id` varchar(64) not null, `mode` varchar(32) not null default 'off', `schedule_type` varchar(16) not null default 'once', `starts_at_ms` bigint not null default 0, `ends_at_ms` bigint not null default 0, `daily_start` varchar(8) not null default '', `daily_end` varchar(8) not null default '', `enabled` tinyint not null default 0, `created_by` varchar(64) not null default '', `updated_at_ms` bigint not null, primary key (`id`), unique key `uk_t_web_group_mute_rules_group` (`group_id`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); $db->exec( "create table if not exists `t_web_broadcast_logs` ( `id` bigint unsigned not null auto_increment, `sender_uid` varchar(64) not null, `target_count` int not null default 0, `success_count` int not null default 0, `failed_count` int not null default 0, `text` text not null, `target_json` text null, `result_json` text null, `created_at_ms` bigint not null, primary key (`id`), key `idx_t_web_broadcast_logs_sender_time` (`sender_uid`, `created_at_ms`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); $db->exec( "create table if not exists `t_web_user_remarks` ( `id` bigint unsigned not null auto_increment, `owner_uid` varchar(64) not null, `target_uid` varchar(64) not null, `alias` varchar(128) not null default '', `created_at_ms` bigint not null, `updated_at_ms` bigint not null, primary key (`id`), unique key `uk_t_web_user_remarks_owner_target` (`owner_uid`, `target_uid`), key `idx_t_web_user_remarks_target` (`target_uid`) ) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_unicode_ci" ); } function webImPlaceholders(array $values): string { return implode(',', array_fill(0, count($values), '?')); } function fetchWebUserRemarkMap(PDO $db, string $ownerId, array $targetIds): array { $targetIds = array_values(array_unique(array_filter(array_map(static function ($targetId): string { return trim((string)$targetId); }, $targetIds)))); if ($ownerId === '' || !$targetIds) { return []; } $statement = $db->prepare( 'select `target_uid`, `alias` from `t_web_user_remarks`' . ' where `owner_uid` = ? and `target_uid` in (' . webImPlaceholders($targetIds) . ')' ); $statement->execute(array_merge([$ownerId], $targetIds)); $remarks = []; foreach ($statement->fetchAll() as $row) { $remarks[(string)$row['target_uid']] = trim((string)($row['alias'] ?? '')); } return $remarks; } function fetchImFriendAliasMap(PDO $db, string $ownerId, array $friendIds): array { $friendIds = array_values(array_unique(array_filter(array_map(static function ($friendId): string { return trim((string)$friendId); }, $friendIds)))); if ($ownerId === '' || !$friendIds) { return []; } $aliases = []; foreach (array_chunk($friendIds, 500) as $chunk) { $statement = $db->prepare( 'select `_friend_uid`, `_alias` from `t_friend`' . ' where `_uid` = ? and `_friend_uid` in (' . webImPlaceholders($chunk) . ') and `_state` = 0' ); $statement->execute(array_merge([$ownerId], $chunk)); foreach ($statement->fetchAll() as $row) { $aliases[(string)$row['_friend_uid']] = trim((string)($row['_alias'] ?? '')); } } return $aliases; } function saveWebUserRemark(PDO $db, string $ownerId, string $targetId, string $alias): void { if ($ownerId === '' || $targetId === '') { return; } if ($alias === '') { $delete = $db->prepare('delete from `t_web_user_remarks` where `owner_uid` = ? and `target_uid` = ?'); $delete->execute([$ownerId, $targetId]); return; } $now = webImNowMs(); $statement = $db->prepare( 'insert into `t_web_user_remarks` (`owner_uid`, `target_uid`, `alias`, `created_at_ms`, `updated_at_ms`)' . ' values (?, ?, ?, ?, ?)' . ' on duplicate key update `alias` = values(`alias`), `updated_at_ms` = values(`updated_at_ms`)' ); $statement->execute([$ownerId, $targetId, $alias, $now, $now]); } function webImNowMs(): int { return (int)floor(microtime(true) * 1000); } function webImMessageUid(): string { $timestampPart = (webImNowMs() - 1514736000000) << 21; return (string)($timestampPart | random_int(1, 0x1FFFFF)); } function javaStringHashCode(string $value): int { $hash = 0; $length = strlen($value); for ($i = 0; $i < $length; $i++) { $hash = (31 * $hash + ord($value[$i])) & 0xFFFFFFFF; if ($hash >= 0x80000000) { $hash -= 0x100000000; } } return $hash; } function imUserMessagesTable(string $userId): string { return 't_user_messages_' . (abs(javaStringHashCode($userId)) % 128); } function imMessageTable(string $messageId): string { $timestamp = ((int)$messageId >> 21) + 1514736000000; $date = (new DateTimeImmutable('@' . (int)floor($timestamp / 1000))) ->setTimezone(new DateTimeZone(IM_DB_TIMEZONE)); $year = (int)$date->format('Y'); $month = (int)$date->format('n') - 1; return 't_messages_' . (($year % 3) * 12 + $month); }