Initial private import

This commit is contained in:
seo
2026-06-08 02:33:05 +09:00
commit a2a89998b6
6 changed files with 1793 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
<?php
// Apache 환경 차단
if (isset($_SERVER['SERVER_SOFTWARE']) && stripos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['error' => 'Access forbidden: Apache environment is not allowed']);
exit;
}
// POST 전용 검사
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405); // Method Not Allowed
header('Content-Type: application/json');
echo json_encode(['error' => 'Method Not Allowed']);
exit;
}
// JSON 요청 검사
$content_type = $_SERVER['CONTENT_TYPE'] ?? '';
if (stripos($content_type, 'application/json') === false) {
http_response_code(415); // Unsupported Media Type
header('Content-Type: application/json');
echo json_encode(['error' => 'Unsupported Content-Type. application/json required']);
exit;
}
function get_authorization_header() {
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
return trim($_SERVER['HTTP_AUTHORIZATION']);
} elseif (function_exists('apache_request_headers')) {
foreach (apache_request_headers() as $key => $value) {
if (strcasecmp($key, 'Authorization') === 0) {
return trim($value);
}
}
}
return null;
}
$auth_header = get_authorization_header();
if (!$auth_header || !preg_match('/^Bearer\s+(\S+)$/', $auth_header, $matches)) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Unauthorized: Missing or invalid Authorization header']);
exit;
}
$allowed_password = 'XBv9z1IGqPUdtVf7DABFA16H0E154LJS';
$password = $matches[1];
if ($password !== $allowed_password) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Unauthorized: Invalid token']);
exit;
}
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Expires: 0');
$baseUrl = "https://ha.seoul.chaegeon.com/api/states";
$headers = [
"Authorization: Bearer REDACTED_HA_TOKEN",
"Content-Type: application/json"
];
// 엔드포인트 URL
$deviceUrl = "$baseUrl/device_tracker.seocaegeonyi_z_fold7";
$geoUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_geocoded_location";
$batteryStateUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_battery_state";
$batteryLevelUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_battery_level";
$watchBatteryStateUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_watch_battery_state";
$watchBatteryLevelUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_watch_battery_level";
$activityUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_detected_activity";
$connectionUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_network_type";
$fascendedUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_floors";
$fdescendedUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_floors";
$stepsUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_steps";
$distanceUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_daily_distance";
$triggerUrl = "$baseUrl/sensor.seocaegeonyi_z_fold7_last_update_trigger";
// GET 파라미터 읽기
$startParam = $_GET['startTime'] ?? null;
$endParam = $_GET['endTime'] ?? null;
// Home Assistant에서 인식 가능한 UTC 포맷
$haFormat = 'Y-m-d\TH:i:s\Z';
if ($startParam && $endParam) {
// 전달받은 값을 DateTime으로 변환 후 UTC 포맷으로 변환
$startTime = (new DateTime($startParam))->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
$endTime = (new DateTime($endParam))->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
} else {
// 기본값: 최근 24시간
$startTime = (new DateTime('-24 hours'))->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
$endTime = (new DateTime())->setTimezone(new DateTimeZone('UTC'))->format($haFormat);
}
// API URL 생성
$historyUrl = "https://ha.seoul.chaegeon.com/api/history/period/{$startTime}?filter_entity_id=device_tracker.seocaegeonyi_z_fold7&end_time={$endTime}";
// 공통 요청 함수
function getData($url, $headers) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// 데이터 불러오기
$deviceData = getData($deviceUrl, $headers);
$geoData = getData($geoUrl, $headers);
$batteryStateData = getData($batteryStateUrl, $headers);
$batteryLevelData = getData($batteryLevelUrl, $headers);
$watchBatteryStateData = getData($watchBatteryStateUrl, $headers);
$watchBatteryLevelData = getData($watchBatteryLevelUrl, $headers);
$activityData = getData($activityUrl, $headers);
$connectionData = getData($connectionUrl, $headers);
$ascendedData = getData($fascendedUrl, $headers);
$descendedData = getData($fdescendedUrl, $headers);
$stepsData = getData($stepsUrl, $headers);
$distanceData = getData($distanceUrl, $headers);
$historyData = getData($historyUrl, $headers);
$triggerData = getData($triggerUrl, $headers);
// 경로 기록
$path = [];
if (isset($historyData[0])) {
foreach ($historyData[0] as $item) {
$lat = $item['attributes']['latitude'] ?? null;
$lng = $item['attributes']['longitude'] ?? null;
$time = $item['last_changed'] ?? null;
if ($lat && $lng) {
$path[] = ['lat' => $lat, 'lng' => $lng, 'time' => $time];
}
}
}
$lang = $_GET['lang'] ?? 'en';
$activityMap = [
'ko' => [
'Stationary' => '가만히 있음',
'Walking' => '걷는 중',
'Running' => '달리는 중',
'Automotive' => '차량 이동 중',
'Cycling' => '자전거 이동 중',
'Unknown' => '-',
],
'en' => [
'Stationary' => 'Stationary',
'Walking' => 'Walking',
'Running' => 'Running',
'Automotive' => 'Automotive',
'Cycling' => 'Cycling',
'Unknown' => '-',
]
];
$aaccuracyMap = [
'ko' => [
'Low' => '낮음',
'Medium' => '중간',
'High' => '높음',
'Unknown' => '-',
],
'en' => [
'Low' => 'Low',
'Medium' => 'Medium',
'High' => 'High',
'Unknown' => '-',
]
];
$connectionMap = [
'ko' => [
'Wi-Fi' => '와이파이',
'Cellular' => '셀룰러',
'none' => '-',
'unknown' => '-',
],
'en' => [
'Wi-Fi' => 'Wi-Fi',
'Cellular' => 'Cellular',
'none' => '-',
'unknown' => '-',
]
];
$batteryStateMap = [
'ko' => [
'Charging' => '충전 중',
'Not Charging' => '충전중이 아님',
'Full' => '가득참',
'Unknown' => '-',
],
'en' => [
'Charging' => 'Charging',
'Not Charging' => 'Not Charging',
'Full' => 'Full',
'Unknown' => '-',
]
];
$triggerMap = [
'ko' => [
"Launch" => "앱 실행",
"Background Fetch" => "백그라운드 갱신",
"Push Notification" => "푸시",
"Periodic" => "주기적",
"Significant Location Change" => "중요한 위치 변화",
"Manual" => "수동",
"Signaled" => "시그널 됨",
"Siri" => "Siri 실행",
"Watch Context" => "워치 상태 전송",
"Geographic Region Entered" => "지정 지역에 들어옴",
"Geographic Region Exited" => "지정 지역을 벗어남",
],
'en' => [
"Launch" => "Launch",
"Background Fetch" => "Background Fetch",
"Push Notification" => "Push Notification",
"Periodic" => "Periodic",
"Significant Location Change" => "Significant Location Change",
"Manual" => "Manual",
"Signaled" => "Signaled",
"Siri" => "Siri",
"Watch Context" => "Watch Context",
"Geographic Region Entered" => "Geographic Region Entered",
"Geographic Region Exited" => "Geographic Region Exited",
]
];
$activityState = $activityData['state'] ?? null;
$aaccuracyState = $activityData['attributes']['Confidence'] ?? null;
$connectionState = $connectionData['state'] ?? null;
$batteryState = $batteryStateData['state'] ?? null;
$watchBatteryState = $watchBatteryStateData['state'] ?? null;
$address = null;
if (($deviceData['state'] ?? null) !== 'not_home') {
$address = $deviceData['state'];
} else {
// Locality 값 확인
$locality = $geoData['attributes']['Locality'] ?? null;
// Locality가 없거나 N/A면 Sub Administrative Area로 대체
if (empty($locality) || $locality === 'N/A') {
$locality = $geoData['attributes']['Sub Administrative Area'] ?? '';
}
// Sub Administrative Area 도 없을 경우 Name만 사용
$address = trim($locality . ' ' . ($geoData['attributes']['Name'] ?? ''));
}
// 해시 요청만 들어온 경우
if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') {
$raw = json_encode([
'latitude' => $deviceData['attributes']['latitude'] ?? null,
'longitude' => $deviceData['attributes']['longitude'] ?? null,
'accuracy' => $deviceData['attributes']['gps_accuracy'] ?? null,
'updated' => $deviceData['last_changed'] ?? null,
'trigger' => $triggerMap[$lang][$triggerData['state']] ?? $triggerData['state'] ?? null,
'address' => $address,
'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null,
'level' => $batteryLevelData['state'] ?? null,
'wstate' => $batteryStateMap[$lang][$watchBatteryState] ?? $watchBatteryState ?? null,
'wlevel' => $watchBatteryLevelData['state'] ?? null,
'ascended' => $ascendedData['state'] ?? null,
'descended' => $descendedData['state'] ?? null,
'steps' => $stepsData['state'] ?? null,
'distance' => $distanceData['state'] ?? null,
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
]);
echo json_encode(['hash' => hash('sha256', $raw)]);
exit;
}
echo json_encode([
'latitude' => $deviceData['attributes']['latitude'] ?? null,
'longitude' => $deviceData['attributes']['longitude'] ?? null,
'accuracy' => $deviceData['attributes']['gps_accuracy'] ?? null,
'updated' => $deviceData['last_changed'] ?? null,
'trigger' => $triggerMap[$lang][$triggerData['state']] ?? $triggerData['state'] ?? null,
'address' => $address,
'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null,
'level' => $batteryLevelData['state'] ?? null,
'wstate' => $batteryStateMap[$lang][$watchBatteryState] ?? $watchBatteryState ?? null,
'wlevel' => $watchBatteryLevelData['state'] ?? null,
'ascended' => $ascendedData['state'] ?? null,
'descended' => $descendedData['state'] ?? null,
'steps' => $stepsData['state'] ?? null,
'distance' => $distanceData['state'] ?? null,
'activity' => $activityMap[$lang][$activityState] ?? $activityState ?? null,
'aaccuracy' => $aaccuracyMap[$lang][$aaccuracyState] ?? $aaccuracyState ?? null,
'connection' => $connectionMap[$lang][$connectionState] ?? $connectionState ?? null,
'history' => $path ?? null,
], JSON_UNESCAPED_UNICODE);
?>