C#:
using Oxide.Core;
using Oxide.Core.Plugins;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("ImageAPI", "15FPS", "1.1.0")]
[Description("Автоматическая загрузка и автообновление изображений / Automatic image download and auto-update")]
public class ImageAPI : RustPlugin
{
private ImagesCache _imagesCache;
private void OnServerInitialized()
{
_imagesCache = new GameObject("ImagesCache").AddComponent<ImagesCache>();
UnityEngine.Object.DontDestroyOnLoad(_imagesCache);
_imagesCache.LoadAllFromData();
_imagesCache.StartWatching();
DownloadAllImagesFromPage(SourceUrl);
Puts("[ImageAPI] Инициализирован. Автообновление изображений включено. / Initialized. Auto image update enabled.");
}
private void Unload()
{
if (_imagesCache != null)
{
_imagesCache.StopWatching();
UnityEngine.Object.Destroy(_imagesCache.gameObject);
}
}
#region API
[HookMethod("AddImage")]
public void AddImage(string name, string path) => _imagesCache.Add(name, path);
[HookMethod("GetImage")]
public string GetImage(string name) => _imagesCache.Get(name);
[HookMethod("RemoveImage")]
public void RemoveImage(string name) => _imagesCache.Remove(name);
#endregion
public class ImagesCache : MonoBehaviour
{
private Dictionary<string, string> _images = new Dictionary<string, string>();
private FileSystemWatcher _watcher;
private readonly string _basePath = Path.Combine(Interface.Oxide.DataDirectory, "15FPS", "ImageAPI");
public void Add(string name, string url)
{
if (_images.ContainsKey(name))
return;
using (var www = new WWW(url))
{
if (!string.IsNullOrEmpty(www.error))
{
print($"Image loading fail! Error: {www.error} / Ошибка загрузки изображения: {www.error}");
return;
}
if (www.bytes == null || www.bytes.Length == 0)
{
Interface.Oxide.LogError($"Failed to add image for {name}. File address possibly invalid / Не удалось добавить изображение {name}, возможно неправильный путь\n {url}");
return;
}
_images[name] = FileStorage.server.Store(www.bytes, FileStorage.Type.png, CommunityEntity.ServerInstance.net.ID).ToString();
}
}
public string Get(string name)
{
if (_images.TryGetValue(name, out var id))
return id;
return string.Empty;
}
public void Remove(string name)
{
if (_images.Remove(name))
Interface.Oxide.LogInfo($"[ImagesCache] Удалено изображение: {name} / Removed image: {name}");
}
public void LoadAllFromData()
{
if (!Directory.Exists(_basePath))
Directory.CreateDirectory(_basePath);
foreach (string file in Directory.GetFiles(_basePath, "*.*", SearchOption.AllDirectories))
{
if (!IsImageFile(file)) continue;
string relativePath = file.Substring(_basePath.Length + 1).Replace("\\", "/");
Add(relativePath, file);
}
Interface.Oxide.LogInfo($"[ImagesCache] Автозагрузка завершена. Загружено {_images.Count} изображений. / Auto-loading completed. {_images.Count} images loaded.");
}
public void StartWatching()
{
_watcher = new FileSystemWatcher(_basePath)
{
IncludeSubdirectories = true,
EnableRaisingEvents = true,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size
};
_watcher.Created += OnFileChanged;
_watcher.Changed += OnFileChanged;
_watcher.Deleted += OnFileDeleted;
_watcher.Renamed += OnFileRenamed;
Interface.Oxide.LogInfo("[ImagesCache] FileSystemWatcher запущен для автообновления изображений. / FileSystemWatcher started for auto image updates.");
}
public void StopWatching()
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = false;
_watcher.Dispose();
_watcher = null;
}
}
private void OnFileChanged(object sender, FileSystemEventArgs e)
{
if (!IsImageFile(e.FullPath)) return;
string relative = e.FullPath.Substring(_basePath.Length + 1).Replace("\\", "/");
Interface.Oxide.NextTick(() =>
{
Add(relative, e.FullPath);
Interface.Oxide.LogInfo($"[ImagesCache] Обновлено изображение: {relative} / Image updated: {relative}");
});
}
private void OnFileDeleted(object sender, FileSystemEventArgs e)
{
string relative = e.FullPath.Substring(_basePath.Length + 1).Replace("\\", "/");
_images.Remove(relative);
Interface.Oxide.LogInfo($"[ImagesCache] Удалено изображение: {relative} / Image removed: {relative}");
}
private void OnFileRenamed(object sender, RenamedEventArgs e)
{
string oldRel = e.OldFullPath.Substring(_basePath.Length + 1).Replace("\\", "/");
string newRel = e.FullPath.Substring(_basePath.Length + 1).Replace("\\", "/");
_images.Remove(oldRel);
if (IsImageFile(e.FullPath))
Add(newRel, e.FullPath);
}
private static bool IsImageFile(string path)
{
string ext = Path.GetExtension(path)?.ToLower();
return ext == ".png" || ext == ".jpg" || ext == ".jpeg";
}
}
private const string SourceUrl = "https://15fps.top/gallery/";
private static readonly string SavePath =
Path.Combine(Interface.Oxide.DataDirectory, "15FPS", "ImageAPI/image");
private void DownloadAllImagesFromPage(string pageUrl)
{
try
{
if (!Directory.Exists(SavePath))
Directory.CreateDirectory(SavePath);
using (WebClient wc = new WebClient())
{
Puts($"[AutoImageDownloader] Загружаю HTML: {pageUrl} / Loading HTML: {pageUrl}");
string html = wc.DownloadString(pageUrl);
var matches = Regex.Matches(
html,
@"src=['""]([^'""]+\.(png|jpg|jpeg|gif))",
RegexOptions.IgnoreCase
);
if (matches.Count == 0)
{
Puts("[AutoImageDownloader] На странице нет изображений. / No images found on the page.");
return;
}
Uri baseUri = new Uri(pageUrl);
int index = 0;
int downloaded = 0;
int skipped = 0;
foreach (Match m in matches)
{
string imgUrl = m.Groups[1].Value;
if (!imgUrl.StartsWith("http"))
imgUrl = new Uri(baseUri, imgUrl).ToString();
string fileName = Path.GetFileName(imgUrl);
if (string.IsNullOrEmpty(fileName))
fileName = $"image_{index}.png";
string filePath = Path.Combine(SavePath, fileName);
if (File.Exists(filePath))
{
Puts($"[AutoImageDownloader] Уже существует → {fileName}, пропускаю / Already exists → {fileName}, skipping");
skipped++;
}
else
{
try
{
Puts($"[AutoImageDownloader] Скачиваю: {imgUrl} / Downloading: {imgUrl}");
wc.DownloadFile(imgUrl, filePath);
downloaded++;
}
catch (Exception ex)
{
Puts($"[AutoImageDownloader] Ошибка загрузки {imgUrl}: {ex.Message} / Error downloading {imgUrl}: {ex.Message}");
}
}
index++;
}
Puts($"[AutoImageDownloader] Готово. Новых: {downloaded}, пропущено: {skipped} / Done. New: {downloaded}, skipped: {skipped}");
}
}
catch (Exception ex)
{
Puts($"[AutoImageDownloader] Ошибка: {ex.Message} / Error: {ex.Message}");
}
}
}
}