Files
ghostgram/submodules/TelegramCore/Sources/AntiDelete/AntiDeleteManager.swift
T
ichmagmaus 812 03f35f40b5 feat: add Ghostgram features
- Anti-Delete: save deleted messages locally
- Ghost Mode: hide online status, read receipts
- Voice Morpher: audio processing effects
- Device Spoof: spoof device info
- Custom GhostIcon app icon
- User Notes: personal notes for contacts
- Misc settings and controllers
- GPLv2 License
2026-01-19 12:01:00 +01:00

242 lines
8.4 KiB
Swift
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.
import Foundation
/// Менеджер для сохранения удалённых сообщений
/// Перехватывает сообщения перед удалением и архивирует их локально
public final class AntiDeleteManager {
public static let shared = AntiDeleteManager()
// MARK: - Settings
private let defaults = UserDefaults.standard
private let enabledKey = "antiDelete.enabled"
private let archiveMediaKey = "antiDelete.archiveMedia"
private let archiveKey = "antiDelete.archive"
private let deletedIdsKey = "antiDelete.deletedIds"
/// Включено ли сохранение удалённых сообщений
public var isEnabled: Bool {
get { defaults.bool(forKey: enabledKey) }
set { defaults.set(newValue, forKey: enabledKey) }
}
/// Сохранять ли медиа-контент
public var archiveMedia: Bool {
get { defaults.bool(forKey: archiveMediaKey) }
set { defaults.set(newValue, forKey: archiveMediaKey) }
}
// MARK: - Deleted Message IDs Storage
private var deletedMessageIds: Set<String> = []
private let deletedIdsLock = NSLock()
/// Пометить сообщение как удалённое
public func markAsDeleted(peerId: Int64, messageId: Int32) {
let key = "\(peerId)_\(messageId)"
deletedIdsLock.lock()
deletedMessageIds.insert(key)
deletedIdsLock.unlock()
saveDeletedIds()
}
/// Проверить, является ли сообщение удалённым
public func isMessageDeleted(peerId: Int64, messageId: Int32) -> Bool {
guard isEnabled else { return false }
let key = "\(peerId)_\(messageId)"
deletedIdsLock.lock()
defer { deletedIdsLock.unlock() }
return deletedMessageIds.contains(key)
}
/// Проверить, является ли сообщение удалённым (по тексту - legacy)
public func isMessageDeleted(text: String) -> Bool {
guard isEnabled else { return false }
// Legacy: проверяем наличие дефолтного префикса для обратной совместимости
let defaultPrefix = "🗑️ "
return text.hasPrefix(defaultPrefix)
}
private func saveDeletedIds() {
deletedIdsLock.lock()
let ids = Array(deletedMessageIds)
deletedIdsLock.unlock()
defaults.set(ids, forKey: deletedIdsKey)
}
private func loadDeletedIds() {
if let ids = defaults.stringArray(forKey: deletedIdsKey) {
deletedIdsLock.lock()
deletedMessageIds = Set(ids)
deletedIdsLock.unlock()
}
}
// MARK: - Archived Messages Storage
/// Структура архивированного сообщения
public struct ArchivedMessage: Codable {
public let globalId: Int32
public let peerId: Int64
public let messageId: Int32
public let timestamp: Int32
public let deletedAt: Int32
public let authorId: Int64?
public let text: String
public let forwardAuthorId: Int64?
public let mediaDescription: String?
public init(
globalId: Int32,
peerId: Int64,
messageId: Int32,
timestamp: Int32,
deletedAt: Int32,
authorId: Int64?,
text: String,
forwardAuthorId: Int64?,
mediaDescription: String?
) {
self.globalId = globalId
self.peerId = peerId
self.messageId = messageId
self.timestamp = timestamp
self.deletedAt = deletedAt
self.authorId = authorId
self.text = text
self.forwardAuthorId = forwardAuthorId
self.mediaDescription = mediaDescription
}
}
private var archivedMessages: [ArchivedMessage] = []
private let archiveLock = NSLock()
private init() {
// Set default values
if defaults.object(forKey: enabledKey) == nil {
defaults.set(true, forKey: enabledKey)
}
if defaults.object(forKey: archiveMediaKey) == nil {
defaults.set(true, forKey: archiveMediaKey)
}
loadArchive()
loadDeletedIds()
}
// MARK: - Archive Operations
/// Архивировать сообщение перед удалением
/// - Parameters:
/// - globalId: Глобальный ID сообщения
/// - peerId: ID чата
/// - messageId: Локальный ID сообщения
/// - timestamp: Время отправки
/// - authorId: ID автора
/// - text: Текст сообщения
/// - forwardAuthorId: ID автора пересланного сообщения
/// - mediaDescription: Описание медиа (тип, размер)
public func archiveMessage(
globalId: Int32,
peerId: Int64,
messageId: Int32,
timestamp: Int32,
authorId: Int64?,
text: String,
forwardAuthorId: Int64? = nil,
mediaDescription: String? = nil
) {
guard isEnabled else { return }
let archived = ArchivedMessage(
globalId: globalId,
peerId: peerId,
messageId: messageId,
timestamp: timestamp,
deletedAt: Int32(Date().timeIntervalSince1970),
authorId: authorId,
text: text,
forwardAuthorId: forwardAuthorId,
mediaDescription: mediaDescription
)
archiveLock.lock()
defer { archiveLock.unlock() }
// Avoid duplicates
if !archivedMessages.contains(where: { $0.globalId == globalId }) {
archivedMessages.append(archived)
saveArchive()
}
}
/// Получить все архивированные сообщения
public func getAllArchivedMessages() -> [ArchivedMessage] {
archiveLock.lock()
defer { archiveLock.unlock() }
return archivedMessages.sorted { $0.deletedAt > $1.deletedAt }
}
/// Получить архивированные сообщения для конкретного чата
/// - Parameter peerId: ID чата
public func getArchivedMessages(forPeerId peerId: Int64) -> [ArchivedMessage] {
archiveLock.lock()
defer { archiveLock.unlock() }
return archivedMessages
.filter { $0.peerId == peerId }
.sorted { $0.deletedAt > $1.deletedAt }
}
/// Количество архивированных сообщений
public var archivedCount: Int {
archiveLock.lock()
defer { archiveLock.unlock() }
return archivedMessages.count
}
/// Получить данные архивированных сообщений для удаления из диалогов
/// Возвращает массив (peerId, messageId)
public func getArchivedMessageData() -> [(peerId: Int64, messageId: Int32)] {
archiveLock.lock()
defer { archiveLock.unlock() }
return archivedMessages.map { (peerId: $0.peerId, messageId: $0.messageId) }
}
/// Очистить архив
public func clearArchive() {
archiveLock.lock()
defer { archiveLock.unlock() }
archivedMessages.removeAll()
saveArchive()
}
/// Удалить конкретное сообщение из архива
public func removeFromArchive(globalId: Int32) {
archiveLock.lock()
defer { archiveLock.unlock() }
archivedMessages.removeAll { $0.globalId == globalId }
saveArchive()
}
// MARK: - Persistence
private func saveArchive() {
do {
let data = try JSONEncoder().encode(archivedMessages)
defaults.set(data, forKey: archiveKey)
} catch {
print("[AntiDelete] Failed to save archive: \(error)")
}
}
private func loadArchive() {
guard let data = defaults.data(forKey: archiveKey) else { return }
do {
archivedMessages = try JSONDecoder().decode([ArchivedMessage].self, from: data)
} catch {
print("[AntiDelete] Failed to load archive: \(error)")
archivedMessages = []
}
}
}