_sega 56a9e2464b FEAT Добавление монет через сайт-панель
1. Появилась возможность добавлять монеты через сайт-панель по кнопке
2. Доработаны css стили для сайт-панели
2026-01-28 14:57:47 +03:00

460 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
//Зависит от SettingsManager
[System.Serializable]
public class ServerResponse
{
public string message;
public string timestamp;
}
public class WebServer : MonoBehaviour
{
private HttpListener listener;
private bool isRunning = true;
private static byte[] css;
[Header("Auth")]
[SerializeField] private string adminPasswordFallback = "1234"; // если файла нет
[SerializeField] private string passwordFileName = "admin_password.txt"; // лежит в persistentDataPath
private string AdminPassword => AuthManager.LoadPasswordOrFallback(adminPasswordFallback, passwordFileName);
async void Start()
{
// SettingsManager.Initialize();//надо будет убирать
LoadCss();
await StartWebServerAsync();
}
async Task StartWebServerAsync()
{
listener = new HttpListener();
listener.Prefixes.Add("http://localhost:8080/");
//тест
listener.Prefixes.Add("http://fishka:8080/");
// 4. ВСЕ сетевые интерфейсы (самый важный!)
listener.Prefixes.Add("http://*:8080/");
listener.Start();
Debug.Log("Async Web сервер запущен");
while (isRunning)
{
try
{
var context = await listener.GetContextAsync();
_ = Task.Run(() => ProcessRequestAsync(context));
}
catch (Exception e)
{
Debug.LogError($"Ошибка: {e.Message}");
}
}
}
async Task ProcessRequestAsync(HttpListenerContext context)
{
try
{
string path = context.Request.Url.LocalPath;
string method = context.Request.HttpMethod;
// Debug.Log($"Запрос: {method} {path}");
// Публичные маршруты (без авторизации)
if (method == "GET" && path == "/style.css")
{
await HandleCssRequestAsync(context);
return;
}
if (method == "GET" && path == "/login")
{
SendHtmlResponse(context, HtmlBuilder.GetLoginPage());
return;
}
if (method == "POST" && path == "/api/login")
{
await HandlePostLoginAsync(context);
return;
}
// Дальше всё — только с авторизацией
if (!AuthManager.IsAuthorized(context.Request, AdminPassword))
{
// красивый редирект на /login
Redirect(context, "/login");
return;
}
// Главная страница
if (method == "GET" && path == "/")
{
await HandleGetSettingsAsync(context);
return;
}
// Рилтайм апдейт 1 настройки
if (method == "POST" && path == "/api/setting")
{
await HandlePostSingleSettingAsync(context);
return;
}
// Кнопка "Добавить монеты"
if (method == "POST" && path == "/api/coins")
{
await HandlePostCoinsAsync(context);
return;
}
// LEGACY (оставил, если вдруг где-то дергается)
if (method == "POST" && path == "/upd")
{
await HandlePostUpdateAsync(context);
return;
}
SendHtmlResponse(context, "<h1>404 Not Found</h1>", 404);
}
catch (Exception ex)
{
Debug.LogError($"Error processing request: {ex.Message}");
SendHtmlResponse(context, $"<h1>Error: {ex.Message}</h1>", 500);
}
}
async Task HandleGetSettingsAsync(HttpListenerContext context)
{
await Task.Run(() =>
{
try
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var htmlTask = Task.Run(() =>
HtmlBuilder.GetSettingsPage(SettingsManager.Container.Settings)
);
if (htmlTask.Wait(TimeSpan.FromSeconds(5)))
{
string htmlContent = htmlTask.Result;
stopwatch.Stop();
if (string.IsNullOrEmpty(htmlContent))
htmlContent = "<h1>Error: Method returned empty string</h1>";
SendHtmlResponse(context, htmlContent);
}
else
{
Debug.LogError("HTML generation TIMEOUT (5 seconds)!");
SendHtmlResponse(context, "<h1>Error: Settings generation timeout</h1>", 500);
}
}
catch (Exception ex)
{
Debug.LogError($"Error in settings html: {ex.Message}");
SendHtmlResponse(context, $"<h1>Error: {ex.Message}</h1>", 500);
}
});
}
async Task HandleCssRequestAsync(HttpListenerContext context)
{
context.Response.ContentType = "text/css; charset=utf-8";
context.Response.ContentLength64 = css.Length;
await context.Response.OutputStream.WriteAsync(css, 0, css.Length);
context.Response.OutputStream.Close();
}
private static void LoadCss()
{
TextAsset cssAsset = Resources.Load<TextAsset>("style");
if (cssAsset != null)
{
css = Encoding.UTF8.GetBytes(cssAsset.text);
Debug.Log("CSS OK");
}
else
{
Debug.LogWarning("CSS NOT OK");
css = Array.Empty<byte>();
}
}
// =========================
// AUTH
// =========================
async Task HandlePostLoginAsync(HttpListenerContext context)
{
try
{
string body = await ReadBodyAsStringAsync(context.Request);
// принимаем и form-url-encoded, и json
string pass = AuthManager.ExtractPassword(body, context.Request.ContentType);
if (string.IsNullOrEmpty(pass))
{
SendJson(context, 400, "{\"ok\":false,\"message\":\"Пароль пустой\"}");
return;
}
if (!AuthManager.VerifyPassword(pass, AdminPassword))
{
SendJson(context, 401, "{\"ok\":false,\"message\":\"Неверный пароль\"}");
return;
}
string token = AuthManager.GenerateToken(AdminPassword, daysValid: 30);
AuthManager.SetAuthCookie(context.Response, token, daysValid: 30);
SendJson(context, 200, "{\"ok\":true}");
}
catch (Exception ex)
{
Debug.LogError($"Login error: {ex.Message}");
SendJson(context, 500, "{\"ok\":false,\"message\":\"Server error\"}");
}
}
// =========================
// REALTIME SETTINGS
// =========================
[Serializable]
private class SettingUpdateDto
{
public string id;
public string value;
}
async Task HandlePostSingleSettingAsync(HttpListenerContext context)
{
try
{
string body = await ReadBodyAsStringAsync(context.Request);
// Ждем JSON: {"id":"SETTING_ID","value":"on/off/123/1.5/SomeString"}
var dto = JsonUtility.FromJson<SettingUpdateDto>(body);
if (dto == null || string.IsNullOrEmpty(dto.id))
{
SendJson(context, 400, "{\"ok\":false,\"message\":\"Bad payload\"}");
return;
}
var (ok, log) = SettingsApplier.ApplySettings(
new Dictionary<string, string> { { dto.id, dto.value ?? "" } },
assumeMissingBoolOff: false // важно! иначе вырубишь все остальные bool
);
if (!ok)
{
SendJson(context, 400, "{\"ok\":false,\"message\":\"Apply failed\"}");
return;
}
// применяем на главном потоке
UnityMainThreadDispatcher.Instance.Enqueue(() => SettingsManager.OnSettingsChanged?.Invoke());
UnityMainThreadDispatcher.Instance.Enqueue(() => GameManager.instance.AdminPanel
.GetComponent<SettingsScreen>().RefreshViews());
SendJson(context, 200, "{\"ok\":true}");
}
catch (Exception ex)
{
Debug.LogError($"POST /api/setting error: {ex.Message}\n{ex.StackTrace}");
SendJson(context, 500, "{\"ok\":false,\"message\":\"Server error\"}");
}
}
// =========================
// COINS HANDLER
// =========================
[Serializable]
private class CoinsDto
{
public int amount;
}
async Task HandlePostCoinsAsync(HttpListenerContext context)
{
try
{
string body = await ReadBodyAsStringAsync(context.Request);
var dto = JsonUtility.FromJson<CoinsDto>(body);
int amount = dto?.amount ?? 0;
if (amount <= 0)
{
SendJson(context, 400, "{\"ok\":false,\"message\":\"amount must be > 0\"}");
return;
}
// ЗАГОТОВКА: сюда ты впишешь реальную логику
UnityMainThreadDispatcher.Instance.Enqueue(() => OnAddCoins(amount));
SendJson(context, 200, "{\"ok\":true}");
}
catch (Exception ex)
{
Debug.LogError($"POST /api/coins error: {ex.Message}\n{ex.StackTrace}");
SendJson(context, 500, "{\"ok\":false,\"message\":\"Server error\"}");
}
}
private void OnAddCoins(int amount) => ComWrapper.AddCreditNoCounters(amount);
// =========================
// LEGACY /upd (как было)
// =========================
async Task HandlePostUpdateAsync(HttpListenerContext context)
{
try
{
Encoding encoding = Encoding.UTF8;
string contentType = context.Request.ContentType?.ToLower() ?? "";
if (contentType.Contains("charset="))
{
try
{
string charset = contentType.Split("charset=")[1].Trim().Split(';')[0];
encoding = Encoding.GetEncoding(charset);
}
catch
{
encoding = Encoding.UTF8;
}
}
string requestBody;
using (var reader = new StreamReader(context.Request.InputStream, encoding))
requestBody = await reader.ReadToEndAsync();
var formDict = new Dictionary<string, string>();
try
{
var formData = System.Web.HttpUtility.ParseQueryString(requestBody, encoding);
foreach (string key in formData.AllKeys)
{
if (key != null)
formDict[key] = formData[key] ?? "";
}
}
catch (Exception parseEx)
{
Debug.LogWarning($"ParseQueryString ошибка: {parseEx.Message}");
}
SettingsApplier.ApplySettings(formDict, assumeMissingBoolOff: true);
UnityMainThreadDispatcher.Instance.Enqueue(() => SettingsManager.OnSettingsChanged?.Invoke());
UnityMainThreadDispatcher.Instance.Enqueue(() => GameManager.instance.AdminPanel.GetComponent<SettingsScreen>().RefreshViews());
SendHtmlResponse(context, "<html><body><h1>OK</h1><a href='/'>Back</a></body></html>");
}
catch (Exception ex)
{
Debug.LogError($"Ошибка обработки POST /upd: {ex.Message}\n{ex.StackTrace}");
SendHtmlResponse(context, $"<h1 style='color:red;'>Ошибка: {ex.Message}</h1>", 500);
}
}
// =========================
// Helpers
// =========================
private async Task<string> ReadBodyAsStringAsync(HttpListenerRequest request)
{
Encoding encoding = Encoding.UTF8;
// иногда Unity/браузер шлет json без charset — ок
if (!string.IsNullOrEmpty(request.ContentType))
{
var ct = request.ContentType.ToLower();
if (ct.Contains("charset="))
{
try
{
string charset = ct.Split("charset=")[1].Trim().Split(';')[0];
encoding = Encoding.GetEncoding(charset);
}
catch { encoding = Encoding.UTF8; }
}
}
using var reader = new StreamReader(request.InputStream, encoding);
return await reader.ReadToEndAsync();
}
void SendHtmlResponse(HttpListenerContext context, string html, int statusCode = 200)
{
try
{
byte[] buffer = Encoding.UTF8.GetBytes(html);
context.Response.StatusCode = statusCode;
context.Response.ContentType = "text/html; charset=utf-8";
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.OutputStream.Close();
}
catch (Exception ex)
{
Debug.LogError($"Error sending response: {ex.Message}");
}
}
private void SendJson(HttpListenerContext context, int statusCode, string json)
{
try
{
byte[] buffer = Encoding.UTF8.GetBytes(json);
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.OutputStream.Close();
}
catch (Exception ex)
{
Debug.LogError($"Error sending json: {ex.Message}");
}
}
private void Redirect(HttpListenerContext context, string location, int statusCode = 302)
{
context.Response.StatusCode = statusCode;
context.Response.Headers["Location"] = location;
context.Response.OutputStream.Close();
}
void OnDestroy()
{
isRunning = false;
listener?.Stop();
listener?.Close();
}
private void OnApplicationQuit()
{
isRunning = false;
listener?.Stop();
listener?.Close();
}
}