mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
GH Settings
This commit is contained in:
parent
7f4870a994
commit
6f8c4cfeec
21
Swiftgram/SGGHSettings/BUILD
Normal file
21
Swiftgram/SGGHSettings/BUILD
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "SGGHSettings",
|
||||||
|
module_name = "SGGHSettings",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//Swiftgram/SGGHSettingsScheme:SGGHSettingsScheme",
|
||||||
|
"//Swiftgram/SGLogging:SGLogging",
|
||||||
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
102
Swiftgram/SGGHSettings/Sources/SGGHSettings.swift
Normal file
102
Swiftgram/SGGHSettings/Sources/SGGHSettings.swift
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import Foundation
|
||||||
|
import SGLogging
|
||||||
|
import SGGHSettingsScheme
|
||||||
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
|
||||||
|
public func updateSGGHSettingsInteractivelly(context: AccountContext) {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let locale = presentationData.strings.baseLanguageCode
|
||||||
|
let _ = Task {
|
||||||
|
do {
|
||||||
|
let settings = try await fetchSGGHSettings(locale: locale)
|
||||||
|
await (context.account.postbox.transaction { transaction in
|
||||||
|
updateAppConfiguration(transaction: transaction, { configuration -> AppConfiguration in
|
||||||
|
var configuration = configuration
|
||||||
|
configuration.sgGHSettings = settings
|
||||||
|
return configuration
|
||||||
|
})
|
||||||
|
}).task
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let maxRetries: Int = 3
|
||||||
|
|
||||||
|
enum SGGHFetchError: Error {
|
||||||
|
case invalidURL
|
||||||
|
case notFound
|
||||||
|
case fetchFailed(statusCode: Int)
|
||||||
|
case decodingFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchSGGHSettings(locale: String) async throws -> SGGHSettings {
|
||||||
|
let baseURL = "https://raw.githubusercontent.com/Swiftgram/settings/refs/heads/main"
|
||||||
|
var candidates = []
|
||||||
|
if let buildNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
|
||||||
|
if locale != "en" {
|
||||||
|
candidates.append("\(buildNumber)_\(locale).json")
|
||||||
|
}
|
||||||
|
candidates.append("\(buildNumber).json")
|
||||||
|
}
|
||||||
|
if locale != "en" {
|
||||||
|
candidates.append("latest_\(locale).json")
|
||||||
|
}
|
||||||
|
candidates.append("latest.json")
|
||||||
|
|
||||||
|
for candidate in candidates {
|
||||||
|
let urlString = "\(baseURL)/\(candidate)"
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
SGLogger.shared.log("SGGHSettings", "Fetch failed for \(candidate). Invalid URL: \(urlString)")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastError: Error?
|
||||||
|
for attempt in 1...maxRetries {
|
||||||
|
do {
|
||||||
|
let (data, response) = try await URLSession.shared.data(from: url)
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
SGLogger.shared.log("SGGHSettings", "Fetch failed for \(candidate). Invalid response type: \(response)")
|
||||||
|
throw SGGHFetchError.fetchFailed(statusCode: -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch httpResponse.statusCode {
|
||||||
|
case 200:
|
||||||
|
do {
|
||||||
|
let settings = try JSONDecoder().decode(SGGHSettings.self, from: data)
|
||||||
|
SGLogger.shared.log("SGGHSettings", "Fetched \(candidate). \(settings)")
|
||||||
|
return settings
|
||||||
|
} catch {
|
||||||
|
SGLogger.shared.log("SGGHSettings", "Failed to decode \(candidate). Error: \(error)")
|
||||||
|
throw SGGHFetchError.decodingFailed
|
||||||
|
}
|
||||||
|
case 404:
|
||||||
|
break // Try the next fallback
|
||||||
|
default:
|
||||||
|
SGLogger.shared.log("SGGHSettings", "Fetch failed for \(candidate). Status code: \(httpResponse.statusCode)")
|
||||||
|
throw SGGHFetchError.fetchFailed(statusCode: httpResponse.statusCode)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
lastError = error
|
||||||
|
if attempt == maxRetries {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
try await Task.sleep(seconds: attempt * 2.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw SGGHFetchError.fetchFailed(statusCode: -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension Task {
|
||||||
|
static func sleep(seconds: Double) async throws {
|
||||||
|
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
||||||
|
}
|
||||||
|
}
|
17
Swiftgram/SGGHSettingsScheme/BUILD
Normal file
17
Swiftgram/SGGHSettingsScheme/BUILD
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "SGGHSettingsScheme",
|
||||||
|
module_name = "SGGHSettingsScheme",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct SGGHSettings: Codable, Equatable {
|
||||||
|
public let announcementsData: String?
|
||||||
|
|
||||||
|
public static var defaultValue: SGGHSettings {
|
||||||
|
return SGGHSettings(announcementsData: nil)
|
||||||
|
}
|
||||||
|
}
|
@ -42,7 +42,6 @@ public struct SignalCompleted: Error {}
|
|||||||
// NoError can be marked a
|
// NoError can be marked a
|
||||||
// try? await signal.awaitable()
|
// try? await signal.awaitable()
|
||||||
public extension Signal {
|
public extension Signal {
|
||||||
@available(iOS 13.0, *)
|
|
||||||
func awaitable(file: String = #file, line: Int = #line) async throws -> T {
|
func awaitable(file: String = #file, line: Int = #line) async throws -> T {
|
||||||
return try await withCheckedThrowingContinuation { continuation in
|
return try await withCheckedThrowingContinuation { continuation in
|
||||||
var disposable: Disposable?
|
var disposable: Disposable?
|
||||||
@ -87,7 +86,6 @@ public extension Signal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
|
||||||
var task: () async throws -> T {
|
var task: () async throws -> T {
|
||||||
{
|
{
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
@ -107,8 +105,7 @@ public extension Signal {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
|
||||||
var stream: AsyncThrowingStream<T, Error> {
|
var stream: AsyncThrowingStream<T, Error> {
|
||||||
AsyncThrowingStream { continuation in
|
AsyncThrowingStream { continuation in
|
||||||
let disposable = self.startStandalone(
|
let disposable = self.startStandalone(
|
||||||
@ -129,7 +126,6 @@ public extension Signal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
|
||||||
public extension Signal where E == NoError {
|
public extension Signal where E == NoError {
|
||||||
var task: () async -> T {
|
var task: () async -> T {
|
||||||
{
|
{
|
||||||
@ -156,7 +152,6 @@ public extension Signal where E == NoError {
|
|||||||
|
|
||||||
// Extension for general Signal types - AsyncStream support
|
// Extension for general Signal types - AsyncStream support
|
||||||
public extension Signal {
|
public extension Signal {
|
||||||
@available(iOS 13.0, *)
|
|
||||||
func awaitableStream() -> AsyncStream<T> {
|
func awaitableStream() -> AsyncStream<T> {
|
||||||
return AsyncStream { continuation in
|
return AsyncStream { continuation in
|
||||||
let disposable = self.start(
|
let disposable = self.start(
|
||||||
@ -180,7 +175,6 @@ public extension Signal {
|
|||||||
|
|
||||||
// Extension for NoError Signal types - AsyncStream support
|
// Extension for NoError Signal types - AsyncStream support
|
||||||
public extension Signal where E == NoError {
|
public extension Signal where E == NoError {
|
||||||
@available(iOS 13.0, *)
|
|
||||||
func awaitableStream() -> AsyncStream<T> {
|
func awaitableStream() -> AsyncStream<T> {
|
||||||
return AsyncStream { continuation in
|
return AsyncStream { continuation in
|
||||||
let disposable = self.start(
|
let disposable = self.start(
|
||||||
|
@ -5,6 +5,7 @@ sgdeps = [
|
|||||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings",
|
"//Swiftgram/SGSimpleSettings:SGSimpleSettings",
|
||||||
"//Swiftgram/SGTranslationLangFix:SGTranslationLangFix",
|
"//Swiftgram/SGTranslationLangFix:SGTranslationLangFix",
|
||||||
"//Swiftgram/SGWebSettingsScheme:SGWebSettingsScheme",
|
"//Swiftgram/SGWebSettingsScheme:SGWebSettingsScheme",
|
||||||
|
"//Swiftgram/SGGHSettingsScheme:SGGHSettingsScheme",
|
||||||
"//Swiftgram/SGConfig:SGConfig",
|
"//Swiftgram/SGConfig:SGConfig",
|
||||||
"//Swiftgram/SGLogging:SGLogging",
|
"//Swiftgram/SGLogging:SGLogging",
|
||||||
]
|
]
|
||||||
|
@ -229,35 +229,42 @@ public func getSGProvidedSuggestions(account: Account) -> Signal<Data?, NoError>
|
|||||||
|
|
||||||
return combineLatest(account.postbox.combinedView(keys: [key]), dismissedSGSuggestionsPromise.get())
|
return combineLatest(account.postbox.combinedView(keys: [key]), dismissedSGSuggestionsPromise.get())
|
||||||
|> map { views, dismissedSuggestionsValue -> Data? in
|
|> map { views, dismissedSuggestionsValue -> Data? in
|
||||||
guard let view = views.views[key] as? PreferencesView else {
|
guard let view = views.views[key] as? PreferencesView,
|
||||||
|
let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else {
|
|
||||||
return nil
|
func parseAnnouncements(from string: String?) -> [[String: Any]] {
|
||||||
}
|
guard let string = string,
|
||||||
guard let announcementsString = appConfiguration.sgWebSettings.global.announcementsData,
|
let data = string.data(using: .utf8),
|
||||||
let announcementsData = announcementsString.data(using: .utf8) else {
|
let json = try? JSONSerialization.jsonObject(with: data, options: []),
|
||||||
return nil
|
let array = json as? [[String: Any]] else {
|
||||||
}
|
return []
|
||||||
|
|
||||||
do {
|
|
||||||
if let suggestions = try JSONSerialization.jsonObject(with: announcementsData, options: []) as? [[String: Any]] {
|
|
||||||
let filteredSuggestions = suggestions.filter { suggestion in
|
|
||||||
guard let id = suggestion["id"] as? String else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return !dismissedSuggestionsValue.contains(id) && !SGSimpleSettings.shared.dismissedSGSuggestions.contains(id)
|
|
||||||
}
|
|
||||||
let modifiedData = try JSONSerialization.data(withJSONObject: filteredSuggestions, options: [])
|
|
||||||
return modifiedData
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
} catch {
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
|
let ghSuggestions = parseAnnouncements(from: appConfiguration.sgGHSettings.announcementsData)
|
||||||
|
let webSuggestions = parseAnnouncements(from: appConfiguration.sgWebSettings.global.announcementsData)
|
||||||
|
|
||||||
|
let combinedSuggestions = ghSuggestions + webSuggestions
|
||||||
|
|
||||||
|
let filteredSuggestions = combinedSuggestions.filter { suggestion in
|
||||||
|
guard let id = suggestion["id"] as? String else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return !dismissedSuggestionsValue.contains(id) &&
|
||||||
|
!SGSimpleSettings.shared.dismissedSGSuggestions.contains(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let modifiedData = try? JSONSerialization.data(withJSONObject: filteredSuggestions, options: []) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return modifiedData
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Postbox
|
import Postbox
|
||||||
import SGWebSettingsScheme
|
import SGWebSettingsScheme
|
||||||
|
import SGGHSettingsScheme
|
||||||
|
|
||||||
public struct AppConfiguration: Codable, Equatable {
|
public struct AppConfiguration: Codable, Equatable {
|
||||||
// MARK: Swiftgram
|
// MARK: Swiftgram
|
||||||
public var sgWebSettings: SGWebSettings
|
public var sgWebSettings: SGWebSettings
|
||||||
|
public var sgGHSettings: SGGHSettingsScheme
|
||||||
|
|
||||||
public var data: JSON?
|
public var data: JSON?
|
||||||
public var hash: Int32
|
public var hash: Int32
|
||||||
|
|
||||||
public static var defaultValue: AppConfiguration {
|
public static var defaultValue: AppConfiguration {
|
||||||
return AppConfiguration(sgWebSettings: SGWebSettings.defaultValue, data: nil, hash: 0)
|
return AppConfiguration(sgWebSettings: SGWebSettings.defaultValue, sgGHSettings: SGGHSettings.defaultValue, data: nil, hash: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(sgWebSettings: SGWebSettings, data: JSON?, hash: Int32) {
|
init(sgWebSettings: SGWebSettings, sgGHSettings: SGGHSettings, data: JSON?, hash: Int32) {
|
||||||
self.sgWebSettings = sgWebSettings
|
self.sgWebSettings = sgWebSettings
|
||||||
|
self.sgGHSettings = sgGHSettings
|
||||||
self.data = data
|
self.data = data
|
||||||
self.hash = hash
|
self.hash = hash
|
||||||
}
|
}
|
||||||
@ -23,6 +26,7 @@ public struct AppConfiguration: Codable, Equatable {
|
|||||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
self.sgWebSettings = (try container.decodeIfPresent(SGWebSettings.self, forKey: "sg")) ?? SGWebSettings.defaultValue
|
self.sgWebSettings = (try container.decodeIfPresent(SGWebSettings.self, forKey: "sg")) ?? SGWebSettings.defaultValue
|
||||||
|
self.sgGHSettings = (try container.decodeIfPresent(SGGHSettings.self, forKey: "sggh")) ?? SGGHSettings.defaultValue
|
||||||
self.data = try container.decodeIfPresent(JSON.self, forKey: "data")
|
self.data = try container.decodeIfPresent(JSON.self, forKey: "data")
|
||||||
self.hash = (try container.decodeIfPresent(Int32.self, forKey: "storedHash")) ?? 0
|
self.hash = (try container.decodeIfPresent(Int32.self, forKey: "storedHash")) ?? 0
|
||||||
}
|
}
|
||||||
@ -32,6 +36,7 @@ public struct AppConfiguration: Codable, Equatable {
|
|||||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
try container.encode(self.sgWebSettings, forKey: "sg")
|
try container.encode(self.sgWebSettings, forKey: "sg")
|
||||||
|
try container.encode(self.sgGHSettings, forKey: "sggh")
|
||||||
try container.encodeIfPresent(self.data, forKey: "data")
|
try container.encodeIfPresent(self.data, forKey: "data")
|
||||||
try container.encode(self.hash, forKey: "storedHash")
|
try container.encode(self.hash, forKey: "storedHash")
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ sgdeps = [
|
|||||||
"//Swiftgram/SGPayWall:SGPayWall",
|
"//Swiftgram/SGPayWall:SGPayWall",
|
||||||
"//Swiftgram/SGProUI:SGProUI",
|
"//Swiftgram/SGProUI:SGProUI",
|
||||||
"//Swiftgram/SGKeychainBackupManager:SGKeychainBackupManager",
|
"//Swiftgram/SGKeychainBackupManager:SGKeychainBackupManager",
|
||||||
|
"//Swiftgram/SGGHSettings:SGGHSettings",
|
||||||
# "//Swiftgram/SGContentAnalysis:SGContentAnalysis"
|
# "//Swiftgram/SGContentAnalysis:SGContentAnalysis"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1330,6 +1330,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
|
|
||||||
// MARK: Swiftgram
|
// MARK: Swiftgram
|
||||||
updateSGWebSettingsInteractivelly(context: context.context)
|
updateSGWebSettingsInteractivelly(context: context.context)
|
||||||
|
updateSGGHSettingsInteractivelly(context: context.context)
|
||||||
let _ = (context.context.sharedContext.presentationData.start(next: { presentationData in
|
let _ = (context.context.sharedContext.presentationData.start(next: { presentationData in
|
||||||
SGLocalizationManager.shared.downloadLocale(presentationData.strings.baseLanguageCode)
|
SGLocalizationManager.shared.downloadLocale(presentationData.strings.baseLanguageCode)
|
||||||
}))
|
}))
|
||||||
@ -2021,6 +2022,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
for (_, context, _) in activeAccounts.accounts {
|
for (_, context, _) in activeAccounts.accounts {
|
||||||
// MARK: Swiftgram
|
// MARK: Swiftgram
|
||||||
updateSGWebSettingsInteractivelly(context: context)
|
updateSGWebSettingsInteractivelly(context: context)
|
||||||
|
updateSGGHSettingsInteractivelly(context: context)
|
||||||
if onlySG {
|
if onlySG {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user