├── .github └── workflows │ └── build.yml ├── .gitignore ├── Makefile ├── Package.swift ├── README.md ├── Screenshots ├── 1.png └── 2.png ├── Sources ├── tgapi │ ├── Constants.h │ ├── FilePickerFix.xm │ ├── FunctionHandler.m │ ├── Headers.h │ ├── Hooks.xm │ ├── LocationHook.xm │ ├── Logger │ │ ├── Logger.h │ │ └── Logger.m │ ├── TLParser.swift │ ├── UI │ │ ├── Headers.h │ │ ├── Icons.h │ │ ├── LanguageSelector.m │ │ ├── LocationSelector.m │ │ ├── TGExtra.m │ │ ├── TGExtraLocalization.m │ │ └── UIHooks.xm │ └── api_sources │ │ ├── Api0.swift │ │ ├── Api1.swift │ │ ├── Api10.swift │ │ ├── Api11.swift │ │ ├── Api12.swift │ │ ├── Api13.swift │ │ ├── Api14.swift │ │ ├── Api15.swift │ │ ├── Api16.swift │ │ ├── Api17.swift │ │ ├── Api18.swift │ │ ├── Api19.swift │ │ ├── Api2.swift │ │ ├── Api20.swift │ │ ├── Api21.swift │ │ ├── Api22.swift │ │ ├── Api23.swift │ │ ├── Api24.swift │ │ ├── Api25.swift │ │ ├── Api26.swift │ │ ├── Api27.swift │ │ ├── Api28.swift │ │ ├── Api29.swift │ │ ├── Api3.swift │ │ ├── Api30.swift │ │ ├── Api31.swift │ │ ├── Api32.swift │ │ ├── Api33.swift │ │ ├── Api34.swift │ │ ├── Api35.swift │ │ ├── Api36.swift │ │ ├── Api37.swift │ │ ├── Api38.swiftNotNeedToBeCompiled │ │ ├── Api4.swift │ │ ├── Api5.swift │ │ ├── Api6.swift │ │ ├── Api7.swift │ │ ├── Api8.swift │ │ ├── Api9.swift │ │ ├── Buffer.swift │ │ ├── DeserializeFunctionResponse.swift │ │ ├── SecretApiLayer101.swift │ │ ├── SecretApiLayer144.swift │ │ ├── SecretApiLayer46.swift │ │ ├── SecretApiLayer73.swift │ │ ├── SecretApiLayer8.swift │ │ ├── TelegramApi.h │ │ └── TelegramApiLogger.swift └── tgapiC │ ├── Tweak.m │ └── include │ ├── Tweak.h │ └── module.modulemap ├── TGExtra.bundle ├── ar.lproj │ └── Localizable.strings ├── cn.lproj │ └── Localizable.strings ├── en.lproj │ └── Localizable.strings ├── es.lproj │ └── Localizable.strings ├── fr.lproj │ └── Localizable.strings ├── it.lproj │ └── Localizable.strings ├── ja.lproj │ └── Localizable.strings ├── langs.json ├── ru.lproj │ └── Localizable.strings └── tw.lproj │ └── Localizable.strings ├── TGExtra.plist └── layout └── DEBIAN └── control /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release Tweak 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | cleanup: 13 | runs-on: macos-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Cleaning up existing releases and tags 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | run: | 21 | gh release delete release --yes || true 22 | git push origin :refs/tags/release || true 23 | 24 | build: 25 | needs: cleanup 26 | runs-on: macos-latest 27 | strategy: 28 | matrix: 29 | scheme: [rootful, rootless, roothide] 30 | include: 31 | - scheme: rootful 32 | scheme_arg: "" 33 | - scheme: rootless 34 | scheme_arg: "THEOS_PACKAGE_SCHEME=rootless" 35 | - scheme: roothide 36 | scheme_arg: "THEOS_PACKAGE_SCHEME=roothide" 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Set up Theos 41 | uses: waruhachi/theos-action@v2.4.6 42 | with: 43 | theos-src: 'waruhachi/theos' 44 | theos-branch: 'main' 45 | sdks-src: 'waruhachi/sdks' 46 | sdks-branch: 'main' 47 | 48 | - name: Build Package 49 | shell: bash 50 | run: | 51 | make clean package ${{ matrix.scheme_arg }} 52 | 53 | - name: Upload DEB 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: ${{ matrix.scheme }}-package 57 | path: packages/*.deb 58 | 59 | release: 60 | needs: build 61 | runs-on: macos-latest 62 | steps: 63 | - uses: actions/checkout@v4 64 | 65 | - name: Download all DEBs 66 | uses: actions/download-artifact@v4 67 | with: 68 | path: all-packages 69 | 70 | - name: Prepare DEBs 71 | run: | 72 | mkdir -p packages 73 | cp all-packages/*-package/*.deb packages/ 74 | 75 | - name: Rename DEBs to remove build number and debug 76 | run: | 77 | shopt -s nullglob 78 | for f in packages/*-*+debug_*.deb; do 79 | base=$(basename "$f") 80 | new=$(echo "$base" | sed -E 's/-[0-9]+\+debug/_/' | sed -E 's/__+/_/g') 81 | mv "packages/$base" "packages/$new" 82 | done 83 | 84 | - name: Generate Checksums 85 | id: metadata 86 | shell: bash 87 | run: | 88 | for pkg in packages/*_iphoneos-arm.deb; do 89 | if [ -f "$pkg" ]; then 90 | echo "### Rootful (iphoneos-arm)" >> release_body.md 91 | echo "* MD5: $(md5 -q $pkg)" >> release_body.md 92 | echo "* SHA1: $(shasum -a 1 $pkg | awk '{ print $1 }')" >> release_body.md 93 | echo "* SHA256: $(shasum -a 256 $pkg | awk '{ print $1 }')" >> release_body.md 94 | echo "" >> release_body.md 95 | fi 96 | done 97 | 98 | for pkg in packages/*_iphoneos-arm64.deb; do 99 | if [ -f "$pkg" ]; then 100 | echo "### Rootless (iphoneos-arm64)" >> release_body.md 101 | echo "* MD5: $(md5 -q $pkg)" >> release_body.md 102 | echo "* SHA1: $(shasum -a 1 $pkg | awk '{ print $1 }')" >> release_body.md 103 | echo "* SHA256: $(shasum -a 256 $pkg | awk '{ print $1 }')" >> release_body.md 104 | echo "" >> release_body.md 105 | fi 106 | done 107 | 108 | for pkg in packages/*_iphoneos-arm64e.deb; do 109 | if [ -f "$pkg" ]; then 110 | echo "### RootHide (iphoneos-arm64e)" >> release_body.md 111 | echo "* MD5: $(md5 -q $pkg)" >> release_body.md 112 | echo "* SHA1: $(shasum -a 1 $pkg | awk '{ print $1 }')" >> release_body.md 113 | echo "* SHA256: $(shasum -a 256 $pkg | awk '{ print $1 }')" >> release_body.md 114 | fi 115 | done 116 | 117 | - name: Create GitHub Release 118 | uses: softprops/action-gh-release@v2 119 | env: 120 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 121 | with: 122 | tag_name: release 123 | name: ${{ github.event.repository.name }} 124 | body_path: release_body.md 125 | files: packages/*.deb 126 | draft: false 127 | prerelease: false 128 | make_latest: true 129 | 130 | - name: Delete build artifacts 131 | uses: geekyeggo/delete-artifact@v5 132 | with: 133 | name: | 134 | rootful-package 135 | rootless-package 136 | roothide-package 137 | failOnError: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | 7 | .theos 8 | /packages 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | TARGET = iphone:16.5:14.0 3 | 4 | include $(THEOS)/makefiles/common.mk 5 | 6 | TWEAK_NAME = TGExtra 7 | 8 | $(TWEAK_NAME)_FILES = $(shell find Sources \( -name '*.swift' -o -name '*.m' -o -name '*.xm' \)) 9 | $(TWEAK_NAME)_SWIFTFLAGS = -ISources/tgapiC/include 10 | $(TWEAK_NAME)_CFLAGS = -fobjc-arc -ISources/tgapiC/include -Wno-deprecated-declarations 11 | $(TWEAK_NAME)_FRAMEWORKS = CoreServices 12 | $(TWEAK_NAME)_LOGOS_DEFAULT_GENERATOR = internal 13 | $(TWEAK_NAME)_RESOURCE_FILES = Sources/tgapi/Resources 14 | 15 | # Copy TGExtra.bundle manually during the packaging step 16 | after-stage:: 17 | @echo ">>> Copying Choco.bundle into .deb package..." 18 | @mkdir -p $(THEOS_STAGING_DIR)/Library/Application\ Support/TGExtra 19 | @cp -a TGExtra.bundle $(THEOS_STAGING_DIR)/Library/Application\ Support/TGExtra 20 | 21 | include $(THEOS_MAKE_PATH)/tweak.mk 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | import Foundation 5 | 6 | let projectDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent() 7 | 8 | @dynamicMemberLookup struct TheosConfiguration { 9 | private let dict: [String: String] 10 | init(at path: String) { 11 | let configURL = URL(fileURLWithPath: path, relativeTo: projectDir) 12 | guard let infoString = try? String(contentsOf: configURL) else { 13 | fatalError(""" 14 | Could not find Theos SPM config. Have you run `make spm` yet? 15 | """) 16 | } 17 | let pairs = infoString.split(separator: "\n").map { 18 | $0.split( 19 | separator: "=", maxSplits: 1, 20 | omittingEmptySubsequences: false 21 | ).map(String.init) 22 | }.map { ($0[0], $0[1]) } 23 | dict = Dictionary(uniqueKeysWithValues: pairs) 24 | } 25 | subscript( 26 | key: String, 27 | or defaultValue: @autoclosure () -> String? = nil 28 | ) -> String { 29 | if let value = dict[key] { 30 | return value 31 | } else if let def = defaultValue() { 32 | return def 33 | } else { 34 | fatalError(""" 35 | Could not get value of key '\(key)' from Theos SPM config. \ 36 | Try running `make spm` again. 37 | """) 38 | } 39 | } 40 | subscript(dynamicMember key: String) -> String { self[key] } 41 | } 42 | let conf = TheosConfiguration(at: ".theos/spm_config") 43 | 44 | let theosPath = conf.theos 45 | let sdk = conf.sdk 46 | let resourceDir = conf.swiftResourceDir 47 | let deploymentTarget = conf.deploymentTarget 48 | let triple = "arm64-apple-ios\(deploymentTarget)" 49 | 50 | let libFlags: [String] = [ 51 | "-F\(theosPath)/vendor/lib", "-F\(theosPath)/lib", 52 | "-I\(theosPath)/vendor/include", "-I\(theosPath)/include" 53 | ] 54 | 55 | let cFlags: [String] = libFlags + [ 56 | "-target", triple, "-isysroot", sdk, 57 | "-Wno-unused-command-line-argument", "-Qunused-arguments", 58 | ] 59 | 60 | let cxxFlags: [String] = [ 61 | ] 62 | 63 | let swiftFlags: [String] = libFlags + [ 64 | "-target", triple, "-sdk", sdk, "-resource-dir", resourceDir, 65 | ] 66 | 67 | let package = Package( 68 | name: "tgapi", 69 | platforms: [.iOS(deploymentTarget)], 70 | products: [ 71 | .library( 72 | name: "tgapi", 73 | targets: ["tgapi"] 74 | ), 75 | ], 76 | targets: [ 77 | .target( 78 | name: "tgapiC", 79 | cSettings: [.unsafeFlags(cFlags)], 80 | cxxSettings: [.unsafeFlags(cxxFlags)] 81 | ), 82 | .target( 83 | name: "tgapi", 84 | dependencies: ["tgapiC"], 85 | swiftSettings: [.unsafeFlags(swiftFlags)] 86 | ), 87 | ] 88 | ) 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TGExtra 2 | A simple Telegram iOS Tweak. 3 | 4 | To Open Tweak menu : Longpress screen with 3 finger (if no flex injected) of 4 fingers. 5 | 6 | ## Screenshots 7 | 8 | ![Screenshot 1](Screenshots/1.png) 9 | ![Screenshot 2](Screenshots/2.png) 10 | 11 | ## Features 12 | 13 | - Disable Ads 14 | - Ghost Mode 15 | - No Read Receipt for messages and Stories 16 | - Allow saving Protected Content ( Due to frequenet Telegram Api updates this feature is only limited for client compiled with 11.8.1 sources) 17 | 18 | 19 | ## Disclaimer 20 | 21 | This project is an **independent modification (tweak)** for the Telegram app. I am **not affiliated, associated, authorized, endorsed by, or in any way officially connected with Telegram Messenger LLP**, or any of its subsidiaries or affiliates. 22 | 23 | This tweak is created solely for **personal and educational purposes**. Use it at your own risk. 24 | 25 | **I do not take any responsibility for any issues, damages, or consequences** resulting from the use or misuse of this tweak. If something breaks, it's not my problem. 26 | -------------------------------------------------------------------------------- /Screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWinner02/TGExtra/eca3a54113394ebe359a4aead14f03636cf6ac96/Screenshots/1.png -------------------------------------------------------------------------------- /Screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWinner02/TGExtra/eca3a54113394ebe359a4aead14f03636cf6ac96/Screenshots/2.png -------------------------------------------------------------------------------- /Sources/tgapi/Constants.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #define kAccountUpdateOnlineStatus 1713919532 5 | #define kMessagesSetTypingAction 1486110434 6 | #define kMessagesReadHistory 238054714 7 | #define kStoriesReadStories -1521034552 8 | #define kGetSponsoredMessages -1680673735 9 | 10 | #define kActionIDTyping 381645902 // .sendMessageTypingAction 11 | #define kActionIDRecordingVideo -1584933265 // .sendMessageRecordVideoAction 12 | #define kActionIDUploadingVideo -378127636 // .sendMessageUploadVideoAction 13 | #define kActionIDRecordingAudio -718310409 // .sendMessageRecordAudioAction 14 | #define kActionIDUploadingVoice -212740181 // .sendMessageUploadAudioAction 15 | #define kActionIDUploadingPhoto -774682074 // .sendMessageUploadPhotoAction 16 | #define kActionIDUploadingFile -1441998364 // .sendMessageUploadDocumentAction 17 | #define kActionIDChoosingLocation 393186209 // .sendMessageGeoLocationAction 18 | #define kActionIDChoosingContact 1653390447 // .sendMessageChooseContactAction 19 | #define kActionIDPlayingGame -580219064 // .sendMessageGamePlayAction 20 | #define kActionIDRecordingRoundVideo -1997373508 // .sendMessageRecordRoundAction 21 | #define kActionIDUploadingRoundVideo 608050278 // .sendMessageUploadRoundAction 22 | #define kActionIDSpeakingInGroupCall -651419003 // .speakingInGroupCallAction 23 | #define kActionIDReserverHistoryImport -606432698 // .sendMessageHistoryImportAction 24 | #define kActionIDChoosingSticker -1336228175 // .sendMessageChooseStickerAction 25 | #define kActionIDEmojiInteraction 630664139 // .sendMessageEmojiInteraction 26 | #define kActionIDEmojiAcknowledgement -1234857938 // .sendMessageEmojiInteractionSeen 27 | 28 | #define kDisableOnlineStatus @"disableOnlineStatus" 29 | 30 | #define kDisableTypingStatus @"disableTypingStatus" 31 | #define kDisableRecordingVideoStatus @"disableRecordingVideoStatus" 32 | #define kDisableUploadingVideoStatus @"disableUploadingVideoStatus" 33 | #define kDisableRecordingVoiceStatus @"disableRecordingVoiceStatus" 34 | #define kDisableUploadingVoiceStatus @"disableUploadingVoiceStatus" 35 | #define kDisableUploadingPhotoStatus @"disableUploadingPhotoStatus" 36 | #define kDisableUploadingFileStatus @"disableUploadingFileStatus" 37 | #define kDisableChoosingLocationStatus @"disableChoosingLocationStatus" 38 | #define kDisableChoosingContactStatus @"disableChoosingContactStatus" 39 | #define kDisablePlayingGameStatus @"disablePlayingGameStatus" 40 | #define kDisableRecordingRoundVideoStatus @"disableRecordingRoundVideoStatus" 41 | #define kDisableUploadingRoundVideoStatus @"disableUploadingRoundVideoStatus" 42 | #define kDisableSpeakingInGroupCallStatus @"disableSpeakingInGroupCallStatus" 43 | #define kDisableChoosingStickerStatus @"disableChoosingStickerStatus" 44 | #define kDisableEmojiInteractionStatus @"disableEmojiInteractionStatus" 45 | #define kDisableEmojiAcknowledgementStatus @"disableEmojiAcknowledgementStatus" 46 | 47 | 48 | #define kDisableMessageReadReceipt @"disableMessageReadReceipt" 49 | #define kDisableStoriesReadReceipt @"disableStoriesReadReceipt" 50 | 51 | #define kDisableAllAds @"disableOnlineStatus" 52 | #define kDisableForwardRestriction @"disableForwardRestriction" 53 | 54 | #define FAKE_LOCATION_ENABLED_KEY @"TGExtraFakeLocation" 55 | #define FAKE_LATITUDE_KEY @"TGExtraSavedLatitude" 56 | #define FAKE_LONGITUDE_KEY @"TGExtraSavedLongitude" 57 | 58 | #define FILE_PICKER_FIX_KEY @"TGExtraFixFilePicker" 59 | #define FILE_PICKER_PATH @"TGExtraFileFixUsingSomeUglyHacks" -------------------------------------------------------------------------------- /Sources/tgapi/FilePickerFix.xm: -------------------------------------------------------------------------------- 1 | #import "Headers.h" 2 | 3 | bool shouldFixFilePicker() { 4 | return [[NSUserDefaults standardUserDefaults] boolForKey:FILE_PICKER_FIX_KEY]; 5 | } 6 | 7 | %hook LegacyMediaPickerDocumentViewController 8 | 9 | - (id)initForOpeningContentTypes:(id)types asCopy:(BOOL)copy { 10 | if (!shouldFixFilePicker()) { 11 | return %orig; 12 | } 13 | 14 | return %orig(types, YES); 15 | } 16 | 17 | - (id)initWithDocumentTypes:(id)types inMode:(NSUInteger)mode { 18 | if (!shouldFixFilePicker()) { 19 | return %orig; 20 | } 21 | 22 | return %orig(types, 0); // Copy Mode 23 | } 24 | 25 | %end 26 | 27 | %hook LegacyMediaPickerUIICloudFileController 28 | 29 | - (void)documentPicker:(id)picker didPickDocumentAtURL:(NSURL *)docUrl { 30 | 31 | if (!shouldFixFilePicker()) { 32 | %orig; 33 | return; 34 | } 35 | 36 | if (!docUrl) return; 37 | 38 | NSURL *url = docUrl; 39 | 40 | NSString *customFolderName = [NSString stringWithFormat:@"choco-%@", [[NSUUID UUID] UUIDString]]; 41 | NSString *uglyHackPath = [NSTemporaryDirectory() stringByAppendingPathComponent:FILE_PICKER_PATH]; 42 | NSString *ourPath = [uglyHackPath stringByAppendingPathComponent:customFolderName]; 43 | 44 | 45 | NSFileManager *manager = [NSFileManager defaultManager]; 46 | if (![manager fileExistsAtPath:ourPath]) { 47 | [manager createDirectoryAtPath:ourPath 48 | withIntermediateDirectories:YES 49 | attributes:nil 50 | error:nil]; 51 | } 52 | 53 | NSString *fileName = [url lastPathComponent]; 54 | NSString *destinationPath = [ourPath stringByAppendingPathComponent:fileName]; 55 | NSURL *destinationURL = [NSURL fileURLWithPath:destinationPath]; 56 | 57 | [manager moveItemAtURL:url toURL:destinationURL error:nil]; 58 | 59 | %orig(picker, destinationURL); 60 | } 61 | 62 | - (void)documentPicker:(id)picker didPickDocumentsAtURLs:(NSArray *)urls { 63 | if (!shouldFixFilePicker()) { 64 | %orig; 65 | return; 66 | } 67 | 68 | NSMutableArray *newURLs = [NSMutableArray array]; 69 | 70 | if (urls.count > 0) { 71 | NSString *customFolderName = [NSString stringWithFormat:@"choco-%@", [[NSUUID UUID] UUIDString]]; 72 | NSString *uglyHackPath = [NSTemporaryDirectory() stringByAppendingPathComponent:FILE_PICKER_PATH]; 73 | NSString *ourPath = [uglyHackPath stringByAppendingPathComponent:customFolderName]; 74 | 75 | NSFileManager *manager = [NSFileManager defaultManager]; 76 | if (![manager fileExistsAtPath:ourPath]) { 77 | [manager createDirectoryAtPath:ourPath 78 | withIntermediateDirectories:YES 79 | attributes:nil 80 | error:nil]; 81 | } 82 | 83 | for (NSURL *url in urls) { 84 | NSString *fileName = [url lastPathComponent]; 85 | NSString *destinationPath = [ourPath stringByAppendingPathComponent:fileName]; 86 | NSURL *destinationURL = [NSURL fileURLWithPath:destinationPath]; 87 | 88 | [manager moveItemAtURL:url toURL:destinationURL error:nil]; 89 | [newURLs addObject:destinationURL]; 90 | } 91 | } 92 | 93 | %orig(picker, newURLs); 94 | } 95 | 96 | %end 97 | 98 | 99 | %hook NSURL 100 | 101 | - (BOOL)startAccessingSecurityScopedResource { 102 | if (!shouldFixFilePicker()) { 103 | return %orig; 104 | } 105 | 106 | if ([self.path containsString:FILE_PICKER_PATH]) { 107 | NSFileManager *fm = [NSFileManager defaultManager]; 108 | if ([fm fileExistsAtPath:self.path]) { 109 | return YES; 110 | } else { 111 | return NO; 112 | } 113 | } 114 | return %orig; 115 | } 116 | 117 | %end 118 | 119 | __attribute__((constructor)) 120 | static void initFileHooks() { 121 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ 122 | 123 | int numClasses = objc_getClassList(NULL, 0); 124 | Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); 125 | numClasses = objc_getClassList(classes, numClasses); 126 | 127 | Class DocumentPickerViewController = Nil; 128 | Class LegacyICloudFileController = Nil; 129 | 130 | for (int i = 0; i < numClasses; i++) { 131 | Class cls = classes[i]; 132 | NSString *className = NSStringFromClass(cls); 133 | 134 | if ([className containsString:@"LegacyMediaPickerUI"]) { 135 | if ([className containsString:@"DocumentPickerViewController"]) { 136 | DocumentPickerViewController = cls; 137 | } else if ([className containsString:@"LegacyICloudFileController"]) { 138 | LegacyICloudFileController = cls; 139 | } 140 | } 141 | } 142 | 143 | free(classes); 144 | 145 | if (DocumentPickerViewController && LegacyICloudFileController) { 146 | %init( 147 | LegacyMediaPickerDocumentViewController = DocumentPickerViewController, 148 | LegacyMediaPickerUIICloudFileController = LegacyICloudFileController 149 | ); 150 | } else { 151 | customLog2(@"Failed to find required classes."); 152 | } 153 | }); 154 | } -------------------------------------------------------------------------------- /Sources/tgapi/FunctionHandler.m: -------------------------------------------------------------------------------- 1 | #import "Headers.h" 2 | 3 | NSData *boolTrue() { 4 | uint8_t bytes[] = {0xB5, 0x75, 0x72, 0x99}; // boolTrue#997275b5 5 | return [NSData dataWithBytes:bytes length:sizeof(bytes)]; 6 | } 7 | 8 | NSData *boolFalse() { 9 | uint8_t bytes[] = {0x37, 0x97, 0x79, 0xBC}; // boolFalse#bc799737 10 | return [NSData dataWithBytes:bytes length:sizeof(bytes)]; 11 | } 12 | 13 | // Handlers 14 | void handleOnlineStatus(MTRequest *request, NSData *payload) { 15 | 16 | NSData *isOfflineData = [payload subdataWithRange:NSMakeRange(payload.length - 4, 4)]; 17 | uint32_t isOffline = 0; 18 | [isOfflineData getBytes:&isOffline length:4]; 19 | 20 | if (isOffline == 3162085175) { // Online (BOOL false) 21 | if ([[NSUserDefaults standardUserDefaults] boolForKey:kDisableOnlineStatus]) { 22 | request.fakeData = boolTrue(); 23 | } 24 | } 25 | 26 | } 27 | 28 | void read_Input_Peer(NSData *data, int *offset) { 29 | #define InputPeerEmpty 2134579434 30 | #define InputPeerSelf 2107670217 31 | #define InputPeerChat 900291769 32 | #define InputPeerUser -571955892 33 | #define InputPeerChannel 666680316 34 | #define InputPeerUserFromChannel -1468331492 35 | #define InputPeerChannelFromMessage -1121318848 36 | 37 | int32_t peerConstructorID = 0; 38 | [data getBytes:&peerConstructorID range:NSMakeRange(*offset, 4)]; 39 | *offset += 4; 40 | 41 | switch (peerConstructorID) { 42 | case InputPeerEmpty: 43 | return; 44 | case InputPeerSelf: 45 | return; 46 | case InputPeerChat: 47 | *offset += 8; 48 | return; 49 | case InputPeerUser: 50 | *offset += 16; 51 | return; 52 | case InputPeerChannel: 53 | *offset += 16; 54 | return; 55 | case InputPeerUserFromChannel: 56 | read_Input_Peer(data, offset); 57 | *offset += 12; 58 | return; 59 | case InputPeerChannelFromMessage: 60 | read_Input_Peer(data, offset); 61 | *offset += 12; 62 | return; 63 | default : 64 | return; 65 | } 66 | } 67 | 68 | void handleSetTyping(MTRequest *request, NSData *payload) { 69 | 70 | int offset = 0; 71 | offset += 4; // Skip First 4 Bytes of constructor id; 72 | int32_t flags = 0; 73 | [payload getBytes:&flags range:NSMakeRange(offset, 4)]; 74 | offset += 4; 75 | 76 | read_Input_Peer(payload, &offset); // Read Peer 77 | 78 | if ((flags & (1 << 0)) != 0) { 79 | offset += 4; // Topic id 80 | } 81 | 82 | int32_t actionID = 0; 83 | [payload getBytes:&actionID range:NSMakeRange(offset, 4)]; 84 | 85 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 86 | bool shouldBlockAction = false; 87 | 88 | //customLog(@"Handle Set Typing action : %d actionData:%@", actionID, [payload description]); 89 | switch (actionID) { 90 | case kActionIDTyping: 91 | shouldBlockAction = [defaults boolForKey:kDisableTypingStatus]; 92 | break; 93 | case kActionIDRecordingVideo: 94 | shouldBlockAction = [defaults boolForKey:kDisableRecordingVideoStatus]; 95 | break; 96 | case kActionIDUploadingVideo: 97 | shouldBlockAction = [defaults boolForKey:kDisableUploadingVideoStatus]; 98 | break; 99 | case kActionIDRecordingAudio: 100 | shouldBlockAction = [defaults boolForKey:kDisableRecordingVoiceStatus]; 101 | break; 102 | case kActionIDUploadingVoice: 103 | shouldBlockAction = [defaults boolForKey:kDisableUploadingVoiceStatus]; 104 | break; 105 | case kActionIDUploadingPhoto: 106 | shouldBlockAction = [defaults boolForKey:kDisableUploadingPhotoStatus]; 107 | break; 108 | case kActionIDUploadingFile: 109 | shouldBlockAction = [defaults boolForKey:kDisableUploadingFileStatus]; 110 | break; 111 | case kActionIDChoosingLocation: 112 | shouldBlockAction = [defaults boolForKey:kDisableChoosingLocationStatus]; 113 | break; 114 | case kActionIDChoosingContact: 115 | shouldBlockAction = [defaults boolForKey:kDisableChoosingContactStatus]; 116 | break; 117 | case kActionIDPlayingGame: 118 | shouldBlockAction = [defaults boolForKey:kDisablePlayingGameStatus]; 119 | break; 120 | case kActionIDRecordingRoundVideo: 121 | shouldBlockAction = [defaults boolForKey:kDisableRecordingRoundVideoStatus]; 122 | break; 123 | case kActionIDUploadingRoundVideo: 124 | shouldBlockAction = [defaults boolForKey:kDisableUploadingRoundVideoStatus]; 125 | break; 126 | case kActionIDSpeakingInGroupCall: 127 | shouldBlockAction = [defaults boolForKey:kDisableSpeakingInGroupCallStatus]; 128 | break; 129 | case kActionIDReserverHistoryImport: 130 | shouldBlockAction = [defaults boolForKey:@"reserverHistroyImport"]; 131 | break; 132 | case kActionIDChoosingSticker: 133 | shouldBlockAction = [defaults boolForKey:kDisableChoosingStickerStatus]; 134 | break; 135 | case kActionIDEmojiInteraction: 136 | shouldBlockAction = [defaults boolForKey:kDisableEmojiInteractionStatus]; 137 | break; 138 | case kActionIDEmojiAcknowledgement: 139 | shouldBlockAction = [defaults boolForKey:kDisableEmojiAcknowledgementStatus]; 140 | break; 141 | } 142 | 143 | if (shouldBlockAction) { 144 | request.fakeData = boolTrue(); 145 | } 146 | } 147 | 148 | void handleMessageReadReceipt(MTRequest *request, NSData *payload) { 149 | if ([[NSUserDefaults standardUserDefaults] boolForKey:kDisableMessageReadReceipt]) { 150 | 151 | uint8_t header[] = {0x85, 0x91, 0xD1, 0x84}; // messages.affectedMessages#84d19185 152 | int32_t pts = 0; 153 | int32_t pts_count = 0; 154 | 155 | NSMutableData *data = [NSMutableData data]; 156 | [data appendBytes:&header length:sizeof(header)]; 157 | [data appendBytes:&pts length:sizeof(pts)]; 158 | [data appendBytes:&pts_count length:sizeof(pts_count)]; 159 | 160 | request.fakeData = data; 161 | } 162 | } 163 | 164 | void handleStoriesReadReceipt(MTRequest *request, NSData *payload) { 165 | if ([[NSUserDefaults standardUserDefaults] boolForKey:kDisableStoriesReadReceipt]) { 166 | 167 | uint8_t vectorID[] = {0x15, 0xC4, 0xB5, 0x1C}; // vector#1cb5c415 168 | int32_t count = 0; 169 | 170 | NSMutableData *data = [NSMutableData data]; 171 | [data appendBytes:&vectorID length:sizeof(vectorID)]; 172 | [data appendBytes:&count length:sizeof(count)]; 173 | 174 | request.fakeData = data; 175 | } 176 | } 177 | 178 | void handleGetSponsoredMessages(MTRequest *request, NSData *payload) { 179 | if ([[NSUserDefaults standardUserDefaults] boolForKey:kDisableAllAds]) { 180 | 181 | uint8_t header[] = {0x0F, 0X49, 0X39, 0X18}; // messages.sponsoredMessagesEmpty#1839490f 182 | request.fakeData = [NSData dataWithBytes:header length:sizeof(header)]; 183 | } 184 | } 185 | 186 | void handleChannelsReadReceipt(MTRequest *request, NSData *payload) { 187 | if ([[NSUserDefaults standardUserDefaults] boolForKey:kDisableMessageReadReceipt]) { 188 | request.fakeData = boolTrue(); 189 | } 190 | } -------------------------------------------------------------------------------- /Sources/tgapi/Headers.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "Logger/Logger.h" 4 | #import "Constants.h" 5 | 6 | @interface TLParser : NSObject 7 | + (NSData *)handleResponse:(NSData *)data functionID:(NSNumber *)ios; 8 | @end 9 | 10 | @interface MTRpcError : NSObject 11 | - (id)initWithErrorCode:(int)code errorDescription:(id)desc; 12 | @end 13 | 14 | @interface MTRequestResponseInfo : NSObject 15 | - (id)initWithNetworkType:(int)a timestamp:(CGFloat)b duration:(CGFloat)c; 16 | @end 17 | 18 | @interface MTRequest : NSObject 19 | @property (nonatomic, strong) NSNumber *functionID; 20 | @property (nonatomic, strong) NSData *fakeData; 21 | @property (nonatomic, copy) void (^completed)(id boxedResponse, MTRequestResponseInfo *info, MTRpcError *error); 22 | @property (nonatomic, strong, readonly) id (^responseParser)(NSData *); 23 | @end 24 | 25 | // Function Handlers 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | void handleOnlineStatus(MTRequest *request, NSData *payload); 30 | void handleSetTyping(MTRequest *request, NSData *payload); 31 | void handleMessageReadReceipt(MTRequest *request, NSData *payload); 32 | void handleStoriesReadReceipt(MTRequest *request, NSData *payload); 33 | void handleGetSponsoredMessages(MTRequest *request, NSData *payload); 34 | void handleChannelsReadReceipt(MTRequest *request, NSData *payload); 35 | #ifdef __cplusplus 36 | } 37 | #endif -------------------------------------------------------------------------------- /Sources/tgapi/Hooks.xm: -------------------------------------------------------------------------------- 1 | #import "Headers.h" 2 | 3 | #define kChannelsReadHistory -871347913 4 | 5 | %hook MTRequest 6 | %property (nonatomic, strong) NSData *fakeData; 7 | %property (nonatomic, strong) NSNumber *functionID; 8 | 9 | - (void)setPayload:(NSData *)payload metadata:(id)metadata shortMetadata:(id)shortMetadata responseParser:(id (^)(NSData *))responseParser { 10 | 11 | // Extract Function id 12 | int32_t functionID; 13 | [payload getBytes:&functionID length:4]; 14 | self.functionID = [NSNumber numberWithInt:functionID]; 15 | 16 | //customLog(@"Function id: %d", functionID); 17 | 18 | id(^hooked_block)(NSData *) = ^(NSData *inputData) { 19 | NSNumber *functionIDNumber = [NSNumber numberWithUnsignedInt:functionID]; 20 | NSData *fuck = [TLParser handleResponse:inputData functionID:functionIDNumber]; 21 | id result; 22 | if (fuck) { 23 | result = responseParser(fuck); 24 | } else { 25 | result = responseParser(inputData); 26 | } 27 | return result; 28 | }; 29 | 30 | switch (functionID) { 31 | case kAccountUpdateOnlineStatus: 32 | handleOnlineStatus(self, payload); 33 | break; 34 | case kMessagesSetTypingAction: 35 | handleSetTyping(self, payload); 36 | break; 37 | case kMessagesReadHistory: 38 | handleMessageReadReceipt(self, payload); 39 | break; 40 | case kStoriesReadStories: 41 | handleStoriesReadReceipt(self, payload); 42 | break; 43 | case kGetSponsoredMessages: 44 | handleGetSponsoredMessages(self, payload); 45 | break; 46 | case kChannelsReadHistory: 47 | handleChannelsReadReceipt(self, payload); 48 | break; 49 | default: 50 | break; 51 | 52 | } 53 | 54 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"disableForwardRestriction"]) { 55 | %orig(payload, metadata, shortMetadata, hooked_block); 56 | } else { 57 | %orig(payload, metadata, shortMetadata, responseParser); 58 | } 59 | } 60 | 61 | %end 62 | 63 | 64 | // Manager which handles requests 65 | %hook MTRequestMessageService 66 | 67 | - (void)addRequest:(MTRequest *)request { 68 | if (request.fakeData) { 69 | @try { 70 | if (request.completed) { 71 | NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; 72 | 73 | MTRequestResponseInfo *info = [[%c(MTRequestResponseInfo) alloc] initWithNetworkType:1 74 | timestamp:currentTime 75 | duration:0.045 76 | ]; 77 | 78 | id result = request.responseParser(request.fakeData); 79 | request.completed(result, info, nil); 80 | } 81 | } @catch (NSException *exception) { 82 | customLog2(@"Exception in MTRequestMessageService hook: %@", exception); 83 | } 84 | return; 85 | } 86 | %orig; 87 | } 88 | 89 | %end 90 | -------------------------------------------------------------------------------- /Sources/tgapi/LocationHook.xm: -------------------------------------------------------------------------------- 1 | #import "Headers.h" 2 | #import 3 | #import 4 | #import 5 | 6 | bool shouldFakeLocation() { 7 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 8 | return [defaults boolForKey:FAKE_LOCATION_ENABLED_KEY]; 9 | } 10 | 11 | @interface TGExtraFakeLocationManager : NSObject 12 | @property (nonatomic, strong) NSHashTable *locationManagers; 13 | @property (nonatomic, strong) NSTimer *lieToDelegateTimer; 14 | + (instancetype)shared; 15 | @end 16 | 17 | @implementation TGExtraFakeLocationManager 18 | 19 | - (instancetype)init { 20 | self = [super init]; 21 | if (self) { 22 | self.locationManagers = [NSHashTable weakObjectsHashTable]; 23 | 24 | [self setupTimer]; 25 | 26 | [[NSNotificationCenter defaultCenter] addObserver:self 27 | selector:@selector(appDidEnterBackground) 28 | name:UIApplicationDidEnterBackgroundNotification 29 | object:nil]; 30 | 31 | [[NSNotificationCenter defaultCenter] addObserver:self 32 | selector:@selector(appDidBecomeActive) 33 | name:UIApplicationDidBecomeActiveNotification 34 | object:nil]; 35 | } 36 | return self; 37 | } 38 | 39 | + (instancetype)shared { 40 | static dispatch_once_t token; 41 | static TGExtraFakeLocationManager *instance; 42 | dispatch_once(&token, ^{ 43 | instance = [[TGExtraFakeLocationManager alloc] init]; 44 | }); 45 | return instance; 46 | } 47 | 48 | - (void)setupTimer { 49 | 50 | if (self.lieToDelegateTimer) return; 51 | __weak typeof(self) weakSelf = self; 52 | 53 | self.lieToDelegateTimer = [NSTimer scheduledTimerWithTimeInterval:20 54 | repeats:YES 55 | block:^(NSTimer * _Nonnull timer) { 56 | [weakSelf lieToDelegates]; 57 | }]; 58 | } 59 | 60 | - (void)lieToDelegates { 61 | if (!shouldFakeLocation()) return; 62 | 63 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 64 | CGFloat savedLongitude = [defaults floatForKey:FAKE_LONGITUDE_KEY]; 65 | CGFloat savedLatitude = [defaults floatForKey:FAKE_LATITUDE_KEY]; 66 | 67 | if (!savedLongitude || !savedLatitude) { 68 | return; 69 | } 70 | 71 | CLLocation *fakeLocation = [[CLLocation alloc] initWithLatitude:savedLatitude longitude:savedLongitude]; 72 | NSArray *fakeLocations = @[fakeLocation]; 73 | 74 | for (CLLocationManager *manager in self.locationManagers) { 75 | if (!manager) continue; 76 | id delegate = manager.delegate; 77 | if (!delegate) continue; 78 | if ([delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) { 79 | [delegate locationManager:manager didUpdateLocations:fakeLocations]; 80 | } 81 | } 82 | } 83 | 84 | - (void)appDidEnterBackground { 85 | [self.lieToDelegateTimer invalidate]; 86 | self.lieToDelegateTimer = nil; 87 | } 88 | 89 | - (void)appDidBecomeActive { 90 | [self setupTimer]; 91 | } 92 | 93 | - (void)dealloc { 94 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 95 | } 96 | 97 | @end 98 | 99 | %hook DeviceLocationManager 100 | 101 | - (void)locationManager:(id)manager didUpdateLocations:(id)locations { 102 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 103 | if (shouldFakeLocation()) { 104 | 105 | CGFloat savedLongitude = [defaults floatForKey:FAKE_LONGITUDE_KEY]; 106 | CGFloat savedLatitude = [defaults floatForKey:FAKE_LATITUDE_KEY]; 107 | 108 | if (savedLongitude && savedLatitude) { 109 | CLLocation *fakeLocation = [[CLLocation alloc] initWithLatitude:savedLatitude longitude:savedLongitude]; 110 | NSArray *fakeLocations = @[fakeLocation]; 111 | %orig(manager, fakeLocations); 112 | return; 113 | } 114 | } 115 | %orig; 116 | } 117 | 118 | %end 119 | 120 | %hook MKCoreLocationProvider 121 | 122 | - (void)locationManager:(id)manager didUpdateLocations:(id)locations { 123 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 124 | if (shouldFakeLocation()) { 125 | 126 | // Get Our Saved Location 127 | CGFloat savedLongitude = [defaults floatForKey:FAKE_LONGITUDE_KEY]; 128 | CGFloat savedLatitude = [defaults floatForKey:FAKE_LATITUDE_KEY]; 129 | 130 | if (savedLongitude && savedLatitude) { 131 | CLLocation *fakeLocation = [[CLLocation alloc] initWithLatitude:savedLatitude longitude:savedLongitude]; 132 | NSArray *fakeLocations = @[fakeLocation]; 133 | %orig(manager, fakeLocations); 134 | return; 135 | } 136 | } 137 | %orig; 138 | } 139 | 140 | %end 141 | 142 | %hook CLLocationManager 143 | 144 | - (id)init { 145 | self = %orig; 146 | if (self) { 147 | [[TGExtraFakeLocationManager shared].locationManagers addObject:self]; 148 | } 149 | return self; 150 | } 151 | 152 | - (void)setDelegate:(id)delegate { 153 | //customLog(@"Set Delegate :%@", delegate); 154 | %orig; 155 | 156 | [[TGExtraFakeLocationManager shared].locationManagers addObject:self]; 157 | } 158 | 159 | %end 160 | 161 | @interface MKCoreLocationProvider : NSObject 162 | - (id)_clLocationManager; 163 | @end 164 | 165 | %hook MKCoreLocationProvider 166 | 167 | - (id)initWithCLLocationManager:(id)locationManager { 168 | if ([locationManager isKindOfClass:[CLLocationManager class]]) { 169 | [[TGExtraFakeLocationManager shared].locationManagers addObject:(CLLocationManager *)locationManager]; 170 | } 171 | 172 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 173 | CLLocationManager *innerLocationManager = [self _clLocationManager]; 174 | [[TGExtraFakeLocationManager shared].locationManagers addObject:innerLocationManager]; 175 | }); 176 | 177 | return %orig; 178 | } 179 | 180 | %end 181 | 182 | 183 | __attribute__((constructor)) 184 | static void initLocationHooks() { 185 | %init( 186 | DeviceLocationManager = objc_getClass("DeviceLocationManager.DeviceLocationManager") 187 | ); 188 | 189 | [TGExtraFakeLocationManager shared]; 190 | } 191 | -------------------------------------------------------------------------------- /Sources/tgapi/Logger/Logger.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | void customLog(NSString *format, ...); 7 | void customLog2(NSString *format, ...); 8 | #ifdef __cplusplus 9 | } 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/tgapi/Logger/Logger.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Logger.h" 3 | 4 | // Declaration for LSBundleProxy 5 | @interface LSBundleProxy : NSObject 6 | @property (nonatomic, assign, readonly) NSDictionary *entitlements; 7 | @property (nonatomic, assign, readonly) NSDictionary *groupContainerURLs; 8 | + (instancetype)bundleProxyForCurrentProcess; 9 | @end 10 | 11 | // Static queue for logging 12 | static dispatch_queue_t logQueue; 13 | 14 | // Static function to fetch App Group info 15 | static NSDictionary *getAppGroup() { 16 | static NSDictionary *cachedGroup = nil; 17 | static dispatch_once_t onceToken; 18 | 19 | dispatch_once(&onceToken, ^{ 20 | LSBundleProxy *bundleProxy = [LSBundleProxy bundleProxyForCurrentProcess]; 21 | NSDictionary *entitlements = bundleProxy.entitlements; 22 | 23 | if (entitlements) { 24 | NSArray *appGroups = entitlements[@"com.apple.security.application-groups"]; 25 | if (appGroups && appGroups.count > 0) { 26 | NSString *appGroupName = appGroups.firstObject; 27 | NSURL *appGroupURL = bundleProxy.groupContainerURLs[appGroupName]; 28 | NSString *appGroupPath = appGroupURL.path; 29 | 30 | if (appGroupName && appGroupPath) { 31 | cachedGroup = @{ 32 | @"name": appGroupName, 33 | @"path": appGroupPath 34 | }; 35 | } else { 36 | cachedGroup = nil; 37 | } 38 | } 39 | } 40 | }); 41 | 42 | return cachedGroup; 43 | } 44 | 45 | // Static function to get the log file path 46 | static NSString *logFilePath() { 47 | static NSString *path = nil; 48 | static dispatch_once_t token; 49 | 50 | dispatch_once(&token, ^{ 51 | NSDictionary *appGroup = getAppGroup(); 52 | NSFileManager *fileManager = [NSFileManager defaultManager]; 53 | 54 | if (appGroup) { 55 | NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; 56 | NSString *chocoWhoreDir = [appGroup[@"path"] stringByAppendingPathComponent:@"group.choco.whore"]; 57 | NSString *logFileName = [NSString stringWithFormat:@"%@.txt", bundleIdentifier]; 58 | path = [chocoWhoreDir stringByAppendingPathComponent:logFileName]; 59 | 60 | // Ensure the directory exists 61 | if (![fileManager fileExistsAtPath:chocoWhoreDir]) { 62 | NSError *error = nil; 63 | [fileManager createDirectoryAtPath:chocoWhoreDir 64 | withIntermediateDirectories:YES 65 | attributes:nil 66 | error:&error]; 67 | if (error) { 68 | NSLog(@"Failed to create directory %@: %@", chocoWhoreDir, error.localizedDescription); 69 | } 70 | } 71 | 72 | // Ensure the file exists 73 | if (![fileManager fileExistsAtPath:path]) { 74 | [fileManager createFileAtPath:path contents:nil attributes:nil]; 75 | } 76 | } else { 77 | // Fallback to the Documents directory 78 | path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/chocoLogs.txt"]; 79 | 80 | // Ensure the file exists 81 | if (![fileManager fileExistsAtPath:path]) { 82 | [fileManager createFileAtPath:path contents:nil attributes:nil]; 83 | } 84 | } 85 | }); 86 | 87 | return path; 88 | } 89 | 90 | // Static logging function 91 | void customLog(NSString *format, ...) { 92 | static dispatch_once_t token; 93 | dispatch_once(&token, ^{ 94 | logQueue = dispatch_queue_create("com.choco.whore", DISPATCH_QUEUE_SERIAL); 95 | }); 96 | 97 | va_list args; 98 | va_start(args, format); 99 | 100 | NSString *formattedMessage = [[NSString alloc] initWithFormat:format arguments:args]; 101 | va_end(args); 102 | 103 | dispatch_async(logQueue, ^{ 104 | // Format the log message 105 | NSDate *currentDate = [NSDate date]; 106 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 107 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 108 | NSString *dateString = [dateFormatter stringFromDate:currentDate]; 109 | 110 | NSString *logMessage = [NSString stringWithFormat:@"%@ - %@\n", dateString, formattedMessage]; 111 | 112 | // Write the log message to the file 113 | NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath()]; 114 | if (fileHandle) { 115 | [fileHandle seekToEndOfFile]; 116 | [fileHandle writeData:[logMessage dataUsingEncoding:NSUTF8StringEncoding]]; 117 | [fileHandle closeFile]; 118 | } 119 | }); 120 | } 121 | 122 | void customLog2(NSString *format, ...) { 123 | va_list args; 124 | va_start(args, format); 125 | 126 | NSString *formattedMessage = [[NSString alloc] initWithFormat:format arguments:args]; 127 | va_end(args); 128 | 129 | // Format the log message 130 | NSDate *currentDate = [NSDate date]; 131 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 132 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 133 | NSString *dateString = [dateFormatter stringFromDate:currentDate]; 134 | 135 | NSString *logMessage = [NSString stringWithFormat:@"%@ - %@\n", dateString, formattedMessage]; 136 | 137 | // Write the log message to the file 138 | NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath()]; 139 | if (fileHandle) { 140 | [fileHandle seekToEndOfFile]; 141 | [fileHandle writeData:[logMessage dataUsingEncoding:NSUTF8StringEncoding]]; 142 | [fileHandle closeFile]; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Sources/tgapi/TLParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(TLParser) 4 | class TLParser: NSObject { 5 | @objc static func handleResponse(_ data: NSData, functionID : NSNumber) -> NSData? { 6 | 7 | let buffer1 = Buffer(nsData: data) 8 | let reader = BufferReader(buffer1) 9 | let signature = reader.readInt32() 10 | 11 | if (signature == 481674261) { // Vector 12 | return data 13 | 14 | /* 15 | if (functionID == -1299661699) { // Get All Secure Values 16 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) 17 | } 18 | else if (functionID == 1705865692) { // Get Multi Wallpaper 19 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) 20 | } 21 | else if (functionID == 1936088002) { // Get Secure Value 22 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) 23 | } 24 | else if (functionID == -1334764157) { // Get Admin Bots 25 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) 26 | } 27 | else if (functionID == -481554986) { // Get Bot commands 28 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) 29 | } 30 | else if (functionID == -1566222003) { // Get Preview Media 31 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.BotPreviewMedia.self) 32 | } 33 | else if (functionID == -37955820) { // Get Chat Levae Suggestions 34 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) 35 | } 36 | else if (functionID == 2061264541) { // Get Contact ids 37 | return data 38 | } 39 | else if (functionID == -2098076769) { // Get Saved contact 40 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedContact.self) 41 | } 42 | else if (functionID == -995929106) { // Get Statuses 43 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactStatus.self) 44 | } 45 | else if (functionID == 1120311183) { // Get Language packs 46 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackLanguage.self) 47 | } 48 | else if (functionID == -269862909) { // Get Lang pack Strings 49 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) 50 | } 51 | else if (functionID == -866424884) { // Get Attachted Stickers 52 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) 53 | } 54 | else if (functionID == -643100844) { // Get Emoji Documents 55 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) 56 | } 57 | else if (functionID == 585256482) { // Get Dialog Unread Marks 58 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) 59 | } 60 | else if (functionID == 1318675378) { // Emoji Language 61 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiLanguage.self) 62 | } 63 | else if (functionID == -1177696786) { // Get Fact Check 64 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.FactCheck.self) 65 | } 66 | else if (functionID == 834782287) { // Message read Receipiend 67 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.ReadParticipantDate.self) 68 | } 69 | else if (functionID == 465367808) { // Get search Counters 70 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.messages.SearchCounter.self) 71 | } 72 | else if (functionID == 486505992) { // Get Split Range 73 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageRange.self) 74 | } 75 | else if (functionID == -1566780372) { // Get Suggested Dialong Filters 76 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilterSuggested.self) 77 | } 78 | else if (functionID == 94983360) { // Received Notify Message 79 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.ReceivedNotifyMessage.self) 80 | } 81 | else if (functionID == 1436924774) { // Received Queue 82 | return data 83 | } 84 | else if (functionID == 660060756) { // Premum Gift options 85 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftCodeOption.self) 86 | } 87 | else if (functionID == -741774392) { // Stars gift optioms 88 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiftOption.self) 89 | } 90 | else if (functionID == -1122042562) { // Stars giveaway options 91 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiveawayOption.self) 92 | } 93 | else if (functionID == -1072773165) { // Stars topup options 94 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTopupOption.self) 95 | } 96 | else if (functionID == -1248003721) { // CHECK GROUP CALL 97 | return data 98 | } 99 | else if (functionID == -2016444625) { 100 | return data 101 | } 102 | else if (functionID == -1369842849) { 103 | return data 104 | } 105 | else if (functionID == 1398375363) { 106 | return data 107 | } 108 | else if (functionID == -1521034552) { 109 | return data 110 | } 111 | else if (functionID == -1703566865) { 112 | return data 113 | } 114 | else if (functionID == -1847836879) { // Get CDN File hashes 115 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) 116 | } 117 | else if (functionID == -1856595926) { // Get file hashes 118 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) 119 | } 120 | else if (functionID == -1691921240) { // Reuppload CDN File 121 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) 122 | } 123 | else if (functionID == -660962397) { // Requiremntes to cintact 124 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.RequirementToContact.self) 125 | } 126 | else if (functionID == 227648840) { // Get users 127 | return Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) 128 | } 129 | */ 130 | } 131 | 132 | let buffer = Buffer(nsData: data) 133 | guard let result = Api.parse(buffer) else { 134 | return nil 135 | } 136 | 137 | let outputBuffer = Buffer() 138 | Api.serializeObject(result, buffer: outputBuffer, boxed: true) 139 | 140 | return outputBuffer.makeData() as NSData 141 | } 142 | } -------------------------------------------------------------------------------- /Sources/tgapi/UI/Headers.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import "../Constants.h" 8 | 9 | @interface TGExtra : UIViewController 10 | @end 11 | 12 | @interface TGLocalization : NSObject 13 | - (NSString *)get:(NSString *)queryString; 14 | - (id)initWithVersion:(int)a code:(id)b dict:(id)c isActive:(BOOL)d; 15 | @end 16 | 17 | @interface TGExtraLocalization : NSObject 18 | @property (nonatomic, strong ) TGLocalization *localization; 19 | + (instancetype)shared; 20 | + (NSString *)localizedStringForKey:(NSString *)key; 21 | @end 22 | 23 | 24 | @interface LanguageSelector : UIViewController 25 | @end 26 | 27 | @interface LocationSelector : UIViewController 28 | @end 29 | -------------------------------------------------------------------------------- /Sources/tgapi/UI/Icons.h: -------------------------------------------------------------------------------- 1 | 2 | #define AUTHOR_MESSAGE @"This Telegram tweak is for personal and educational use only. We are not affiliated with Telegram in any way. All trademarks, including the Telegram name and logo, belong to their respective owners. Don’t use this to break rules, be shady, or violate Telegram’s terms—we’re not responsible if things go sideways. Use at your own risk. \n \nAlso… if you like it, say something. I seriously live off validation. \n \nAlso.. If you Want to support me by donating a dollar or Two, Contact me on Telegram" 3 | 4 | 5 | #define CHOCOPNG @"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" 6 | 7 | 8 | #define GHOSTPNG @"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" -------------------------------------------------------------------------------- /Sources/tgapi/UI/LanguageSelector.m: -------------------------------------------------------------------------------- 1 | #import "Headers.h" 2 | #import 3 | 4 | @interface LanguageSelector () 5 | @property (nonatomic, strong) UITableView *tableView; 6 | @property (nonatomic, strong) NSArray *languages; 7 | @end 8 | 9 | @implementation LanguageSelector 10 | 11 | - (void)viewDidLoad { 12 | [super viewDidLoad]; 13 | 14 | NSString *filePath = jbroot(@"/Library/Application Support/TGExtra/TGExtra.bundle/langs.json"); 15 | 16 | NSError *jsonDecodeError = nil; 17 | NSData *data = [NSData dataWithContentsOfFile:filePath]; 18 | NSArray *langs = nil; 19 | 20 | if (data) { 21 | langs = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDecodeError]; 22 | } 23 | 24 | if (jsonDecodeError || !langs) { 25 | self.languages = @[ 26 | @{ 27 | @"name" : @"English", 28 | @"code" : @"en", 29 | @"flag" : @"🇺🇸" 30 | } 31 | ]; 32 | } else { 33 | self.languages = langs; 34 | } 35 | 36 | self.title = @"Change Language"; 37 | [self loadLanguages]; 38 | [self setupTableView]; 39 | } 40 | 41 | - (void)setupTableView { 42 | self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped]; 43 | self.tableView.delegate = self; 44 | self.tableView.dataSource = self; 45 | self.tableView.translatesAutoresizingMaskIntoConstraints = NO; 46 | 47 | [self.view addSubview:self.tableView]; 48 | 49 | [NSLayoutConstraint activateConstraints:@[ 50 | [self.tableView.topAnchor constraintEqualToAnchor:self.view.topAnchor], 51 | [self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 52 | [self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 53 | [self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor] 54 | ]]; 55 | } 56 | 57 | - (void)loadLanguages { 58 | NSMutableArray *languages = [NSMutableArray array]; 59 | 60 | for (NSDictionary *language in self.languages) { 61 | NSString *localizationFilePath = [NSString stringWithFormat:@"%@/TGExtra.bundle/%@.lproj/Localizable.strings", jbroot(@"/Library/Application Support/TGExtra"), language[@"code"]]; 62 | BOOL hasFile = [[NSFileManager defaultManager] fileExistsAtPath:localizationFilePath]; 63 | 64 | [languages addObject:@{ 65 | @"code": language[@"code"], 66 | @"name" : language[@"name"], 67 | @"flag": language[@"flag"], 68 | @"path" : localizationFilePath, 69 | @"isValid" : @(hasFile)} 70 | ]; 71 | } 72 | 73 | self.languages = [languages copy]; 74 | } 75 | 76 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 77 | return 1; 78 | } 79 | 80 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 81 | return self.languages.count; 82 | } 83 | 84 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 85 | static NSString *identifier = @"languageCell"; 86 | 87 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 88 | if (!cell) { 89 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier]; 90 | } else { 91 | cell.imageView.image = nil; 92 | cell.accessoryView = nil; 93 | cell.alpha = 1.0; 94 | cell.userInteractionEnabled = YES; 95 | cell.accessoryType = UITableViewCellAccessoryNone; 96 | } 97 | 98 | NSDictionary *languageData = self.languages[indexPath.row]; 99 | 100 | NSString *title = [NSString stringWithFormat:@"%@ %@", languageData[@"flag"], languageData[@"name"]]; 101 | cell.textLabel.text = title; 102 | 103 | if (![languageData[@"isValid"] boolValue]) { 104 | cell.alpha = 0.6; 105 | cell.userInteractionEnabled = NO; 106 | } 107 | NSString *selectedLanguageCode = [[NSUserDefaults standardUserDefaults] stringForKey:@"TGExtraLanguage"]; 108 | 109 | if ([selectedLanguageCode isEqualToString:languageData[@"code"]]) { 110 | cell.accessoryType = UITableViewCellAccessoryCheckmark; 111 | } 112 | 113 | return cell; 114 | } 115 | 116 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 117 | NSDictionary *languageData = self.languages[indexPath.row]; 118 | 119 | NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:languageData[@"path"]]; 120 | 121 | if (!dict) { 122 | [self showAlertWithTitle:@"Error" message:@"Failed to load language localization data"]; 123 | return; 124 | } 125 | 126 | TGLocalization *localization = [[objc_getClass("TGLocalization") alloc] initWithVersion:96929692 127 | code:languageData[@"code"] 128 | dict:dict 129 | isActive:YES]; 130 | 131 | if (localization) { 132 | [TGExtraLocalization shared].localization = localization; 133 | 134 | [[NSUserDefaults standardUserDefaults] setObject:languageData[@"code"] forKey:@"TGExtraLanguage"]; 135 | [[NSUserDefaults standardUserDefaults] synchronize]; 136 | 137 | [[NSNotificationCenter defaultCenter] postNotificationName:@"LanguageChangedNotification" object:nil]; 138 | 139 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 140 | 141 | [tableView reloadData]; 142 | } else { 143 | [self showAlertWithTitle:@"Error" message:@"Failed to load the language."]; 144 | } 145 | } 146 | 147 | - (void)showAlertWithTitle:(NSString *)title message:(NSString *)message { 148 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:title 149 | message:message 150 | preferredStyle:UIAlertControllerStyleAlert]; 151 | 152 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" 153 | style:UIAlertActionStyleDefault 154 | handler:nil]; 155 | 156 | [alert addAction:okAction]; 157 | 158 | [self presentViewController:alert animated:YES completion:nil]; 159 | } 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /Sources/tgapi/UI/LocationSelector.m: -------------------------------------------------------------------------------- 1 | #import "Headers.h" 2 | #import 3 | 4 | @interface LocationSelector () 5 | @property (nonatomic, strong) MKMapView *mapView; 6 | @end 7 | 8 | @implementation LocationSelector 9 | 10 | - (void)viewDidLoad { 11 | [super viewDidLoad]; 12 | 13 | [self setupMapView]; 14 | [self setupCloseButton]; 15 | [self setupMapSelectorSegment]; 16 | [self loadDefaultLocation]; 17 | } 18 | 19 | - (void)setupMapView { 20 | self.mapView = [[MKMapView alloc] initWithFrame:self.view.bounds]; 21 | self.mapView.delegate = self; 22 | [self.view addSubview:self.mapView]; 23 | 24 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapTap:)]; 25 | [self.mapView addGestureRecognizer:tapGesture]; 26 | } 27 | 28 | - (void)setupCloseButton { 29 | UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; 30 | [closeButton setImage:[UIImage systemImageNamed:@"xmark.square.fill"] forState:UIControlStateNormal]; 31 | closeButton.tintColor = [UIColor redColor]; 32 | [closeButton addTarget:self action:@selector(closeButtonTapped) forControlEvents:UIControlEventTouchUpInside]; 33 | 34 | UIBarButtonItem *closeBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:closeButton]; 35 | self.navigationItem.leftBarButtonItem = closeBarButtonItem; 36 | } 37 | 38 | - (void)closeButtonTapped { 39 | [self.navigationController dismissViewControllerAnimated:YES completion:nil]; 40 | 41 | } 42 | 43 | - (void)setupMapSelectorSegment { 44 | NSArray *mapTypes = @[@"Map", @"Satellite", @"Hybrid"]; 45 | UISegmentedControl *segmentControl = [[UISegmentedControl alloc] initWithItems:mapTypes]; 46 | segmentControl.selectedSegmentIndex = 0; 47 | 48 | [segmentControl addTarget:self action:@selector(mapTypeChanged:) forControlEvents:UIControlEventValueChanged]; 49 | 50 | segmentControl.translatesAutoresizingMaskIntoConstraints = NO; 51 | [self.view addSubview:segmentControl]; 52 | 53 | [NSLayoutConstraint activateConstraints:@[ 54 | [segmentControl.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:16], 55 | [segmentControl.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-16], 56 | [segmentControl.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-12], 57 | [segmentControl.heightAnchor constraintEqualToConstant:32] 58 | ]]; 59 | } 60 | 61 | - (void)mapTypeChanged:(UISegmentedControl *)sender { 62 | switch (sender.selectedSegmentIndex) { 63 | case 0: 64 | self.mapView.mapType = MKMapTypeStandard; 65 | break; 66 | case 1: 67 | self.mapView.mapType = MKMapTypeSatellite; 68 | break; 69 | case 2: 70 | self.mapView.mapType = MKMapTypeHybrid; 71 | break; 72 | default: 73 | break; 74 | } 75 | } 76 | 77 | - (void)loadDefaultLocation { 78 | CGFloat savedLongitude = [[NSUserDefaults standardUserDefaults] floatForKey:FAKE_LONGITUDE_KEY]; 79 | CGFloat savedLatitude = [[NSUserDefaults standardUserDefaults] floatForKey:FAKE_LATITUDE_KEY]; 80 | 81 | CLLocationCoordinate2D centerCoordinate; 82 | if (savedLongitude && savedLatitude) { 83 | centerCoordinate = CLLocationCoordinate2DMake(savedLatitude, savedLongitude); 84 | // Add a pin for the saved location 85 | [self addPinAtCoordinate:centerCoordinate withTitle:@"Last Selected Location"]; 86 | } else { 87 | centerCoordinate = CLLocationCoordinate2DMake(37.7749, -122.4194); // Default to San Francisco 88 | } 89 | 90 | MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(centerCoordinate, 5000, 5000); 91 | [self.mapView setRegion:region animated:YES]; 92 | } 93 | 94 | - (void)handleMapTap:(UITapGestureRecognizer *)gesture { 95 | // Get the tap location 96 | CGPoint touchPoint = [gesture locationInView:self.mapView]; 97 | 98 | // Convert to map coordinates 99 | CLLocationCoordinate2D coordinate = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView]; 100 | 101 | // Add a pin at the selected location 102 | [self addPinAtCoordinate:coordinate withTitle:@"Selected Location"]; 103 | 104 | // Save the selected location to UserDefaults 105 | [[NSUserDefaults standardUserDefaults] setFloat:coordinate.longitude forKey:FAKE_LONGITUDE_KEY]; 106 | [[NSUserDefaults standardUserDefaults] setFloat:coordinate.latitude forKey:FAKE_LATITUDE_KEY]; 107 | [[NSUserDefaults standardUserDefaults] synchronize]; 108 | 109 | [[NSNotificationCenter defaultCenter] postNotificationName:@"TGExtraLocationChanged" object:nil]; 110 | } 111 | 112 | - (void)addPinAtCoordinate:(CLLocationCoordinate2D)coordinate withTitle:(NSString *)title { 113 | MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init]; 114 | annotation.coordinate = coordinate; 115 | annotation.title = title; 116 | 117 | [self.mapView removeAnnotations:self.mapView.annotations]; 118 | [self.mapView addAnnotation:annotation]; 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /Sources/tgapi/UI/TGExtra.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "Icons.h" 4 | #import "Headers.h" 5 | 6 | #define TGLoc(key) [TGExtraLocalization localizedStringForKey:(key)] 7 | 8 | @interface TGExtra () 9 | @property (nonatomic, strong) UITableView *tableView; 10 | @property (nonatomic, strong) NSString *cacheSize; 11 | @end 12 | 13 | @implementation TGExtra 14 | 15 | - (void)viewDidLoad { 16 | 17 | [self setupTableView]; 18 | [self setupIconAsHeader]; 19 | [self setupApplyButton]; 20 | self.title = @"TGExtra"; 21 | 22 | [[NSNotificationCenter defaultCenter] addObserver:self 23 | selector:@selector(didChangeLanguage) 24 | name:@"LanguageChangedNotification" 25 | object:nil]; 26 | 27 | [[NSNotificationCenter defaultCenter] addObserver:self 28 | selector:@selector(didChangeFakeLocation) 29 | name:@"TGExtraLocationChanged" 30 | object:nil]; 31 | } 32 | 33 | - (void)didChangeLanguage { 34 | [self.tableView reloadData]; 35 | } 36 | 37 | - (void)didChangeFakeLocation { 38 | NSIndexSet *section = [NSIndexSet indexSetWithIndex:4]; 39 | [self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationAutomatic]; 40 | } 41 | 42 | - (void)setupTableView { 43 | self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped]; 44 | self.tableView.delegate = self; 45 | self.tableView.dataSource = self; 46 | self.tableView.translatesAutoresizingMaskIntoConstraints = NO; 47 | 48 | [self.view addSubview:self.tableView]; 49 | 50 | [NSLayoutConstraint activateConstraints:@[ 51 | [self.tableView.topAnchor constraintEqualToAnchor:self.view.topAnchor], 52 | [self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 53 | [self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 54 | [self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor] 55 | ]]; 56 | } 57 | 58 | - (void)setupIconAsHeader { 59 | UIView *logoContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 100)]; 60 | 61 | // Logo Image 62 | //NSString *imagePath = [[NSBundle mainBundle].bundlePath stringByAppendingPathComponent:@"Choco.png"]; 63 | NSData *imageData = [[NSData alloc] initWithBase64EncodedString:CHOCOPNG options:NSDataBase64DecodingIgnoreUnknownCharacters]; 64 | UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:imageData]]; 65 | iconView.translatesAutoresizingMaskIntoConstraints = NO; 66 | iconView.layer.cornerRadius = 100 / 4; 67 | iconView.userInteractionEnabled = YES; 68 | iconView.clipsToBounds = YES; 69 | iconView.contentMode = UIViewContentModeScaleAspectFill; 70 | 71 | [logoContainer addSubview:iconView]; 72 | 73 | [NSLayoutConstraint activateConstraints:@[ 74 | [iconView.centerYAnchor constraintEqualToAnchor:logoContainer.centerYAnchor], 75 | [iconView.centerXAnchor constraintEqualToAnchor:logoContainer.centerXAnchor], 76 | [iconView.widthAnchor constraintEqualToConstant:100], 77 | [iconView.heightAnchor constraintEqualToConstant:100] 78 | ]]; 79 | 80 | self.tableView.tableHeaderView = logoContainer; 81 | } 82 | 83 | - (void)setupApplyButton { 84 | UIButton *applyChangesButton = [UIButton buttonWithType:UIButtonTypeSystem]; 85 | UIImage *applyImage = [UIImage systemImageNamed:@"checkmark.square"]; 86 | applyImage = [applyImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 87 | applyChangesButton.tintColor = [UIColor systemPinkColor]; 88 | [applyChangesButton setImage:applyImage forState:UIControlStateNormal]; 89 | [applyChangesButton addTarget:self action:@selector(applyChanges) forControlEvents:UIControlEventTouchUpInside]; 90 | UIBarButtonItem *applyButtonItem = [[UIBarButtonItem alloc] initWithCustomView:applyChangesButton]; 91 | self.navigationItem.rightBarButtonItems = @[applyButtonItem]; 92 | } 93 | 94 | - (void)applyChanges { 95 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:TGLoc(@"APPLY") 96 | message:TGLoc(@"APPLY_CHANGES") 97 | preferredStyle:UIAlertControllerStyleAlert]; 98 | 99 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:TGLoc(@"OK") 100 | style:UIAlertActionStyleDefault 101 | handler:^(UIAlertAction * _Nonnull action) { 102 | [[UIApplication sharedApplication] performSelector:@selector(suspend)]; 103 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 104 | exit(0); 105 | }); 106 | }]; 107 | 108 | [alert addAction:okAction]; 109 | 110 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:TGLoc(@"CANCEL") 111 | style:UIAlertActionStyleCancel 112 | handler:nil]; 113 | [alert addAction:cancelAction]; 114 | [self presentViewController:alert animated:YES completion:nil]; 115 | } 116 | 117 | - (UIColor *)dynamicColorBW { 118 | static dispatch_once_t token; 119 | static UIColor *cached; 120 | dispatch_once(&token, ^{ 121 | cached = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trait) { 122 | if (trait.userInterfaceStyle == UIUserInterfaceStyleDark) { 123 | return [UIColor whiteColor]; 124 | } else { 125 | return [UIColor blackColor]; 126 | } 127 | }]; 128 | }); 129 | return cached; 130 | } 131 | 132 | # pragma mark - UITableViewDataSource 133 | 134 | typedef NS_ENUM(NSInteger, TABLE_VIEW_SECTIONS) { 135 | GHOST_MODE = 0, 136 | READ_RECEIPT = 1, 137 | MISC = 2, 138 | FILE_FIXER = 3, 139 | FAKE_LOCATION = 4, 140 | LANGUAGE = 5, 141 | CREDITS = 6, 142 | }; 143 | 144 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 145 | return 7; 146 | } 147 | 148 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 149 | switch (section) { 150 | case GHOST_MODE: 151 | return 17; 152 | case READ_RECEIPT: 153 | return 2; 154 | case MISC: 155 | return 2; 156 | case FILE_FIXER: 157 | return 2; 158 | case FAKE_LOCATION: 159 | return 2; 160 | case LANGUAGE: 161 | return 1; 162 | case CREDITS: 163 | return 2; 164 | default: 165 | return 0; 166 | } 167 | return 0; 168 | } 169 | 170 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 171 | 172 | switch (section) { 173 | case GHOST_MODE: 174 | return TGLoc(@"GHOST_MODE_SECTION_HEADER"); 175 | case READ_RECEIPT: 176 | return TGLoc(@"READ_RECEIPT_SECTION_HEADER"); 177 | case MISC: 178 | return TGLoc(@"MISC_SECTION_HEADER"); 179 | case FILE_FIXER: 180 | return TGLoc(@"FILE_FIXER_SECTION_HEADER"); 181 | case FAKE_LOCATION: 182 | return TGLoc(@"FAKE_LOCATION_SECTION_HEADER"); 183 | case LANGUAGE: 184 | return TGLoc(@"LANGUAGE_SECTION_HEADER"); 185 | case CREDITS: 186 | return TGLoc(@"CREDITS_SECTION_HEADER"); 187 | default: 188 | return nil; 189 | } 190 | return nil; 191 | } 192 | 193 | - (UITableViewCell *)switchCellFromTableView:(UITableView *)tableView { 194 | UITableViewCell *switchCell = [tableView dequeueReusableCellWithIdentifier:@"switchCell"]; 195 | if (!switchCell) { 196 | switchCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"switchCell"]; 197 | } 198 | 199 | return switchCell; 200 | } 201 | 202 | - (UITableViewCell *)normalCellFromTableView:(UITableView *)tableView { 203 | UITableViewCell *normalCell = [tableView dequeueReusableCellWithIdentifier:@"normalCell"]; 204 | if (!normalCell) { 205 | normalCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"normalCell"]; 206 | } 207 | 208 | return normalCell; 209 | } 210 | 211 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 212 | UITableViewCell *cell; 213 | 214 | if (indexPath.section == 0) { // GHOST MOODE 215 | cell = [self switchCellFromTableView:tableView]; 216 | cell.imageView.image = nil; 217 | 218 | if (indexPath.row == 0) { 219 | cell.textLabel.text = TGLoc(@"DISABLE_ONLINE_STATUS_TITLE"); 220 | cell.detailTextLabel.text = TGLoc(@"DISABLE_ONLINE_STATUS_SUBTITLE"); 221 | } 222 | else if (indexPath.row == 1) { 223 | cell.textLabel.text = TGLoc(@"DISABLE_TYPING_STATUS_TITLE"); 224 | cell.detailTextLabel.text = TGLoc(@"DISABLE_TYPING_STATUS_SUBTITLE"); 225 | } 226 | else if (indexPath.row == 2) { 227 | cell.textLabel.text = TGLoc(@"DISABLE_RECORDING_VIDEO_STATUS_TITLE"); 228 | cell.detailTextLabel.text = TGLoc(@"DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE"); 229 | } 230 | else if (indexPath.row == 3) { 231 | cell.textLabel.text = TGLoc(@"DISABLE_UPLOADING_VIDEO_STATUS_TITLE"); 232 | cell.detailTextLabel.text = TGLoc(@"DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE"); 233 | } 234 | else if (indexPath.row == 4) { 235 | cell.textLabel.text = TGLoc(@"DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE"); 236 | cell.detailTextLabel.text = TGLoc(@"DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE"); 237 | } 238 | else if (indexPath.row == 5) { 239 | cell.textLabel.text = TGLoc(@"DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE"); 240 | cell.detailTextLabel.text = TGLoc(@"DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE"); 241 | } 242 | else if (indexPath.row == 6) { 243 | cell.textLabel.text = TGLoc(@"DISABLE_UPLOADING_PHOTO_STATUS_TITLE"); 244 | cell.detailTextLabel.text = TGLoc(@"DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE"); 245 | } 246 | else if (indexPath.row == 7) { 247 | cell.textLabel.text = TGLoc(@"DISABLE_UPLOADING_FILE_STATUS_TITLE"); 248 | cell.detailTextLabel.text = TGLoc(@"DISABLE_UPLOADING_FILE_STATUS_SUBTITLE"); 249 | } 250 | else if (indexPath.row == 8) { 251 | cell.textLabel.text = TGLoc(@"DISABLE_CHOOSING_LOCATION_STATUS_TITLE"); 252 | cell.detailTextLabel.text = TGLoc(@"DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE"); 253 | } 254 | else if (indexPath.row == 9) { 255 | cell.textLabel.text = TGLoc(@"DISABLE_CHOOSING_CONTACT_TITLE"); 256 | cell.detailTextLabel.text = TGLoc(@"DISABLE_CHOOSING_CONTACT_SUBTITLE"); 257 | } 258 | else if (indexPath.row == 10) { 259 | cell.textLabel.text = TGLoc(@"DISABLE_PLAYING_GAME_STATUS_TITLE"); 260 | cell.detailTextLabel.text = TGLoc(@"DISABLE_PLAYING_GAME_STATUS_SUBTITLE"); 261 | } 262 | else if (indexPath.row == 11) { 263 | cell.textLabel.text = TGLoc(@"DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE"); 264 | cell.detailTextLabel.text = TGLoc(@"DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE"); 265 | } 266 | else if (indexPath.row == 12) { 267 | cell.textLabel.text = TGLoc(@"DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE"); 268 | cell.detailTextLabel.text = TGLoc(@"DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE"); 269 | } 270 | else if (indexPath.row == 13) { 271 | cell.textLabel.text = TGLoc(@"DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE"); 272 | cell.detailTextLabel.text = TGLoc(@"DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE"); 273 | } 274 | else if (indexPath.row == 14) { 275 | cell.textLabel.text = TGLoc(@"DISABLE_CHOOSING_STICKER_STATUS_TITLE"); 276 | cell.detailTextLabel.text = TGLoc(@"DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE"); 277 | } 278 | else if (indexPath.row == 15) { 279 | cell.textLabel.text = TGLoc(@"DISABLE_EMOJI_INTERACTION_STATUS_TITLE"); 280 | cell.detailTextLabel.text = TGLoc(@"DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE"); 281 | } 282 | else if (indexPath.row == 16) { 283 | cell.textLabel.text = TGLoc(@"DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE"); 284 | cell.detailTextLabel.text = TGLoc(@"DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE"); 285 | } 286 | 287 | UISwitch *toggle = (UISwitch *)cell.accessoryView; 288 | if (!toggle || ![toggle isKindOfClass:[UISwitch class]]) { 289 | toggle = [[UISwitch alloc] init]; 290 | } 291 | 292 | NSString *switchKey = [self switchKeyForIndexPath:indexPath]; 293 | toggle.on = [[NSUserDefaults standardUserDefaults] boolForKey:switchKey]; 294 | [toggle addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged]; 295 | toggle.tag = 1000 + (indexPath.section *1000) + indexPath.row; 296 | cell.accessoryView = toggle; 297 | 298 | cell.textLabel.numberOfLines = 0; 299 | cell.detailTextLabel.numberOfLines = 0; 300 | return cell; 301 | 302 | } 303 | else if (indexPath.section == 1) { // Read Receipts 304 | cell = [self switchCellFromTableView:tableView]; 305 | cell.imageView.image = nil; 306 | 307 | if (indexPath.row == 0) { 308 | cell.textLabel.text = TGLoc(@"DISABLE_MESSAGE_READ_RECEIPT_TITLE"); 309 | cell.detailTextLabel.text = TGLoc(@"DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE"); 310 | } 311 | else if (indexPath.row == 1) { 312 | cell.textLabel.text = TGLoc(@"DISABLE_STORY_READ_RECEIPT_TITLE"); 313 | cell.detailTextLabel.text = TGLoc(@"DISABLE_STORY_READ_RECEIPT_SUBTITLE"); 314 | } 315 | 316 | UISwitch *toggle = (UISwitch *)cell.accessoryView; 317 | if (!toggle || ![toggle isKindOfClass:[UISwitch class]]) { 318 | toggle = [[UISwitch alloc] init]; 319 | } 320 | 321 | NSString *switchKey = [self switchKeyForIndexPath:indexPath]; 322 | toggle.on = [[NSUserDefaults standardUserDefaults] boolForKey:switchKey]; 323 | [toggle addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged]; 324 | toggle.tag = 1000 + (indexPath.section *1000) + indexPath.row; 325 | cell.accessoryView = toggle; 326 | 327 | cell.textLabel.numberOfLines = 0; 328 | cell.detailTextLabel.numberOfLines = 0; 329 | return cell; 330 | } 331 | else if (indexPath.section == 2) { // MISC 332 | cell = [self switchCellFromTableView:tableView]; 333 | cell.imageView.image = nil; 334 | 335 | if (indexPath.row == 0) { 336 | cell.textLabel.text = TGLoc(@"DISABLE_ALL_ADS_TITLE"); 337 | cell.detailTextLabel.text = TGLoc(@"DISABLE_ALL_ADS_SUBTITLE"); 338 | } 339 | else if (indexPath.row == 1) { 340 | cell.textLabel.text = TGLoc(@"ENABLE_SAVING_PROTECTED_CONTENT_TITLE"); 341 | cell.detailTextLabel.text = TGLoc(@"ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE"); 342 | } 343 | 344 | UISwitch *toggle = (UISwitch *)cell.accessoryView; 345 | if (!toggle || ![toggle isKindOfClass:[UISwitch class]]) { 346 | toggle = [[UISwitch alloc] init]; 347 | } 348 | 349 | NSString *switchKey = [self switchKeyForIndexPath:indexPath]; 350 | toggle.on = [[NSUserDefaults standardUserDefaults] boolForKey:switchKey]; 351 | [toggle addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged]; 352 | toggle.tag = 1000 + (indexPath.section *1000) + indexPath.row; 353 | cell.accessoryView = toggle; 354 | 355 | cell.textLabel.numberOfLines = 0; 356 | cell.detailTextLabel.numberOfLines = 0; 357 | return cell; 358 | 359 | } 360 | if (indexPath.section == 3) { // File Picker Fix 361 | if (indexPath.row ==0) { //Enable File Picker Fix 362 | cell = [self switchCellFromTableView:tableView]; 363 | 364 | cell.imageView.image = [UIImage systemImageNamed:@"folder.fill.badge.gear"]; 365 | cell.imageView.tintColor = [self dynamicColorBW]; 366 | cell.textLabel.text = TGLoc(@"FIX_FILE_PICKER_TITLE"); 367 | cell.detailTextLabel.text = TGLoc(@"FIX_FILE_PICKER_SUBTITLE"); 368 | 369 | UISwitch *toggle = (UISwitch *)cell.accessoryView; 370 | if (!toggle || ![toggle isKindOfClass:[UISwitch class]]) { 371 | toggle = [[UISwitch alloc] init]; 372 | } 373 | 374 | NSString *switchKey = [self switchKeyForIndexPath:indexPath]; 375 | toggle.on = [[NSUserDefaults standardUserDefaults] boolForKey:switchKey]; 376 | [toggle addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged]; 377 | toggle.tag = 1000 + (indexPath.section *1000) + indexPath.row; 378 | cell.accessoryView = toggle; 379 | 380 | cell.textLabel.numberOfLines = 0; 381 | cell.detailTextLabel.numberOfLines = 0; 382 | return cell; 383 | } 384 | 385 | if (indexPath.row == 1) { 386 | cell = [self normalCellFromTableView:tableView]; 387 | cell.imageView.image = nil; 388 | 389 | cell.textLabel.text = TGLoc(@"CLEAR_FILE_PICKER_CACHE_TITLE"); 390 | cell.detailTextLabel.text = TGLoc(@"CLEAR_FILE_PICKER_CACHE_SUBTITLE"); 391 | cell.imageView.image = [UIImage systemImageNamed:@"trash"]; 392 | cell.imageView.tintColor = [UIColor redColor]; 393 | 394 | // Initially show a UIActivityIndicator 395 | UIActivityIndicatorView *loadingIcon = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; 396 | [loadingIcon startAnimating]; 397 | cell.accessoryView = loadingIcon; 398 | 399 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 400 | if (!self.cacheSize) { 401 | self.cacheSize = [self sizeOfUglyFileFixDirectory]; 402 | } 403 | 404 | dispatch_async(dispatch_get_main_queue(), ^{ 405 | UITableViewCell *currentCell = [tableView cellForRowAtIndexPath:indexPath]; 406 | if (currentCell == cell) { 407 | UILabel *sizeLabel = [[UILabel alloc] init]; 408 | sizeLabel.text = self.cacheSize; 409 | cell.accessoryView = sizeLabel; 410 | 411 | [sizeLabel sizeToFit]; 412 | } 413 | }); 414 | }); 415 | 416 | cell.textLabel.numberOfLines = 0; 417 | cell.detailTextLabel.numberOfLines = 0; 418 | return cell; 419 | } 420 | } 421 | 422 | if (indexPath.section == 4) { // Fake Location 423 | if (indexPath.row ==0) { 424 | cell = [self switchCellFromTableView:tableView]; 425 | 426 | cell.imageView.image = [UIImage systemImageNamed:@"location.fill"]; 427 | cell.imageView.tintColor = [self dynamicColorBW]; 428 | cell.textLabel.text = TGLoc(@"ENABLE_FAKE_LOCATION_TITLE"); 429 | cell.detailTextLabel.text = TGLoc(@"ENABLE_FAKE_LOCATION_SUBTITLE"); 430 | 431 | UISwitch *toggle = (UISwitch *)cell.accessoryView; 432 | if (!toggle || ![toggle isKindOfClass:[UISwitch class]]) { 433 | toggle = [[UISwitch alloc] init]; 434 | } 435 | 436 | NSString *switchKey = [self switchKeyForIndexPath:indexPath]; 437 | toggle.on = [[NSUserDefaults standardUserDefaults] boolForKey:switchKey]; 438 | [toggle addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged]; 439 | toggle.tag = 1000 + (indexPath.section *1000) + indexPath.row; 440 | cell.accessoryView = toggle; 441 | } 442 | 443 | if (indexPath.row == 1) { 444 | cell = [self normalCellFromTableView:tableView]; 445 | 446 | cell.imageView.image = [UIImage systemImageNamed:@"location.fill"]; 447 | cell.imageView.tintColor = [self dynamicColorBW]; 448 | cell.textLabel.text = TGLoc(@"SELECT_FAKE_LOCATION_TITLE"); 449 | 450 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 451 | CGFloat savedLongitude = [defaults floatForKey:FAKE_LONGITUDE_KEY]; 452 | CGFloat savedLatitude = [defaults floatForKey:FAKE_LATITUDE_KEY]; 453 | 454 | NSString *savedCord = savedCord = [NSString stringWithFormat:@"lon :%f\nlat :%f", savedLongitude ? : 0, savedLatitude ? : 0]; 455 | 456 | cell.textLabel.numberOfLines = 0; 457 | cell.detailTextLabel.text = savedCord; 458 | } 459 | cell.detailTextLabel.numberOfLines = 0; 460 | return cell; 461 | } 462 | 463 | if (indexPath.section == 5) { // Language 464 | cell = [self normalCellFromTableView:tableView]; 465 | if (indexPath.row == 0) { 466 | cell.textLabel.text = @"Change Language"; 467 | cell.detailTextLabel.text = @""; 468 | cell.imageView.image = [UIImage systemImageNamed:@"globe"]; 469 | cell.imageView.tintColor = [self dynamicColorBW]; 470 | cell.imageView.layer.cornerRadius = 40/8; 471 | cell.imageView.layer.masksToBounds = YES; 472 | cell.accessoryView = nil; 473 | 474 | cell.textLabel.numberOfLines = 0; 475 | cell.detailTextLabel.numberOfLines = 0; 476 | return cell; 477 | } 478 | } 479 | 480 | if (indexPath.section == 6) { // Credits 481 | cell = [self normalCellFromTableView:tableView]; 482 | 483 | if (indexPath.row == 0) { 484 | cell.textLabel.text = @"Chocolate Fluffy (Dumb Whore)"; 485 | cell.detailTextLabel.text = @"Developer"; 486 | cell.detailTextLabel.textColor = [UIColor lightGrayColor]; 487 | NSData *imageData = [[NSData alloc] initWithBase64EncodedString:CHOCOPNG options:NSDataBase64DecodingIgnoreUnknownCharacters]; 488 | cell.imageView.image = [UIImage imageWithData:imageData scale:2.0]; 489 | cell.imageView.layer.cornerRadius = 40/8; 490 | cell.imageView.layer.masksToBounds = YES; 491 | cell.accessoryView = nil; 492 | 493 | } 494 | else if (indexPath.row == 1) { 495 | cell.textLabel.text = TGLoc(@"DISCLAIMER"); 496 | cell.detailTextLabel.text = @"A note from whore"; 497 | cell.imageView.image = [UIImage systemImageNamed:@"note.text"]; 498 | cell.imageView.tintColor = [self dynamicColorBW]; 499 | cell.accessoryView = nil; 500 | cell.detailTextLabel.textColor = [UIColor lightGrayColor]; 501 | } 502 | cell.textLabel.numberOfLines = 0; 503 | cell.detailTextLabel.numberOfLines = 0; 504 | return cell; 505 | } 506 | 507 | return cell; 508 | } 509 | 510 | # pragma mark - UITableViewDelegate 511 | 512 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 513 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 514 | 515 | if (indexPath.section == FILE_FIXER) { // File Picker Fix 516 | if (indexPath.row == 1) { 517 | [self clearFilePickerFixCache]; 518 | } 519 | } 520 | 521 | if (indexPath.section == FAKE_LOCATION) { // Fake Location 522 | if (indexPath.row == 1) { 523 | [self showLocationSelector]; 524 | } 525 | } 526 | 527 | if (indexPath.section == LANGUAGE) { // Language 528 | if (indexPath.row == 0) { 529 | [self showLanguageSelector]; 530 | } 531 | } 532 | 533 | if (indexPath.section == CREDITS) { 534 | if (indexPath.row == 0) { 535 | NSString *base64String = @"aHR0cHM6Ly90Lm1lL3VsdGltYXRlUG9pc29u"; 536 | NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; 537 | NSString *decodedURL = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; 538 | 539 | NSURL *url = [NSURL URLWithString:decodedURL]; 540 | if ([[UIApplication sharedApplication] canOpenURL:url]) { 541 | [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; 542 | } 543 | } 544 | else if (indexPath.row == 1) { 545 | [self showDisclaimer]; 546 | } 547 | } 548 | } 549 | 550 | - (void)switchChanged:(UISwitch *)sender { 551 | NSInteger adjustedTag = sender.tag - 1000; 552 | NSInteger section = adjustedTag / 1000; 553 | NSInteger row = adjustedTag % 1000; 554 | 555 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; 556 | NSString *switchKey = [self switchKeyForIndexPath:indexPath]; 557 | 558 | if (switchKey) { 559 | [[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:switchKey]; 560 | } 561 | } 562 | 563 | - (NSString *)switchKeyForIndexPath:(NSIndexPath *)indexPath { 564 | switch (indexPath.section) { 565 | case 0: 566 | switch (indexPath.row) { 567 | case 0: return kDisableOnlineStatus; 568 | case 1: return kDisableTypingStatus; 569 | case 2: return kDisableRecordingVideoStatus; 570 | case 3: return kDisableUploadingVideoStatus; 571 | case 4: return kDisableRecordingVoiceStatus; 572 | case 5: return kDisableUploadingVoiceStatus; 573 | case 6: return kDisableUploadingPhotoStatus; 574 | case 7: return kDisableUploadingFileStatus; 575 | case 8: return kDisableChoosingLocationStatus; 576 | case 9: return kDisableChoosingContactStatus; 577 | case 10: return kDisablePlayingGameStatus; 578 | case 11: return kDisableRecordingRoundVideoStatus; 579 | case 12: return kDisableUploadingRoundVideoStatus; 580 | case 13: return kDisableSpeakingInGroupCallStatus; 581 | case 14: return kDisableChoosingStickerStatus; 582 | case 15: return kDisableEmojiInteractionStatus; 583 | case 16: return kDisableEmojiAcknowledgementStatus; 584 | default: return nil; 585 | } 586 | case 1: 587 | switch (indexPath.row) { 588 | case 0: return kDisableMessageReadReceipt; 589 | case 1: return kDisableStoriesReadReceipt; 590 | default: return nil; 591 | } 592 | case 2: 593 | switch (indexPath.row) { 594 | case 0: return kDisableAllAds; 595 | case 1: return kDisableForwardRestriction; 596 | default: return nil; 597 | } 598 | case 3: 599 | switch (indexPath.row) { 600 | case 0: return FILE_PICKER_FIX_KEY; 601 | default: return nil; 602 | } 603 | case 4: 604 | switch (indexPath.row) { 605 | case 0: return FAKE_LOCATION_ENABLED_KEY; 606 | default: return nil; 607 | } 608 | default: 609 | return nil; 610 | } 611 | } 612 | 613 | - (NSString *)sizeOfUglyFileFixDirectory { 614 | NSString *uglyFixDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:FILE_PICKER_PATH]; 615 | 616 | // Calculate size of it recursively 617 | unsigned long long totalSize = 0; 618 | NSFileManager *fileManager = [NSFileManager defaultManager]; 619 | NSArray *contents = [fileManager subpathsAtPath:uglyFixDirectory]; 620 | 621 | for (NSString *path in contents) { 622 | NSString *fullPath = [uglyFixDirectory stringByAppendingPathComponent:path]; 623 | BOOL isDirectory; 624 | if ([fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory]) { 625 | if (!isDirectory) { 626 | NSDictionary *attributes = [fileManager attributesOfItemAtPath:fullPath error:nil]; 627 | totalSize += [attributes fileSize]; 628 | } 629 | } 630 | } 631 | 632 | // Format the size into MB or GB 633 | NSString *formattedSize; 634 | if (totalSize >= 1024 * 1024 * 1024) { // if the size is >= 1GB 635 | formattedSize = [NSString stringWithFormat:@"%.2f GB", totalSize / (1024.0 * 1024.0 * 1024.0)]; 636 | } else { 637 | formattedSize = [NSString stringWithFormat:@"%.2f MB", totalSize / (1024.0 * 1024.0)]; 638 | } 639 | return formattedSize; 640 | } 641 | 642 | - (void)showDisclaimer { 643 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:TGLoc(@"DISCLAIMER") 644 | message:TGLoc(@"AUTHOR_MESSAGE") 645 | preferredStyle:UIAlertControllerStyleAlert]; 646 | 647 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:TGLoc(@"OK") 648 | style:UIAlertActionStyleDefault 649 | handler:nil]; 650 | 651 | [alert addAction:okAction]; 652 | 653 | [self presentViewController:alert animated:YES completion:nil]; 654 | } 655 | 656 | - (void)showLanguageSelector { 657 | LanguageSelector *ui = [LanguageSelector new]; 658 | UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:ui]; 659 | [self presentViewController:navVC animated:YES completion:nil]; 660 | } 661 | 662 | - (void)showLocationSelector { 663 | LocationSelector *ui = [LocationSelector new]; 664 | UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:ui]; 665 | [self presentViewController:navVC animated:YES completion:nil]; 666 | } 667 | 668 | - (void)clearFilePickerFixCache { 669 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:TGLoc(@"CACHE_CLEAR_WARNING_TITLE") 670 | message:TGLoc(@"CACHE_CLEAR_WARNING_MESSAGE") 671 | preferredStyle:UIAlertControllerStyleAlert]; 672 | 673 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:TGLoc(@"OK") 674 | style:UIAlertActionStyleDestructive 675 | handler:^(UIAlertAction *action) { 676 | NSString *uglyFixDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"TGExtraFileFixUsingSomeUglyHacks"]; 677 | 678 | NSError *error = nil; 679 | [[NSFileManager defaultManager] removeItemAtPath:uglyFixDirectory error:&error]; 680 | 681 | if (error) { 682 | NSLog(@"Failed to remove cache directory: %@", error.localizedDescription); 683 | } else { 684 | NSLog(@"Successfully cleared cache: %@", uglyFixDirectory); 685 | } 686 | 687 | self.cacheSize = @"Cleared"; 688 | 689 | // Reload section or row as needed 690 | dispatch_async(dispatch_get_main_queue(), ^{ 691 | NSIndexSet *section = [NSIndexSet indexSetWithIndex:FILE_FIXER]; 692 | [self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationAutomatic]; 693 | }); 694 | }]; 695 | 696 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:TGLoc(@"CANCEL") 697 | style:UIAlertActionStyleCancel 698 | handler:nil]; 699 | 700 | [alert addAction:cancelAction]; 701 | [alert addAction:okAction]; 702 | 703 | [self presentViewController:alert animated:YES completion:nil]; 704 | } 705 | 706 | - (void)dealloc { 707 | [[NSNotificationCenter defaultCenter] removeObserver:self name:@"LanguageChangedNotification" object:nil]; 708 | 709 | [[NSNotificationCenter defaultCenter] removeObserver:self name:@"TGExtralocationChanged" object:nil]; 710 | 711 | } 712 | 713 | @end 714 | -------------------------------------------------------------------------------- /Sources/tgapi/UI/TGExtraLocalization.m: -------------------------------------------------------------------------------- 1 | #import "Headers.h" 2 | 3 | @implementation TGExtraLocalization 4 | 5 | + (instancetype)shared { 6 | static TGExtraLocalization *instance; 7 | static dispatch_once_t token; 8 | dispatch_once(&token, ^{ 9 | instance = [TGExtraLocalization new]; 10 | [instance loadDefault]; 11 | }); 12 | return instance; 13 | } 14 | 15 | - (void)loadDefault { 16 | NSString *selectedLanguageCode = [[NSUserDefaults standardUserDefaults] stringForKey:@"TGExtraLanguage"]; 17 | 18 | if (!selectedLanguageCode) { 19 | selectedLanguageCode = @"en"; 20 | } 21 | 22 | NSString *localizationFilePath = [NSString stringWithFormat:@"%@/TGExtra.bundle/%@.lproj/Localizable.strings", jbroot(@"/Library/Application Support/TGExtra"), selectedLanguageCode]; 23 | 24 | NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:localizationFilePath]; 25 | 26 | self.localization = [[objc_getClass("TGLocalization") alloc] initWithVersion:96929692 27 | code:selectedLanguageCode 28 | dict:dict 29 | isActive:YES]; 30 | 31 | } 32 | 33 | + (NSString *)localizedStringForKey:(NSString *)key { 34 | if (!key) return nil; 35 | 36 | NSString *localizedString = [[TGExtraLocalization shared].localization get:key]; 37 | 38 | if (!localizedString) return key; 39 | 40 | return localizedString; 41 | } 42 | @end 43 | -------------------------------------------------------------------------------- /Sources/tgapi/UI/UIHooks.xm: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Headers.h" 3 | 4 | 5 | // Menu Open 6 | @interface ASDisplayNode : NSObject 7 | @property (atomic, assign, readonly) UIView *view; 8 | @property (atomic, copy, readonly) NSArray *subnodes; 9 | @property (atomic, copy, readwrite) NSString *accessibilityLabel; 10 | @property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture; 11 | @property (nonatomic, strong) UITapGestureRecognizer *tapGesture; 12 | - (void)__handleSettingsTabLongPress:(UILongPressGestureRecognizer *)gesture; 13 | - (void)__handle5PleTap; 14 | @end 15 | 16 | static __weak TGLocalization *TGLocalizationShared = nil; 17 | 18 | %hook TGLocalization 19 | 20 | - (id)initWithVersion:(int)a code:(id)b dict:(id)c isActive:(BOOL)d { 21 | TGLocalization *instance = %orig; 22 | if (a != 96929692 && instance) { 23 | TGLocalizationShared = instance; 24 | } 25 | return instance; 26 | } 27 | 28 | %end 29 | 30 | void showUI() { 31 | TGExtra *ui = [TGExtra new]; 32 | UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:ui]; 33 | 34 | UIWindow *window = UIApplication.sharedApplication.keyWindow; 35 | UIViewController *rootVC = window.rootViewController; 36 | if (rootVC) { 37 | [rootVC presentViewController:navVC animated:YES completion:nil]; 38 | } 39 | } 40 | 41 | %hook ASDisplayNode 42 | %property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture; 43 | %property (nonatomic, strong) UITapGestureRecognizer *tapGesture; 44 | 45 | %new 46 | - (void)__handleSettingsTabLongPress:(UILongPressGestureRecognizer *)gesture { 47 | if (gesture.state == UIGestureRecognizerStateBegan) { 48 | showUI(); 49 | } 50 | } 51 | 52 | %new 53 | - (void)__handle5PleTap { 54 | showUI(); 55 | } 56 | 57 | %end 58 | 59 | %hook TabBarNode 60 | 61 | - (void)didEnterHierarchy { 62 | %orig; 63 | 64 | ASDisplayNode *mainNode = self; 65 | 66 | for (ASDisplayNode *child in mainNode.subnodes) { 67 | NSString *localizedTitle = @"Chats"; 68 | 69 | NSString *resultTitle = [TGLocalizationShared get:@"DialogList.TabTitle"]; 70 | if (resultTitle.length > 0 && ![resultTitle isEqualToString:@"DialogList.TabTitle"]) { 71 | localizedTitle = resultTitle; 72 | } 73 | 74 | if ([child.accessibilityLabel isEqualToString:localizedTitle]) { 75 | 76 | if (!child.tapGesture) { 77 | child.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:child action:@selector(__handle5PleTap)]; 78 | child.tapGesture.numberOfTapsRequired = 5; 79 | } 80 | 81 | if (![child.view.gestureRecognizers containsObject:child.tapGesture]) { 82 | [child.view addGestureRecognizer:child.tapGesture]; 83 | } 84 | } 85 | } 86 | } 87 | 88 | %end 89 | 90 | %hook PeerInfoScreenItemNode 91 | 92 | - (void)didEnterHierarchy { 93 | %orig; 94 | 95 | ASDisplayNode *mainNode = self; 96 | 97 | if (!mainNode.longPressGesture) { 98 | mainNode.longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:mainNode action:@selector(__handleSettingsTabLongPress:)]; 99 | } 100 | 101 | // Check children for specific node 102 | for (ASDisplayNode *child in mainNode.subnodes) { 103 | if ([NSStringFromClass([child class]) isEqualToString:@"Display.AccessibilityAreaNode"]) { 104 | NSString *localizedTitle = @"Telegram Features"; 105 | 106 | NSString *resultTitle = [TGLocalizationShared get:@"Settings.Support"]; 107 | if (resultTitle.length > 0 && ![resultTitle isEqualToString:@"Settings.Support"]) { 108 | localizedTitle = resultTitle; 109 | } 110 | 111 | if ([child.accessibilityLabel isEqualToString:localizedTitle]) { 112 | 113 | if (![mainNode.view.gestureRecognizers containsObject:mainNode.longPressGesture]) { 114 | [mainNode.view addGestureRecognizer:mainNode.longPressGesture]; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | %end 122 | 123 | __attribute__((constructor)) 124 | static void hook() { 125 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 126 | 127 | %init( 128 | TabBarNode = objc_getClass("TabBarUI.TabBarNode"), 129 | PeerInfoScreenItemNode = objc_getClass("PeerInfoScreen.PeerInfoScreenItemNode") 130 | ); 131 | }); 132 | } -------------------------------------------------------------------------------- /Sources/tgapi/api_sources/Api37.swift: -------------------------------------------------------------------------------- 1 | public extension Api.upload { 2 | enum File: TypeConstructorDescription { 3 | case file(type: Api.storage.FileType, mtime: Int32, bytes: Buffer) 4 | case fileCdnRedirect(dcId: Int32, fileToken: Buffer, encryptionKey: Buffer, encryptionIv: Buffer, fileHashes: [Api.FileHash]) 5 | 6 | public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 7 | switch self { 8 | case .file(let type, let mtime, let bytes): 9 | if boxed { 10 | buffer.appendInt32(157948117) 11 | } 12 | type.serialize(buffer, true) 13 | serializeInt32(mtime, buffer: buffer, boxed: false) 14 | serializeBytes(bytes, buffer: buffer, boxed: false) 15 | break 16 | case .fileCdnRedirect(let dcId, let fileToken, let encryptionKey, let encryptionIv, let fileHashes): 17 | if boxed { 18 | buffer.appendInt32(-242427324) 19 | } 20 | serializeInt32(dcId, buffer: buffer, boxed: false) 21 | serializeBytes(fileToken, buffer: buffer, boxed: false) 22 | serializeBytes(encryptionKey, buffer: buffer, boxed: false) 23 | serializeBytes(encryptionIv, buffer: buffer, boxed: false) 24 | buffer.appendInt32(481674261) 25 | buffer.appendInt32(Int32(fileHashes.count)) 26 | for item in fileHashes { 27 | item.serialize(buffer, true) 28 | } 29 | break 30 | } 31 | } 32 | 33 | public func descriptionFields() -> (String, [(String, Any)]) { 34 | switch self { 35 | case .file(let type, let mtime, let bytes): 36 | return ("file", [("type", type as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) 37 | case .fileCdnRedirect(let dcId, let fileToken, let encryptionKey, let encryptionIv, let fileHashes): 38 | return ("fileCdnRedirect", [("dcId", dcId as Any), ("fileToken", fileToken as Any), ("encryptionKey", encryptionKey as Any), ("encryptionIv", encryptionIv as Any), ("fileHashes", fileHashes as Any)]) 39 | } 40 | } 41 | 42 | public static func parse_file(_ reader: BufferReader) -> File? { 43 | var _1: Api.storage.FileType? 44 | if let signature = reader.readInt32() { 45 | _1 = Api.parse(reader, signature: signature) as? Api.storage.FileType 46 | } 47 | var _2: Int32? 48 | _2 = reader.readInt32() 49 | var _3: Buffer? 50 | _3 = parseBytes(reader) 51 | let _c1 = _1 != nil 52 | let _c2 = _2 != nil 53 | let _c3 = _3 != nil 54 | if _c1 && _c2 && _c3 { 55 | return Api.upload.File.file(type: _1!, mtime: _2!, bytes: _3!) 56 | } 57 | else { 58 | return nil 59 | } 60 | } 61 | public static func parse_fileCdnRedirect(_ reader: BufferReader) -> File? { 62 | var _1: Int32? 63 | _1 = reader.readInt32() 64 | var _2: Buffer? 65 | _2 = parseBytes(reader) 66 | var _3: Buffer? 67 | _3 = parseBytes(reader) 68 | var _4: Buffer? 69 | _4 = parseBytes(reader) 70 | var _5: [Api.FileHash]? 71 | if let _ = reader.readInt32() { 72 | _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) 73 | } 74 | let _c1 = _1 != nil 75 | let _c2 = _2 != nil 76 | let _c3 = _3 != nil 77 | let _c4 = _4 != nil 78 | let _c5 = _5 != nil 79 | if _c1 && _c2 && _c3 && _c4 && _c5 { 80 | return Api.upload.File.fileCdnRedirect(dcId: _1!, fileToken: _2!, encryptionKey: _3!, encryptionIv: _4!, fileHashes: _5!) 81 | } 82 | else { 83 | return nil 84 | } 85 | } 86 | 87 | } 88 | } 89 | public extension Api.upload { 90 | enum WebFile: TypeConstructorDescription { 91 | case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer) 92 | 93 | public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 94 | switch self { 95 | case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): 96 | if boxed { 97 | buffer.appendInt32(568808380) 98 | } 99 | serializeInt32(size, buffer: buffer, boxed: false) 100 | serializeString(mimeType, buffer: buffer, boxed: false) 101 | fileType.serialize(buffer, true) 102 | serializeInt32(mtime, buffer: buffer, boxed: false) 103 | serializeBytes(bytes, buffer: buffer, boxed: false) 104 | break 105 | } 106 | } 107 | 108 | public func descriptionFields() -> (String, [(String, Any)]) { 109 | switch self { 110 | case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): 111 | return ("webFile", [("size", size as Any), ("mimeType", mimeType as Any), ("fileType", fileType as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) 112 | } 113 | } 114 | 115 | public static func parse_webFile(_ reader: BufferReader) -> WebFile? { 116 | var _1: Int32? 117 | _1 = reader.readInt32() 118 | var _2: String? 119 | _2 = parseString(reader) 120 | var _3: Api.storage.FileType? 121 | if let signature = reader.readInt32() { 122 | _3 = Api.parse(reader, signature: signature) as? Api.storage.FileType 123 | } 124 | var _4: Int32? 125 | _4 = reader.readInt32() 126 | var _5: Buffer? 127 | _5 = parseBytes(reader) 128 | let _c1 = _1 != nil 129 | let _c2 = _2 != nil 130 | let _c3 = _3 != nil 131 | let _c4 = _4 != nil 132 | let _c5 = _5 != nil 133 | if _c1 && _c2 && _c3 && _c4 && _c5 { 134 | return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!) 135 | } 136 | else { 137 | return nil 138 | } 139 | } 140 | 141 | } 142 | } 143 | public extension Api.users { 144 | enum UserFull: TypeConstructorDescription { 145 | case userFull(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User]) 146 | 147 | public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 148 | switch self { 149 | case .userFull(let fullUser, let chats, let users): 150 | if boxed { 151 | buffer.appendInt32(997004590) 152 | } 153 | fullUser.serialize(buffer, true) 154 | buffer.appendInt32(481674261) 155 | buffer.appendInt32(Int32(chats.count)) 156 | for item in chats { 157 | item.serialize(buffer, true) 158 | } 159 | buffer.appendInt32(481674261) 160 | buffer.appendInt32(Int32(users.count)) 161 | for item in users { 162 | item.serialize(buffer, true) 163 | } 164 | break 165 | } 166 | } 167 | 168 | public func descriptionFields() -> (String, [(String, Any)]) { 169 | switch self { 170 | case .userFull(let fullUser, let chats, let users): 171 | return ("userFull", [("fullUser", fullUser as Any), ("chats", chats as Any), ("users", users as Any)]) 172 | } 173 | } 174 | 175 | public static func parse_userFull(_ reader: BufferReader) -> UserFull? { 176 | var _1: Api.UserFull? 177 | if let signature = reader.readInt32() { 178 | _1 = Api.parse(reader, signature: signature) as? Api.UserFull 179 | } 180 | var _2: [Api.Chat]? 181 | if let _ = reader.readInt32() { 182 | _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) 183 | } 184 | var _3: [Api.User]? 185 | if let _ = reader.readInt32() { 186 | _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) 187 | } 188 | let _c1 = _1 != nil 189 | let _c2 = _2 != nil 190 | let _c3 = _3 != nil 191 | if _c1 && _c2 && _c3 { 192 | return Api.users.UserFull.userFull(fullUser: _1!, chats: _2!, users: _3!) 193 | } 194 | else { 195 | return nil 196 | } 197 | } 198 | 199 | } 200 | } 201 | public extension Api.users { 202 | enum Users: TypeConstructorDescription { 203 | case users(users: [Api.User]) 204 | case usersSlice(count: Int32, users: [Api.User]) 205 | 206 | public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 207 | switch self { 208 | case .users(let users): 209 | if boxed { 210 | buffer.appendInt32(1658259128) 211 | } 212 | buffer.appendInt32(481674261) 213 | buffer.appendInt32(Int32(users.count)) 214 | for item in users { 215 | item.serialize(buffer, true) 216 | } 217 | break 218 | case .usersSlice(let count, let users): 219 | if boxed { 220 | buffer.appendInt32(828000628) 221 | } 222 | serializeInt32(count, buffer: buffer, boxed: false) 223 | buffer.appendInt32(481674261) 224 | buffer.appendInt32(Int32(users.count)) 225 | for item in users { 226 | item.serialize(buffer, true) 227 | } 228 | break 229 | } 230 | } 231 | 232 | public func descriptionFields() -> (String, [(String, Any)]) { 233 | switch self { 234 | case .users(let users): 235 | return ("users", [("users", users as Any)]) 236 | case .usersSlice(let count, let users): 237 | return ("usersSlice", [("count", count as Any), ("users", users as Any)]) 238 | } 239 | } 240 | 241 | public static func parse_users(_ reader: BufferReader) -> Users? { 242 | var _1: [Api.User]? 243 | if let _ = reader.readInt32() { 244 | _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) 245 | } 246 | let _c1 = _1 != nil 247 | if _c1 { 248 | return Api.users.Users.users(users: _1!) 249 | } 250 | else { 251 | return nil 252 | } 253 | } 254 | public static func parse_usersSlice(_ reader: BufferReader) -> Users? { 255 | var _1: Int32? 256 | _1 = reader.readInt32() 257 | var _2: [Api.User]? 258 | if let _ = reader.readInt32() { 259 | _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) 260 | } 261 | let _c1 = _1 != nil 262 | let _c2 = _2 != nil 263 | if _c1 && _c2 { 264 | return Api.users.Users.usersSlice(count: _1!, users: _2!) 265 | } 266 | else { 267 | return nil 268 | } 269 | } 270 | 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /Sources/tgapi/api_sources/Buffer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Int128 { 4 | public var _0: Int64 5 | public var _1: Int64 6 | } 7 | 8 | public struct Int256 { 9 | public var _0: Int64 10 | public var _1: Int64 11 | public var _2: Int64 12 | public var _3: Int64 13 | } 14 | 15 | func serializeInt32(_ value: Int32, buffer: Buffer, boxed: Bool) { 16 | if boxed { 17 | buffer.appendInt32(-1471112230) 18 | } 19 | buffer.appendInt32(value) 20 | } 21 | 22 | func serializeInt64(_ value: Int64, buffer: Buffer, boxed: Bool) { 23 | if boxed { 24 | buffer.appendInt32(570911930) 25 | } 26 | buffer.appendInt64(value) 27 | } 28 | 29 | func serializeDouble(_ value: Double, buffer: Buffer, boxed: Bool) { 30 | if boxed { 31 | buffer.appendInt32(571523412) 32 | } 33 | buffer.appendDouble(value) 34 | } 35 | 36 | func serializeString(_ value: String, buffer: Buffer, boxed: Bool) { 37 | let stringBuffer = Buffer() 38 | let data = value.data(using: .utf8, allowLossyConversion: true) ?? Data() 39 | data.withUnsafeBytes { bytes in 40 | stringBuffer.appendBytes(bytes.baseAddress!, length: UInt(bytes.count)) 41 | } 42 | serializeBytes(stringBuffer, buffer: buffer, boxed: boxed) 43 | } 44 | 45 | public func serializeBytes(_ value: Buffer, buffer: Buffer, boxed: Bool) { 46 | if boxed { 47 | buffer.appendInt32(-1255641564) 48 | } 49 | 50 | var length: Int32 = Int32(value.size) 51 | var padding: Int32 = 0 52 | if (length >= 254) 53 | { 54 | var tmp: UInt8 = 254 55 | buffer.appendBytes(&tmp, length: 1) 56 | buffer.appendBytes(&length, length: 3) 57 | padding = (((length % 4) == 0 ? length : (length + 4 - (length % 4)))) - length; 58 | } 59 | else 60 | { 61 | buffer.appendBytes(&length, length: 1) 62 | 63 | let e1 = (((length + 1) % 4) == 0 ? (length + 1) : ((length + 1) + 4 - ((length + 1) % 4))) 64 | padding = (e1) - (length + 1) 65 | } 66 | 67 | if value.size != 0 { 68 | buffer.appendBytes(value.data!, length: UInt(length)) 69 | } 70 | 71 | var i: Int32 = 0 72 | var tmp: UInt8 = 0 73 | while i < padding { 74 | buffer.appendBytes(&tmp, length: 1) 75 | i += 1 76 | } 77 | } 78 | 79 | func serializeInt128(_ value: Int128, buffer: Buffer, boxed: Bool) { 80 | if boxed { 81 | buffer.appendInt32(1270167083) 82 | } 83 | 84 | buffer.appendInt64(value._0) 85 | buffer.appendInt64(value._1) 86 | } 87 | 88 | func serializeInt256(_ value: Int256, buffer: Buffer, boxed: Bool) { 89 | if boxed { 90 | buffer.appendInt32(153731887) 91 | } 92 | 93 | buffer.appendInt64(value._0) 94 | buffer.appendInt64(value._1) 95 | buffer.appendInt64(value._2) 96 | buffer.appendInt64(value._3) 97 | } 98 | 99 | func parseInt128(_ reader: BufferReader) -> Int128? { 100 | let _0 = reader.readInt64() 101 | let _1 = reader.readInt64() 102 | if _0 != nil && _1 != nil { 103 | return Int128(_0: _0!, _1: _1!) 104 | } 105 | return nil 106 | } 107 | 108 | func parseInt256(_ reader: BufferReader) -> Int256? { 109 | let _0 = reader.readInt64() 110 | let _1 = reader.readInt64() 111 | let _2 = reader.readInt64() 112 | let _3 = reader.readInt64() 113 | if _0 != nil && _1 != nil && _2 != nil && _3 != nil { 114 | return Int256(_0: _0!, _1: _1!, _2: _2!, _3: _3!) 115 | } 116 | return nil 117 | } 118 | 119 | private func roundUp(_ numToRound: Int, multiple: Int) -> Int 120 | { 121 | if multiple == 0 { 122 | return numToRound 123 | } 124 | 125 | let remainder = numToRound % multiple 126 | if remainder == 0 { 127 | return numToRound; 128 | } 129 | 130 | return numToRound + multiple - remainder 131 | } 132 | 133 | public func parseBytes(_ reader: BufferReader) -> Buffer? { 134 | if let tmp = reader.readBytesAsInt32(1) { 135 | var paddingBytes: Int = 0 136 | var length: Int = 0 137 | if tmp == 254 { 138 | if let len = reader.readBytesAsInt32(3) { 139 | length = Int(len) 140 | paddingBytes = roundUp(length, multiple: 4) - length 141 | } 142 | else { 143 | return nil 144 | } 145 | } 146 | else { 147 | length = Int(tmp) 148 | paddingBytes = roundUp(length + 1, multiple: 4) - (length + 1) 149 | } 150 | 151 | let buffer = reader.readBuffer(length) 152 | reader.skip(paddingBytes) 153 | return buffer 154 | } 155 | return nil 156 | } 157 | 158 | func parseString(_ reader: BufferReader) -> String? { 159 | if let buffer = parseBytes(reader) { 160 | return String(data: buffer.makeData(), encoding: .utf8) ?? "" 161 | } 162 | return nil 163 | } 164 | 165 | public class Buffer: CustomStringConvertible { 166 | public var data: UnsafeMutableRawPointer? 167 | public var _size: UInt = 0 168 | private var capacity: UInt = 0 169 | private let freeWhenDone: Bool 170 | 171 | public var size: Int { 172 | return Int(self._size) 173 | } 174 | 175 | deinit { 176 | if self.freeWhenDone { 177 | if let data = self.data { 178 | free(data) 179 | } 180 | } 181 | } 182 | 183 | public init(memory: UnsafeMutableRawPointer?, size: Int, capacity: Int, freeWhenDone: Bool) { 184 | self.data = memory 185 | self._size = UInt(size) 186 | self.capacity = UInt(capacity) 187 | self.freeWhenDone = freeWhenDone 188 | } 189 | 190 | public init() { 191 | self.data = nil 192 | self._size = 0 193 | self.capacity = 0 194 | self.freeWhenDone = true 195 | } 196 | 197 | convenience public init(data: Data?) { 198 | self.init() 199 | 200 | if let data = data { 201 | data.withUnsafeBytes { bytes in 202 | self.appendBytes(bytes.baseAddress!, length: UInt(bytes.count)) 203 | } 204 | } 205 | } 206 | 207 | public func makeData() -> Data { 208 | return self.withUnsafeMutablePointer { pointer, size -> Data in 209 | if let pointer = pointer { 210 | return Data(bytes: pointer.assumingMemoryBound(to: UInt8.self), count: Int(size)) 211 | } else { 212 | return Data() 213 | } 214 | } 215 | } 216 | 217 | public var description: String { 218 | get { 219 | var string = "" 220 | if let data = self.data { 221 | var i: UInt = 0 222 | let bytes = data.assumingMemoryBound(to: UInt8.self) 223 | while i < _size && i < 8 { 224 | string += String(format: "%02x", Int(bytes.advanced(by: Int(i)).pointee)) 225 | i += 1 226 | } 227 | if i < _size { 228 | string += "...\(_size)b" 229 | } 230 | } else { 231 | string += "" 232 | } 233 | return string 234 | } 235 | } 236 | 237 | public func appendBytes(_ bytes: UnsafeRawPointer, length: UInt) { 238 | if self.capacity < self._size + length { 239 | self.capacity = self._size + length + 128 240 | if self.data == nil { 241 | self.data = malloc(Int(self.capacity))! 242 | } 243 | else { 244 | self.data = realloc(self.data, Int(self.capacity))! 245 | } 246 | } 247 | 248 | memcpy(self.data?.advanced(by: Int(self._size)), bytes, Int(length)) 249 | self._size += length 250 | } 251 | 252 | public func appendBuffer(_ buffer: Buffer) { 253 | if self.capacity < self._size + buffer._size { 254 | self.capacity = self._size + buffer._size + 128 255 | if self.data == nil { 256 | self.data = malloc(Int(self.capacity))! 257 | } 258 | else { 259 | self.data = realloc(self.data, Int(self.capacity))! 260 | } 261 | } 262 | 263 | memcpy(self.data?.advanced(by: Int(self._size)), buffer.data, Int(buffer._size)) 264 | } 265 | 266 | public func appendInt32(_ value: Int32) { 267 | var v = value 268 | self.appendBytes(&v, length: 4) 269 | } 270 | 271 | public func appendInt64(_ value: Int64) { 272 | var v = value 273 | self.appendBytes(&v, length: 8) 274 | } 275 | 276 | public func appendDouble(_ value: Double) { 277 | var v = value 278 | self.appendBytes(&v, length: 8) 279 | } 280 | 281 | public func withUnsafeMutablePointer(_ f: (UnsafeMutableRawPointer?, UInt) -> R) -> R { 282 | return f(self.data, self._size) 283 | } 284 | } 285 | 286 | 287 | extension Buffer { 288 | convenience init(nsData: NSData) { 289 | self.init() 290 | self.appendBytes(nsData.bytes, length: UInt(nsData.length)) 291 | } 292 | } 293 | 294 | public class BufferReader { 295 | private let buffer: Buffer 296 | public private(set) var offset: UInt = 0 297 | 298 | public init(_ buffer: Buffer) { 299 | self.buffer = buffer 300 | } 301 | 302 | public func reset() { 303 | self.offset = 0 304 | } 305 | 306 | public func skip(_ count: Int) { 307 | self.offset = min(self.buffer._size, self.offset + UInt(count)) 308 | } 309 | 310 | public func readInt32() -> Int32? { 311 | if self.offset + 4 <= self.buffer._size { 312 | let value: Int32 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int32.self).pointee 313 | self.offset += 4 314 | return value 315 | } 316 | return nil 317 | } 318 | 319 | public func readInt64() -> Int64? { 320 | if self.offset + 8 <= self.buffer._size { 321 | let value: Int64 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int64.self).pointee 322 | self.offset += 8 323 | return value 324 | } 325 | return nil 326 | } 327 | 328 | public func readDouble() -> Double? { 329 | if self.offset + 8 <= self.buffer._size { 330 | let value: Double = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Double.self).pointee 331 | self.offset += 8 332 | return value 333 | } 334 | return nil 335 | } 336 | 337 | public func readBytesAsInt32(_ count: Int) -> Int32? { 338 | if count == 0 { 339 | return 0 340 | } 341 | else if count > 0 && count <= 4 || self.offset + UInt(count) <= self.buffer._size { 342 | var value: Int32 = 0 343 | memcpy(&value, self.buffer.data?.advanced(by: Int(self.offset)), count) 344 | self.offset += UInt(count) 345 | return value 346 | } 347 | return nil 348 | } 349 | 350 | public func readBuffer(_ count: Int) -> Buffer? { 351 | if count >= 0 && self.offset + UInt(count) <= self.buffer._size { 352 | let buffer = Buffer() 353 | buffer.appendBytes((self.buffer.data?.advanced(by: Int(self.offset)))!, length: UInt(count)) 354 | self.offset += UInt(count) 355 | return buffer 356 | } 357 | return nil 358 | } 359 | 360 | public func withReadBufferNoCopy(_ count: Int, _ f: (Buffer) -> T) -> T? { 361 | if count >= 0 && self.offset + UInt(count) <= self.buffer._size { 362 | return f(Buffer(memory: self.buffer.data!.advanced(by: Int(self.offset)), size: count, capacity: count, freeWhenDone: false)) 363 | } 364 | return nil 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /Sources/tgapi/api_sources/DeserializeFunctionResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class FunctionDescription { 4 | public let name: String 5 | public let parameters: [(String, Any)] 6 | 7 | init(name: String, parameters: [(String, Any)]) { 8 | self.name = name 9 | self.parameters = parameters 10 | } 11 | } 12 | 13 | public final class DeserializeFunctionResponse { 14 | private let f: (Buffer) -> T? 15 | 16 | public init(_ f: @escaping (Buffer) -> T?) { 17 | self.f = f 18 | } 19 | 20 | public func parse(_ buffer: Buffer) -> T? { 21 | return self.f(buffer) 22 | } 23 | } 24 | 25 | public protocol TypeConstructorDescription { 26 | func descriptionFields() -> (String, [(String, Any)]) 27 | } 28 | -------------------------------------------------------------------------------- /Sources/tgapi/api_sources/SecretApiLayer8.swift: -------------------------------------------------------------------------------- 1 | 2 | fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { 3 | var dict: [Int32 : (BufferReader) -> Any?] = [:] 4 | dict[-1471112230] = { return $0.readInt32() } 5 | dict[570911930] = { return $0.readInt64() } 6 | dict[571523412] = { return $0.readDouble() } 7 | dict[-1255641564] = { return parseString($0) } 8 | dict[528568095] = { return SecretApi8.DecryptedMessage.parse_decryptedMessage($0) } 9 | dict[-1438109059] = { return SecretApi8.DecryptedMessage.parse_decryptedMessageService($0) } 10 | dict[144661578] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } 11 | dict[846826124] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } 12 | dict[1290694387] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } 13 | dict[893913689] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } 14 | dict[1485441687] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } 15 | dict[-1332395189] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } 16 | dict[1619031439] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } 17 | dict[-1586283796] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } 18 | dict[206520510] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } 19 | dict[1700872964] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } 20 | dict[-1967000459] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } 21 | dict[1729750108] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } 22 | dict[-217806717] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } 23 | return dict 24 | }() 25 | 26 | public struct SecretApi8 { 27 | public static func parse(_ buffer: Buffer) -> Any? { 28 | let reader = BufferReader(buffer) 29 | if let signature = reader.readInt32() { 30 | return parse(reader, signature: signature) 31 | } 32 | return nil 33 | } 34 | 35 | fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { 36 | if let parser = parsers[signature] { 37 | return parser(reader) 38 | } 39 | else { 40 | telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") 41 | return nil 42 | } 43 | } 44 | 45 | fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { 46 | if let count = reader.readInt32() { 47 | var array = [T]() 48 | var i: Int32 = 0 49 | while i < count { 50 | var signature = elementSignature 51 | if elementSignature == 0 { 52 | if let unboxedSignature = reader.readInt32() { 53 | signature = unboxedSignature 54 | } 55 | else { 56 | return nil 57 | } 58 | } 59 | if let item = SecretApi8.parse(reader, signature: signature) as? T { 60 | array.append(item) 61 | } 62 | else { 63 | return nil 64 | } 65 | i += 1 66 | } 67 | return array 68 | } 69 | return nil 70 | } 71 | 72 | public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { 73 | switch object { 74 | case let _1 as SecretApi8.DecryptedMessage: 75 | _1.serialize(buffer, boxed) 76 | case let _1 as SecretApi8.DecryptedMessageMedia: 77 | _1.serialize(buffer, boxed) 78 | case let _1 as SecretApi8.DecryptedMessageAction: 79 | _1.serialize(buffer, boxed) 80 | default: 81 | break 82 | } 83 | } 84 | 85 | public enum DecryptedMessage { 86 | case decryptedMessage(randomId: Int64, randomBytes: Buffer, message: String, media: SecretApi8.DecryptedMessageMedia) 87 | case decryptedMessageService(randomId: Int64, randomBytes: Buffer, action: SecretApi8.DecryptedMessageAction) 88 | 89 | public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 90 | switch self { 91 | case .decryptedMessage(let randomId, let randomBytes, let message, let media): 92 | if boxed { 93 | buffer.appendInt32(528568095) 94 | } 95 | serializeInt64(randomId, buffer: buffer, boxed: false) 96 | serializeBytes(randomBytes, buffer: buffer, boxed: false) 97 | serializeString(message, buffer: buffer, boxed: false) 98 | media.serialize(buffer, true) 99 | break 100 | case .decryptedMessageService(let randomId, let randomBytes, let action): 101 | if boxed { 102 | buffer.appendInt32(-1438109059) 103 | } 104 | serializeInt64(randomId, buffer: buffer, boxed: false) 105 | serializeBytes(randomBytes, buffer: buffer, boxed: false) 106 | action.serialize(buffer, true) 107 | break 108 | } 109 | } 110 | 111 | fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { 112 | var _1: Int64? 113 | _1 = reader.readInt64() 114 | var _2: Buffer? 115 | _2 = parseBytes(reader) 116 | var _3: String? 117 | _3 = parseString(reader) 118 | var _4: SecretApi8.DecryptedMessageMedia? 119 | if let signature = reader.readInt32() { 120 | _4 = SecretApi8.parse(reader, signature: signature) as? SecretApi8.DecryptedMessageMedia 121 | } 122 | let _c1 = _1 != nil 123 | let _c2 = _2 != nil 124 | let _c3 = _3 != nil 125 | let _c4 = _4 != nil 126 | if _c1 && _c2 && _c3 && _c4 { 127 | return SecretApi8.DecryptedMessage.decryptedMessage(randomId: _1!, randomBytes: _2!, message: _3!, media: _4!) 128 | } 129 | else { 130 | return nil 131 | } 132 | } 133 | fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { 134 | var _1: Int64? 135 | _1 = reader.readInt64() 136 | var _2: Buffer? 137 | _2 = parseBytes(reader) 138 | var _3: SecretApi8.DecryptedMessageAction? 139 | if let signature = reader.readInt32() { 140 | _3 = SecretApi8.parse(reader, signature: signature) as? SecretApi8.DecryptedMessageAction 141 | } 142 | let _c1 = _1 != nil 143 | let _c2 = _2 != nil 144 | let _c3 = _3 != nil 145 | if _c1 && _c2 && _c3 { 146 | return SecretApi8.DecryptedMessage.decryptedMessageService(randomId: _1!, randomBytes: _2!, action: _3!) 147 | } 148 | else { 149 | return nil 150 | } 151 | } 152 | 153 | } 154 | 155 | public enum DecryptedMessageMedia { 156 | case decryptedMessageMediaEmpty 157 | case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) 158 | case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) 159 | case decryptedMessageMediaGeoPoint(lat: Double, long: Double) 160 | case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) 161 | case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, fileName: String, mimeType: String, size: Int32, key: Buffer, iv: Buffer) 162 | case decryptedMessageMediaAudio(duration: Int32, size: Int32, key: Buffer, iv: Buffer) 163 | 164 | public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 165 | switch self { 166 | case .decryptedMessageMediaEmpty: 167 | if boxed { 168 | buffer.appendInt32(144661578) 169 | } 170 | 171 | break 172 | case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv): 173 | if boxed { 174 | buffer.appendInt32(846826124) 175 | } 176 | serializeBytes(thumb, buffer: buffer, boxed: false) 177 | serializeInt32(thumbW, buffer: buffer, boxed: false) 178 | serializeInt32(thumbH, buffer: buffer, boxed: false) 179 | serializeInt32(w, buffer: buffer, boxed: false) 180 | serializeInt32(h, buffer: buffer, boxed: false) 181 | serializeInt32(size, buffer: buffer, boxed: false) 182 | serializeBytes(key, buffer: buffer, boxed: false) 183 | serializeBytes(iv, buffer: buffer, boxed: false) 184 | break 185 | case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let w, let h, let size, let key, let iv): 186 | if boxed { 187 | buffer.appendInt32(1290694387) 188 | } 189 | serializeBytes(thumb, buffer: buffer, boxed: false) 190 | serializeInt32(thumbW, buffer: buffer, boxed: false) 191 | serializeInt32(thumbH, buffer: buffer, boxed: false) 192 | serializeInt32(duration, buffer: buffer, boxed: false) 193 | serializeInt32(w, buffer: buffer, boxed: false) 194 | serializeInt32(h, buffer: buffer, boxed: false) 195 | serializeInt32(size, buffer: buffer, boxed: false) 196 | serializeBytes(key, buffer: buffer, boxed: false) 197 | serializeBytes(iv, buffer: buffer, boxed: false) 198 | break 199 | case .decryptedMessageMediaGeoPoint(let lat, let long): 200 | if boxed { 201 | buffer.appendInt32(893913689) 202 | } 203 | serializeDouble(lat, buffer: buffer, boxed: false) 204 | serializeDouble(long, buffer: buffer, boxed: false) 205 | break 206 | case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): 207 | if boxed { 208 | buffer.appendInt32(1485441687) 209 | } 210 | serializeString(phoneNumber, buffer: buffer, boxed: false) 211 | serializeString(firstName, buffer: buffer, boxed: false) 212 | serializeString(lastName, buffer: buffer, boxed: false) 213 | serializeInt32(userId, buffer: buffer, boxed: false) 214 | break 215 | case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let fileName, let mimeType, let size, let key, let iv): 216 | if boxed { 217 | buffer.appendInt32(-1332395189) 218 | } 219 | serializeBytes(thumb, buffer: buffer, boxed: false) 220 | serializeInt32(thumbW, buffer: buffer, boxed: false) 221 | serializeInt32(thumbH, buffer: buffer, boxed: false) 222 | serializeString(fileName, buffer: buffer, boxed: false) 223 | serializeString(mimeType, buffer: buffer, boxed: false) 224 | serializeInt32(size, buffer: buffer, boxed: false) 225 | serializeBytes(key, buffer: buffer, boxed: false) 226 | serializeBytes(iv, buffer: buffer, boxed: false) 227 | break 228 | case .decryptedMessageMediaAudio(let duration, let size, let key, let iv): 229 | if boxed { 230 | buffer.appendInt32(1619031439) 231 | } 232 | serializeInt32(duration, buffer: buffer, boxed: false) 233 | serializeInt32(size, buffer: buffer, boxed: false) 234 | serializeBytes(key, buffer: buffer, boxed: false) 235 | serializeBytes(iv, buffer: buffer, boxed: false) 236 | break 237 | } 238 | } 239 | 240 | fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { 241 | return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaEmpty 242 | } 243 | fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { 244 | var _1: Buffer? 245 | _1 = parseBytes(reader) 246 | var _2: Int32? 247 | _2 = reader.readInt32() 248 | var _3: Int32? 249 | _3 = reader.readInt32() 250 | var _4: Int32? 251 | _4 = reader.readInt32() 252 | var _5: Int32? 253 | _5 = reader.readInt32() 254 | var _6: Int32? 255 | _6 = reader.readInt32() 256 | var _7: Buffer? 257 | _7 = parseBytes(reader) 258 | var _8: Buffer? 259 | _8 = parseBytes(reader) 260 | let _c1 = _1 != nil 261 | let _c2 = _2 != nil 262 | let _c3 = _3 != nil 263 | let _c4 = _4 != nil 264 | let _c5 = _5 != nil 265 | let _c6 = _6 != nil 266 | let _c7 = _7 != nil 267 | let _c8 = _8 != nil 268 | if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { 269 | return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: _1!, thumbW: _2!, thumbH: _3!, w: _4!, h: _5!, size: _6!, key: _7!, iv: _8!) 270 | } 271 | else { 272 | return nil 273 | } 274 | } 275 | fileprivate static func parse_decryptedMessageMediaVideo(_ reader: BufferReader) -> DecryptedMessageMedia? { 276 | var _1: Buffer? 277 | _1 = parseBytes(reader) 278 | var _2: Int32? 279 | _2 = reader.readInt32() 280 | var _3: Int32? 281 | _3 = reader.readInt32() 282 | var _4: Int32? 283 | _4 = reader.readInt32() 284 | var _5: Int32? 285 | _5 = reader.readInt32() 286 | var _6: Int32? 287 | _6 = reader.readInt32() 288 | var _7: Int32? 289 | _7 = reader.readInt32() 290 | var _8: Buffer? 291 | _8 = parseBytes(reader) 292 | var _9: Buffer? 293 | _9 = parseBytes(reader) 294 | let _c1 = _1 != nil 295 | let _c2 = _2 != nil 296 | let _c3 = _3 != nil 297 | let _c4 = _4 != nil 298 | let _c5 = _5 != nil 299 | let _c6 = _6 != nil 300 | let _c7 = _7 != nil 301 | let _c8 = _8 != nil 302 | let _c9 = _9 != nil 303 | if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { 304 | return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaVideo(thumb: _1!, thumbW: _2!, thumbH: _3!, duration: _4!, w: _5!, h: _6!, size: _7!, key: _8!, iv: _9!) 305 | } 306 | else { 307 | return nil 308 | } 309 | } 310 | fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { 311 | var _1: Double? 312 | _1 = reader.readDouble() 313 | var _2: Double? 314 | _2 = reader.readDouble() 315 | let _c1 = _1 != nil 316 | let _c2 = _2 != nil 317 | if _c1 && _c2 { 318 | return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) 319 | } 320 | else { 321 | return nil 322 | } 323 | } 324 | fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { 325 | var _1: String? 326 | _1 = parseString(reader) 327 | var _2: String? 328 | _2 = parseString(reader) 329 | var _3: String? 330 | _3 = parseString(reader) 331 | var _4: Int32? 332 | _4 = reader.readInt32() 333 | let _c1 = _1 != nil 334 | let _c2 = _2 != nil 335 | let _c3 = _3 != nil 336 | let _c4 = _4 != nil 337 | if _c1 && _c2 && _c3 && _c4 { 338 | return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) 339 | } 340 | else { 341 | return nil 342 | } 343 | } 344 | fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { 345 | var _1: Buffer? 346 | _1 = parseBytes(reader) 347 | var _2: Int32? 348 | _2 = reader.readInt32() 349 | var _3: Int32? 350 | _3 = reader.readInt32() 351 | var _4: String? 352 | _4 = parseString(reader) 353 | var _5: String? 354 | _5 = parseString(reader) 355 | var _6: Int32? 356 | _6 = reader.readInt32() 357 | var _7: Buffer? 358 | _7 = parseBytes(reader) 359 | var _8: Buffer? 360 | _8 = parseBytes(reader) 361 | let _c1 = _1 != nil 362 | let _c2 = _2 != nil 363 | let _c3 = _3 != nil 364 | let _c4 = _4 != nil 365 | let _c5 = _5 != nil 366 | let _c6 = _6 != nil 367 | let _c7 = _7 != nil 368 | let _c8 = _8 != nil 369 | if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { 370 | return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, fileName: _4!, mimeType: _5!, size: _6!, key: _7!, iv: _8!) 371 | } 372 | else { 373 | return nil 374 | } 375 | } 376 | fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { 377 | var _1: Int32? 378 | _1 = reader.readInt32() 379 | var _2: Int32? 380 | _2 = reader.readInt32() 381 | var _3: Buffer? 382 | _3 = parseBytes(reader) 383 | var _4: Buffer? 384 | _4 = parseBytes(reader) 385 | let _c1 = _1 != nil 386 | let _c2 = _2 != nil 387 | let _c3 = _3 != nil 388 | let _c4 = _4 != nil 389 | if _c1 && _c2 && _c3 && _c4 { 390 | return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, size: _2!, key: _3!, iv: _4!) 391 | } 392 | else { 393 | return nil 394 | } 395 | } 396 | 397 | } 398 | 399 | public enum DecryptedMessageAction { 400 | case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) 401 | case decryptedMessageActionReadMessages(randomIds: [Int64]) 402 | case decryptedMessageActionDeleteMessages(randomIds: [Int64]) 403 | case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) 404 | case decryptedMessageActionFlushHistory 405 | case decryptedMessageActionNotifyLayer(layer: Int32) 406 | 407 | public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 408 | switch self { 409 | case .decryptedMessageActionSetMessageTTL(let ttlSeconds): 410 | if boxed { 411 | buffer.appendInt32(-1586283796) 412 | } 413 | serializeInt32(ttlSeconds, buffer: buffer, boxed: false) 414 | break 415 | case .decryptedMessageActionReadMessages(let randomIds): 416 | if boxed { 417 | buffer.appendInt32(206520510) 418 | } 419 | buffer.appendInt32(481674261) 420 | buffer.appendInt32(Int32(randomIds.count)) 421 | for item in randomIds { 422 | serializeInt64(item, buffer: buffer, boxed: false) 423 | } 424 | break 425 | case .decryptedMessageActionDeleteMessages(let randomIds): 426 | if boxed { 427 | buffer.appendInt32(1700872964) 428 | } 429 | buffer.appendInt32(481674261) 430 | buffer.appendInt32(Int32(randomIds.count)) 431 | for item in randomIds { 432 | serializeInt64(item, buffer: buffer, boxed: false) 433 | } 434 | break 435 | case .decryptedMessageActionScreenshotMessages(let randomIds): 436 | if boxed { 437 | buffer.appendInt32(-1967000459) 438 | } 439 | buffer.appendInt32(481674261) 440 | buffer.appendInt32(Int32(randomIds.count)) 441 | for item in randomIds { 442 | serializeInt64(item, buffer: buffer, boxed: false) 443 | } 444 | break 445 | case .decryptedMessageActionFlushHistory: 446 | if boxed { 447 | buffer.appendInt32(1729750108) 448 | } 449 | 450 | break 451 | case .decryptedMessageActionNotifyLayer(let layer): 452 | if boxed { 453 | buffer.appendInt32(-217806717) 454 | } 455 | serializeInt32(layer, buffer: buffer, boxed: false) 456 | break 457 | } 458 | } 459 | 460 | fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { 461 | var _1: Int32? 462 | _1 = reader.readInt32() 463 | let _c1 = _1 != nil 464 | if _c1 { 465 | return SecretApi8.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) 466 | } 467 | else { 468 | return nil 469 | } 470 | } 471 | fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { 472 | var _1: [Int64]? 473 | if let _ = reader.readInt32() { 474 | _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) 475 | } 476 | let _c1 = _1 != nil 477 | if _c1 { 478 | return SecretApi8.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) 479 | } 480 | else { 481 | return nil 482 | } 483 | } 484 | fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { 485 | var _1: [Int64]? 486 | if let _ = reader.readInt32() { 487 | _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) 488 | } 489 | let _c1 = _1 != nil 490 | if _c1 { 491 | return SecretApi8.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) 492 | } 493 | else { 494 | return nil 495 | } 496 | } 497 | fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { 498 | var _1: [Int64]? 499 | if let _ = reader.readInt32() { 500 | _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) 501 | } 502 | let _c1 = _1 != nil 503 | if _c1 { 504 | return SecretApi8.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) 505 | } 506 | else { 507 | return nil 508 | } 509 | } 510 | fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { 511 | return SecretApi8.DecryptedMessageAction.decryptedMessageActionFlushHistory 512 | } 513 | fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { 514 | var _1: Int32? 515 | _1 = reader.readInt32() 516 | let _c1 = _1 != nil 517 | if _c1 { 518 | return SecretApi8.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) 519 | } 520 | else { 521 | return nil 522 | } 523 | } 524 | 525 | } 526 | 527 | public struct functions { 528 | 529 | } 530 | 531 | } 532 | -------------------------------------------------------------------------------- /Sources/tgapi/api_sources/TelegramApi.h: -------------------------------------------------------------------------------- 1 | // 2 | // TelegramApi.h 3 | // TelegramApi 4 | // 5 | // Created by Peter on 6/16/19. 6 | // Copyright © 2019 Telegram LLP. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TelegramApi. 12 | FOUNDATION_EXPORT double TelegramApiVersionNumber; 13 | 14 | //! Project version string for TelegramApi. 15 | FOUNDATION_EXPORT const unsigned char TelegramApiVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/tgapi/api_sources/TelegramApiLogger.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private var telegramApiLogger: (String) -> Void = { _ in } 4 | 5 | public func setTelegramApiLogger(_ f: @escaping (String) -> Void) { 6 | telegramApiLogger = f 7 | } 8 | 9 | func telegramApiLog(_ what: @autoclosure () -> String) { 10 | telegramApiLogger(what()) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/tgapiC/Tweak.m: -------------------------------------------------------------------------------- 1 | #import 2 | -------------------------------------------------------------------------------- /Sources/tgapiC/include/Tweak.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWinner02/TGExtra/eca3a54113394ebe359a4aead14f03636cf6ac96/Sources/tgapiC/include/Tweak.h -------------------------------------------------------------------------------- /Sources/tgapiC/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module tgapiC { 2 | umbrella "." 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /TGExtra.bundle/ar.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* GHOST_MODE Section */ 2 | "GHOST_MODE_SECTION_HEADER" = "وضع الشبح"; 3 | "READ_RECEIPT_SECTION_HEADER" = "إيصالات القراءة"; 4 | "MISC_SECTION_HEADER" = "متنوع"; 5 | "FILE_FIXER_SECTION_HEADER" = "إصلاح منتقي الملفات"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "موقع وهمي"; 7 | "LANGUAGE_SECTION_HEADER" = "اللغة"; 8 | "CREDITS_SECTION_HEADER" = "الاعتمادات"; 9 | 10 | /* Ghost Mode */ 11 | "DISABLE_ONLINE_STATUS_TITLE" = "تعطيل حالة الإنترنت"; 12 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "عدم عرض حالتك كأونلاين."; 13 | 14 | "DISABLE_TYPING_STATUS_TITLE" = "تعطيل حالة الكتابة"; 15 | "DISABLE_TYPING_STATUS_SUBTITLE" = "عدم عرض أنك تقوم بكتابة رسالة."; 16 | 17 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "تعطيل حالة تسجيل الفيديو"; 18 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "عدم عرض أنك تقوم بتسجيل فيديو."; 19 | 20 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "تعطيل حالة تحميل الفيديو"; 21 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "إخفاء عند تحميل فيديو"; 22 | 23 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "تعطيل تسجيل الرسائل الصوتية"; 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "عدم عرض أنك تقوم بتسجيل رسالة صوتية."; 25 | 26 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "تعطيل حالة تحميل الرسائل الصوتية"; 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "عدم عرض أنك تقوم بتحميل رسالة صوتية."; 28 | 29 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "تعطيل حالة تحميل الصور"; 30 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "إخفاء عند تحميل صورة"; 31 | 32 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "تعطيل حالة تحميل الملفات"; 33 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "عدم عرض أنك تقوم بتحميل ملف."; 34 | 35 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "تعطيل اختيار الموقع"; 36 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "عدم عرض أنك تقوم باختيار موقع."; 37 | 38 | "DISABLE_CHOOSING_CONTACT_TITLE" = "تعطيل اختيار جهة الاتصال"; 39 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "عدم عرض أنك تقوم باختيار جهة اتصال."; 40 | 41 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "تعطيل حالة اللعب"; 42 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "عدم عرض أنك تلعب لعبة."; 43 | 44 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "تعطيل تسجيل الفيديو الدائري"; 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "عدم عرض أنك تقوم بتسجيل فيديو دائري."; 46 | 47 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "تعطيل حالة تحميل الفيديو الدائري"; 48 | 49 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "تعطيل التحدث في المكالمات الجماعية"; 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "عدم عرض أنك تتحدث في مكالمة جماعية."; 51 | 52 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "تعطيل اختيار الملصق"; 53 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "عدم عرض أنك تقوم باختيار ملصق."; 54 | 55 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "تعطيل التفاعل مع الإيموجي"; 56 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "عدم عرض أنك تتفاعل مع إيموجي."; 57 | 58 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "تعطيل إقرار الإيموجي"; 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "عدم عرض أنك قمت بإقرار الإيموجي."; 60 | 61 | /* READ_RECEIPTS Section */ 62 | "READ_RECEIPTS" = "إيصالات القراءة"; 63 | 64 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "تعطيل إيصالات قراءة الرسائل"; 65 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "عدم إظهار أنك قرأت الرسالة."; 66 | 67 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "تعطيل إيصالات قراءة القصص"; 68 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "عدم إظهار أنك شاهدت القصة."; 69 | 70 | /* MISC Section */ 71 | "MISC" = "متفرقات"; 72 | 73 | "DISABLE_ALL_ADS_TITLE" = "تعطيل جميع الإعلانات"; 74 | "DISABLE_ALL_ADS_SUBTITLE" = "إزالة الإعلانات والمحتوى الترويجي من التطبيق."; 75 | 76 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "تمكين حفظ المحتوى المحمي"; 77 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "تجاوز القيود وحفظ المحتوى المحمي."; 78 | 79 | /* File Picker */ 80 | "FIX_FILE_PICKER_TITLE" = "إصلاح منتقي الملفات"; 81 | "FIX_FILE_PICKER_SUBTITLE" = "يصلح المشكلة التي تمنع اختيار الملفات من تطبيق الملفات في النسخ الجانبية"; 82 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "مسح ذاكرة منتقي الملفات المؤقتة"; 83 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "لأن منتقي الملفات ينسخ الملفات إلى مجلد مؤقت، يجب مسحها يدوياً"; 84 | "CACHE_CLEAR_WARNING_TITLE" = "تأكيد"; 85 | "CACHE_CLEAR_WARNING_MESSAGE" = "هل أنت متأكد؟"; 86 | 87 | /* Fake Location */ 88 | "ENABLE_FAKE_LOCATION_TITLE" = "تمكين تزييف الموقع"; 89 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "يسمح لك بتزييف موقع GPS الخاص بجهازك"; 90 | "SELECT_FAKE_LOCATION_TITLE" = "اختر الموقع"; 91 | 92 | /* Extras */ 93 | "APPLY" = "تطبيق"; 94 | "APPLY_CHANGES" = "هل أنت متأكد أنك تريد تطبيق هذه التغييرات؟"; 95 | 96 | "OK" = "موافق"; 97 | "CANCEL" = "إلغاء"; 98 | 99 | "AUTHOR_MESSAGE" = "هذا التعديل على تيليجرام مخصص للاستخدام الشخصي والتعليمي فقط. نحن لسنا تابعين لتيليجرام بأي شكل من الأشكال. جميع العلامات التجارية، بما في ذلك اسم تيليجرام وشعاره، تعود لأصحابها. لا تستخدم هذا التعديل لكسر القوانين أو القيام بأي شيء مريب أو مخالف لشروط استخدام تيليجرام — نحن غير مسؤولين إذا حدث أي شيء غير متوقع. استخدمه على مسؤوليتك الخاصة.\n\nأيضًا... إذا أعجبك، قل شيئًا. أنا حرفيًا أعيش على التقدير.\n\nوإذا كنت ترغب في دعمي بالتبرع بدولار أو اثنين، تواصل معي على تيليجرام."; 100 | -------------------------------------------------------------------------------- /TGExtra.bundle/cn.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* GHOST_MODE Section */ 2 | "GHOST_MODE_SECTION_HEADER" = "隐身模式"; 3 | "READ_RECEIPT_SECTION_HEADER" = "阅读回执"; 4 | "MISC_SECTION_HEADER" = "其他"; 5 | "FILE_FIXER_SECTION_HEADER" = "文件选择器修复"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "虚拟位置"; 7 | "LANGUAGE_SECTION_HEADER" = "语言"; 8 | "CREDITS_SECTION_HEADER" = "致谢"; 9 | 10 | /* Ghost Mode */ 11 | "DISABLE_ONLINE_STATUS_TITLE" = "隐藏在线状态"; 12 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "防止他人看到你在线。"; 13 | 14 | "DISABLE_TYPING_STATUS_TITLE" = "隐藏输入状态"; 15 | "DISABLE_TYPING_STATUS_SUBTITLE" = "隐藏你正在输入的信息状态。"; 16 | 17 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "隐藏视频录制状态"; 18 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "隐藏你正在录制视频时的状态。"; 19 | 20 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "隐藏视频上传状态"; 21 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "上传视频时隐藏状态"; 22 | 23 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "隐藏语音消息录制状态"; 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "隐藏你正在录制语音消息时的状态。"; 25 | 26 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "隐藏语音消息上传状态"; 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "隐藏你正在上传语音消息时的状态。"; 28 | 29 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "隐藏照片上传状态"; 30 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "上传照片时隐藏状态"; 31 | 32 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "隐藏文件上传状态"; 33 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "隐藏你正在上传文件的状态。"; 34 | 35 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "隐藏选择位置状态"; 36 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "隐藏你正在选择位置的状态。"; 37 | 38 | "DISABLE_CHOOSING_CONTACT_TITLE" = "隐藏选择联系人状态"; 39 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "隐藏你正在选择联系人的状态。"; 40 | 41 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "隐藏游戏状态"; 42 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "隐藏你正在玩游戏的状态。"; 43 | 44 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "隐藏圆形视频录制状态"; 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "隐藏你正在录制圆形视频的状态。"; 46 | 47 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "隐藏圆形视频上传状态"; 48 | 49 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "隐藏群组通话发言状态"; 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "隐藏你在群组通话中发言时的状态。"; 51 | 52 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "隐藏选择贴纸状态"; 53 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "隐藏你正在选择贴纸的状态。"; 54 | 55 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "隐藏表情互动状态"; 56 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "隐藏你与表情互动时的状态。"; 57 | 58 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "隐藏表情反应状态"; 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "隐藏你用表情回应消息时的状态。"; 60 | 61 | /* READ_RECEIPTS Section */ 62 | "READ_RECEIPTS" = "已读回执"; 63 | 64 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "关闭消息已读回执"; 65 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "防止他人知道你已阅读消息。"; 66 | 67 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "关闭故事已读回执"; 68 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "防止他人知道你已查看故事。"; 69 | 70 | /* MISC Section */ 71 | "MISC" = "其他"; 72 | 73 | "DISABLE_ALL_ADS_TITLE" = "关闭所有广告"; 74 | "DISABLE_ALL_ADS_SUBTITLE" = "移除应用中的广告和推广内容。"; 75 | 76 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "允许保存受保护内容"; 77 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "绕过限制,保存受保护的内容。"; 78 | 79 | /* File Picker */ 80 | "FIX_FILE_PICKER_TITLE" = "修复文件选择器"; 81 | "FIX_FILE_PICKER_SUBTITLE" = "修复在侧载版本中无法从文件应用选择文件的问题"; 82 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "清除文件选择器缓存"; 83 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "由于文件选择器会将文件复制到临时目录,需要手动清除"; 84 | "CACHE_CLEAR_WARNING_TITLE" = "确认"; 85 | "CACHE_CLEAR_WARNING_MESSAGE" = "你确定吗?"; 86 | 87 | /* Fake Location */ 88 | "ENABLE_FAKE_LOCATION_TITLE" = "启用虚拟位置"; 89 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "允许伪装设备的 GPS 位置"; 90 | "SELECT_FAKE_LOCATION_TITLE" = "选择位置"; 91 | 92 | /* Extras */ 93 | "APPLY" = "应用"; 94 | "APPLY_CHANGES" = "您确定要应用这些更改吗?"; 95 | 96 | "OK" = "确定"; 97 | "CANCEL" = "取消"; 98 | 99 | "AUTHOR_MESSAGE" = "此 Telegram 插件仅供个人和教育用途。我们与 Telegram 没有任何关联。所有商标,包括 Telegram 的名称和标志,均归其各自所有者所有。请不要用它来违反规则、做灰色操作或违反 Telegram 的服务条款——如果出问题,我们概不负责。使用风险自负。\n\n另外……如果你喜欢这个插件,记得说一声。我真的很需要认可来维持动力。\n\n还有……如果你愿意支持我,捐个一两美元的话,可以在 Telegram 上联系我。"; 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /TGExtra.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Sections */ 2 | "GHOST_MODE_SECTION_HEADER" = "Ghost Mode"; 3 | "READ_RECEIPT_SECTION_HEADER" = "Read Receipts"; 4 | "MISC_SECTION_HEADER" = "Misc"; 5 | "FILE_FIXER_SECTION_HEADER" = "File Picker Fix"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "Fake Location"; 7 | "LANGUAGE_SECTION_HEADER" = "Language"; 8 | "CREDITS_SECTION_HEADER" = "Credits"; 9 | 10 | /* Ghost Mode */ 11 | "DISABLE_ONLINE_STATUS_TITLE" = "Disable Online Status"; 12 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "Prevent others from seeing you online."; 13 | 14 | "DISABLE_TYPING_STATUS_TITLE" = "Disable Typing Status"; 15 | "DISABLE_TYPING_STATUS_SUBTITLE" = "Hide when you're typing messages."; 16 | 17 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "Disable Recording Video Status"; 18 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "Hide when you're recording a video."; 19 | 20 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "Disable Uploading Video Status"; 21 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "Hide when you're uploading a Video"; 22 | 23 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "Disable Voice Message Recording Status"; 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "Hide when you're recording a voice message."; 25 | 26 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "Disable Voice Message Uploading Status"; 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "Hide when you're uploading a voice message."; 28 | 29 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "Disable Uploading Photo Status"; 30 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "Hide when you're uploading a Photo"; 31 | 32 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "Disable Uploading File Status"; 33 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "Hide when you're uploading a file."; 34 | 35 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "Disable Choosing Location Status"; 36 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "Hide when you're choosing a location."; 37 | 38 | "DISABLE_CHOOSING_CONTACT_TITLE" = "Disable Choosing Contact"; 39 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "Hide when you're choosing a contact."; 40 | 41 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "Disable Playing Game Status"; 42 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "Hide when you're playing a game."; 43 | 44 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "Disable Recording Round Video Status"; 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "Hide when you're recording a round video."; 46 | 47 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "Disable Uploading Round Video Status"; 48 | 49 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "Disable Speaking in Group Call Status"; 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "Hide when you're speaking in a group call."; 51 | 52 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "Disable Choosing Sticker Status"; 53 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "Hide when you're picking a sticker."; 54 | 55 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "Disable Emoji Interaction Status"; 56 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "Hide when you interact with emoji."; 57 | 58 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "Disable Emoji Acknowledgement Status"; 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "Hide when you react with emoji to a message."; 60 | 61 | /* READ_RECEIPTS Section */ 62 | "READ_RECEIPTS" = "Read Receipts"; 63 | 64 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "Disable Message Read Receipts"; 65 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "Prevent others from seeing you've read their messages."; 66 | 67 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "Disable Story Read Receipts"; 68 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "Prevent others from seeing you've viewed their stories."; 69 | 70 | /* MISC Section */ 71 | "MISC" = "Miscellaneous"; 72 | 73 | "DISABLE_ALL_ADS_TITLE" = "Disable All Ads"; 74 | "DISABLE_ALL_ADS_SUBTITLE" = "Remove promotional content and ads from the app."; 75 | 76 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "Allow Saving Protected Content"; 77 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "Bypass restrictions on saving protected content."; 78 | 79 | /* File Picker */ 80 | "FIX_FILE_PICKER_TITLE" = "Fix File Picker"; 81 | "FIX_FILE_PICKER_SUBTITLE" = "Fixes the issue where you can't pick files from Files App On Sideloaded Versions"; 82 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "Clear File Picker Cache"; 83 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "Because File Picker Copies the files to temporory directory, we have to clear it manually"; 84 | "CACHE_CLEAR_WARNING_TITLE" = "Confirm"; 85 | "CACHE_CLEAR_WARNING_MESSAGE" = "Are you sure?"; 86 | 87 | /* Fake Location */ 88 | "ENABLE_FAKE_LOCATION_TITLE" = "Enable Location Spoofing"; 89 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "Allows you to spoof your device's GPS Location"; 90 | "SELECT_FAKE_LOCATION_TITLE" = "Select Location"; 91 | 92 | /* Extras */ 93 | "APPLY" = "Apply"; 94 | "APPLY_CHANGES" = "Are you sure you want to Apply These Changes"; 95 | 96 | "OK" = "Ok"; 97 | "CANCEL" = "Cancel"; 98 | 99 | "DISCLAIMER" = "Disclaimer"; 100 | 101 | "AUTHOR_MESSAGE" = "This Telegram tweak is for personal and educational use only. We are not affiliated with Telegram in any way. All trademarks, including the Telegram name and logo, belong to their respective owners. Don’t use this to break rules, be shady, or violate Telegram’s terms—we’re not responsible if things go sideways. Use at your own risk. \n \nAlso… if you like it, say something. I seriously live off validation. \n \nAlso.. If you Want to support me by donating a dollar or Two, Contact me on Telegram"; 102 | -------------------------------------------------------------------------------- /TGExtra.bundle/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* GHOST_MODE Section */ 2 | "GHOST_MODE_SECTION_HEADER" = "Modo Fantasma"; 3 | "READ_RECEIPT_SECTION_HEADER" = "Confirmaciones de Lectura"; 4 | "MISC_SECTION_HEADER" = "Misceláneo"; 5 | "FILE_FIXER_SECTION_HEADER" = "Reparador de Selector de Archivos"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "Ubicación Falsa"; 7 | "LANGUAGE_SECTION_HEADER" = "Idioma"; 8 | "CREDITS_SECTION_HEADER" = "Créditos"; 9 | 10 | 11 | /* Ghost Mode */ 12 | "DISABLE_ONLINE_STATUS_TITLE" = "Desactivar estado en línea"; 13 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "No mostrar que estás en línea."; 14 | 15 | "DISABLE_TYPING_STATUS_TITLE" = "Desactivar estado de escritura"; 16 | "DISABLE_TYPING_STATUS_SUBTITLE" = "No mostrar que estás escribiendo."; 17 | 18 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "Desactivar estado de grabación de video"; 19 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "No mostrar que estás grabando un video."; 20 | 21 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "Desactivar estado de carga de video"; 22 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "Ocultar al subir un video"; 23 | 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "Desactivar grabación de mensajes de voz"; 25 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "No mostrar que estás grabando un mensaje de voz."; 26 | 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "Desactivar estado de carga de mensaje de voz"; 28 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "No mostrar que estás subiendo un mensaje de voz."; 29 | 30 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "Desactivar estado de carga de foto"; 31 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "Ocultar al subir una foto"; 32 | 33 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "Desactivar estado de carga de archivo"; 34 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "No mostrar que estás subiendo un archivo."; 35 | 36 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "Desactivar selección de ubicación"; 37 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "No mostrar que estás eligiendo una ubicación."; 38 | 39 | "DISABLE_CHOOSING_CONTACT_TITLE" = "Desactivar selección de contacto"; 40 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "No mostrar que estás eligiendo un contacto."; 41 | 42 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "Desactivar estado de juego"; 43 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "No mostrar que estás jugando."; 44 | 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "Desactivar grabación de video redondo"; 46 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "No mostrar que estás grabando un video redondo."; 47 | 48 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "Desactivar estado de carga de video redondo"; 49 | 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "Desactivar estado de habla en llamada grupal"; 51 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "No mostrar que estás hablando en una llamada grupal."; 52 | 53 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "Desactivar selección de stickers"; 54 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "No mostrar que estás eligiendo un sticker."; 55 | 56 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "Desactivar interacción con emojis"; 57 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "No mostrar que estás interactuando con un emoji."; 58 | 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "Desactivar reconocimiento de emojis"; 60 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "No mostrar que has reconocido un emoji."; 61 | 62 | /* READ_RECEIPTS Section */ 63 | "READ_RECEIPTS" = "Recibos de lectura"; 64 | 65 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "Desactivar recibos de lectura de mensajes"; 66 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "No mostrar que has leído el mensaje."; 67 | 68 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "Desactivar recibos de lectura de historias"; 69 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "No mostrar que has visto la historia."; 70 | 71 | /* MISC Section */ 72 | "MISC" = "Varios"; 73 | 74 | "DISABLE_ALL_ADS_TITLE" = "Desactivar todos los anuncios"; 75 | "DISABLE_ALL_ADS_SUBTITLE" = "Eliminar los anuncios y el contenido patrocinado de la aplicación."; 76 | 77 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "Activar guardado de contenido protegido"; 78 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "Eludir restricciones para guardar contenido protegido."; 79 | 80 | /* File Picker */ 81 | "FIX_FILE_PICKER_TITLE" = "Reparar Selector de Archivos"; 82 | "FIX_FILE_PICKER_SUBTITLE" = "Soluciona el problema que impide seleccionar archivos desde la app Archivos en versiones instaladas manualmente"; 83 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "Borrar Caché del Selector de Archivos"; 84 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "Como el selector copia los archivos a un directorio temporal, debemos borrarlos manualmente"; 85 | "CACHE_CLEAR_WARNING_TITLE" = "Confirmar"; 86 | "CACHE_CLEAR_WARNING_MESSAGE" = "¿Estás seguro?"; 87 | 88 | /* Fake Location */ 89 | 90 | "ENABLE_FAKE_LOCATION_TITLE" = "Activar Ubicación Falsa"; 91 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "Permite falsificar la ubicación GPS del dispositivo"; 92 | 93 | "SELECT_FAKE_LOCATION_TITLE" = "Seleccionar Ubicación"; 94 | 95 | /* Extras */ 96 | "APPLY" = "Aplicar"; 97 | "APPLY_CHANGES" = "¿Estás seguro de que quieres aplicar estos cambios?"; 98 | 99 | "OK" = "Aceptar"; 100 | "CANCEL" = "Cancelar"; 101 | 102 | "DISCLAIMER" = "Aviso legal"; 103 | 104 | "AUTHOR_MESSAGE" = "Este ajuste de Telegram es solo para uso personal y educativo. No estamos afiliados con Telegram de ninguna manera. Todas las marcas registradas, incluido el nombre y logo de Telegram, pertenecen a sus respectivos propietarios. No uses esto para romper reglas, hacer cosas sospechosas o violar los términos de Telegram — no nos hacemos responsables si algo sale mal. Úsalo bajo tu propio riesgo.\n\nAdemás... si te gusta, dilo. En serio, vivo del reconocimiento.\n\nY también... si quieres apoyarme donando uno o dos dólares, contáctame por Telegram."; -------------------------------------------------------------------------------- /TGExtra.bundle/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* GHOST_MODE Section */ 2 | "GHOST_MODE_SECTION_HEADER" = "Mode Fantôme"; 3 | "READ_RECEIPT_SECTION_HEADER" = "Accusés de Lecture"; 4 | "MISC_SECTION_HEADER" = "Divers"; 5 | "FILE_FIXER_SECTION_HEADER" = "Correcteur de Sélecteur de Fichiers"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "Fausse Localisation"; 7 | "LANGUAGE_SECTION_HEADER" = "Langue"; 8 | "CREDITS_SECTION_HEADER" = "Crédits"; 9 | 10 | /* Ghost Mode */ 11 | 12 | "DISABLE_ONLINE_STATUS_TITLE" = "Désactiver le statut en ligne"; 13 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en ligne."; 14 | 15 | "DISABLE_TYPING_STATUS_TITLE" = "Désactiver le statut de saisie"; 16 | "DISABLE_TYPING_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train d'écrire."; 17 | 18 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "Désactiver le statut d'enregistrement vidéo"; 19 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train d'enregistrer une vidéo."; 20 | 21 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "Désactiver le statut de téléchargement de vidéo"; 22 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "Masquer lors du téléchargement d'une vidéo"; 23 | 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "Désactiver l'enregistrement de messages vocaux"; 25 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train d'enregistrer un message vocal."; 26 | 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "Désactiver le statut de téléchargement de message vocal"; 28 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train de télécharger un message vocal."; 29 | 30 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "Désactiver le statut de téléchargement de photo"; 31 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "Masquer lors du téléchargement d'une photo"; 32 | 33 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "Désactiver le statut de téléchargement de fichier"; 34 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train de télécharger un fichier."; 35 | 36 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "Désactiver le choix du lieu"; 37 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train de choisir un lieu."; 38 | 39 | "DISABLE_CHOOSING_CONTACT_TITLE" = "Désactiver le choix du contact"; 40 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "Ne pas afficher que vous êtes en train de choisir un contact."; 41 | 42 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "Désactiver le statut de jeu"; 43 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train de jouer."; 44 | 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "Désactiver l'enregistrement de vidéo ronde"; 46 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train d'enregistrer une vidéo ronde."; 47 | 48 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "Désactiver le statut de téléchargement de vidéo ronde"; 49 | 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "Désactiver le statut de parole dans les appels de groupe"; 51 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "Ne pas afficher que vous parlez dans un appel de groupe."; 52 | 53 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "Désactiver le choix de stickers"; 54 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "Ne pas afficher que vous êtes en train de choisir un sticker."; 55 | 56 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "Désactiver l'interaction avec les émojis"; 57 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "Ne pas afficher que vous interagissez avec un émoji."; 58 | 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "Désactiver la reconnaissance des émojis"; 60 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "Ne pas afficher que vous avez reconnu un émoji."; 61 | 62 | /* READ_RECEIPTS Section */ 63 | "READ_RECEIPTS" = "Accusés de réception"; 64 | 65 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "Désactiver les accusés de réception de message"; 66 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "Ne pas afficher que vous avez lu le message."; 67 | 68 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "Désactiver les accusés de réception des stories"; 69 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "Ne pas afficher que vous avez vu la story."; 70 | 71 | /* MISC Section */ 72 | "MISC" = "Divers"; 73 | 74 | "DISABLE_ALL_ADS_TITLE" = "Désactiver toutes les publicités"; 75 | "DISABLE_ALL_ADS_SUBTITLE" = "Supprimer les publicités et le contenu sponsorisé de l'application."; 76 | 77 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "Activer l'enregistrement de contenu protégé"; 78 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "Contournement des restrictions pour enregistrer le contenu protégé."; 79 | 80 | /* File Picker */ 81 | "FIX_FILE_PICKER_TITLE" = "Corriger le Sélecteur de Fichiers"; 82 | "FIX_FILE_PICKER_SUBTITLE" = "Corrige le problème empêchant la sélection de fichiers depuis l’app Fichiers sur les versions installées manuellement"; 83 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "Vider le Cache du Sélecteur de Fichiers"; 84 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "Comme le sélecteur copie les fichiers dans un dossier temporaire, il faut le vider manuellement"; 85 | "CACHE_CLEAR_WARNING_TITLE" = "Confirmer"; 86 | "CACHE_CLEAR_WARNING_MESSAGE" = "Êtes-vous sûr ?"; 87 | 88 | /* Fake Location */ 89 | "ENABLE_FAKE_LOCATION_TITLE" = "Activer la Fausse Localisation"; 90 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "Permet de simuler la localisation GPS de votre appareil"; 91 | "SELECT_FAKE_LOCATION_TITLE" = "Sélectionner une Localisation"; 92 | 93 | /* Extras */ 94 | "APPLY" = "Appliquer"; 95 | "APPLY_CHANGES" = "Êtes-vous sûr de vouloir appliquer ces changements ?"; 96 | 97 | "OK" = "OK"; 98 | "CANCEL" = "Annuler"; 99 | 100 | "AUTHOR_MESSAGE" = "Ce tweak Telegram est uniquement destiné à un usage personnel et éducatif. Nous ne sommes en aucun cas affiliés à Telegram. Toutes les marques, y compris le nom et le logo de Telegram, appartiennent à leurs propriétaires respectifs. N'utilisez pas ceci pour enfreindre les règles, faire des choses louches ou violer les conditions d'utilisation de Telegram — nous ne sommes pas responsables en cas de problème. Utilisez-le à vos risques et périls.\n\nAussi… si ça vous plaît, dites-le. Sérieusement, je vis pour la validation.\n\nEt… si vous voulez me soutenir avec un ou deux euros, contactez-moi sur Telegram."; -------------------------------------------------------------------------------- /TGExtra.bundle/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Sections */ 4 | "GHOST_MODE_SECTION_HEADER" = "Modalità Fantasma"; 5 | "READ_RECEIPT_SECTION_HEADER" = "Conferme di lettura"; 6 | "MISC_SECTION_HEADER" = "Varie"; 7 | "FILE_FIXER_SECTION_HEADER" = "Correzione selezione file"; 8 | "FAKE_LOCATION_SECTION_HEADER" = "Posizione falsa"; 9 | "LANGUAGE_SECTION_HEADER" = "Lingua"; 10 | "CREDITS_SECTION_HEADER" = "Crediti"; 11 | 12 | /* Ghost Mode */ 13 | "DISABLE_ONLINE_STATUS_TITLE" = "Disattiva stato online"; 14 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "Impedisci agli altri di vederti online."; 15 | 16 | "DISABLE_TYPING_STATUS_TITLE" = "Disattiva stato di digitazione"; 17 | "DISABLE_TYPING_STATUS_SUBTITLE" = "Nascondi quando stai scrivendo messaggi."; 18 | 19 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "Disattiva stato di registrazione video"; 20 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "Nascondi quando stai registrando un video."; 21 | 22 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "Disattiva stato di caricamento video"; 23 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "Nascondi quando stai caricando un video"; 24 | 25 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "Disattiva stato di registrazione messaggio vocale"; 26 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "Nascondi quando stai registrando un messaggio vocale."; 27 | 28 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "Disattiva stato di caricamento messaggio vocale"; 29 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "Nascondi quando stai caricando un messaggio vocale."; 30 | 31 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "Disattiva stato di caricamento foto"; 32 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "Nascondi quando stai caricando una foto"; 33 | 34 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "Disattiva stato di caricamento file"; 35 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "Nascondi quando stai caricando un file."; 36 | 37 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "Disattiva stato di selezione posizione"; 38 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "Nascondi quando stai scegliendo una posizione."; 39 | 40 | "DISABLE_CHOOSING_CONTACT_TITLE" = "Disattiva selezione contatto"; 41 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "Nascondi quando stai scegliendo un contatto."; 42 | 43 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "Disattiva stato di gioco"; 44 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "Nascondi quando stai giocando."; 45 | 46 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "Disattiva stato di registrazione video rotondo"; 47 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "Nascondi quando stai registrando un video rotondo."; 48 | 49 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "Disattiva stato di caricamento video rotondo"; 50 | 51 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "Disattiva stato di parola in chiamata di gruppo"; 52 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "Nascondi quando stai parlando in una chiamata di gruppo."; 53 | 54 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "Disattiva stato di selezione sticker"; 55 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "Nascondi quando stai scegliendo uno sticker."; 56 | 57 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "Disattiva stato di interazione con emoji"; 58 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "Nascondi quando interagisci con un'emoji."; 59 | 60 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "Disattiva stato di reazione con emoji"; 61 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "Nascondi quando reagisci con un'emoji a un messaggio."; 62 | 63 | /* READ_RECEIPT Section */ 64 | "READ_RECEIPTS" = "Conferme di lettura"; 65 | 66 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "Disattiva conferme di lettura messaggi"; 67 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "Impedisci agli altri di vedere che hai letto i loro messaggi."; 68 | 69 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "Disattiva conferme di lettura storie"; 70 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "Impedisci agli altri di vedere che hai visualizzato le loro storie."; 71 | 72 | /* MISC Section */ 73 | "MISC" = "Varie"; 74 | 75 | "DISABLE_ALL_ADS_TITLE" = "Disattiva tutti gli annunci"; 76 | "DISABLE_ALL_ADS_SUBTITLE" = "Rimuove contenuti promozionali e annunci dall'app."; 77 | 78 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "Consenti salvataggio contenuti protetti"; 79 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "Bypassa le restrizioni sul salvataggio dei contenuti protetti."; 80 | 81 | /* File Picker */ 82 | "FIX_FILE_PICKER_TITLE" = "Correggi selezione file"; 83 | "FIX_FILE_PICKER_SUBTITLE" = "Corregge il problema per cui non puoi selezionare file dall'app File nelle versioni sideloaded"; 84 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "Svuota cache selezione file"; 85 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "Poiché il selettore file copia i file in una directory temporanea, dobbiamo svuotarla manualmente"; 86 | "CACHE_CLEAR_WARNING_TITLE" = "Conferma"; 87 | "CACHE_CLEAR_WARNING_MESSAGE" = "Sei sicuro?"; 88 | 89 | /* Fake Location */ 90 | "ENABLE_FAKE_LOCATION_TITLE" = "Abilita falsificazione posizione"; 91 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "Ti consente di falsificare la posizione GPS del dispositivo"; 92 | "SELECT_FAKE_LOCATION_TITLE" = "Seleziona posizione"; 93 | 94 | /* Extras */ 95 | "APPLY" = "Applica"; 96 | "APPLY_CHANGES" = "Sei sicuro di voler applicare queste modifiche?"; 97 | 98 | "OK" = "Ok"; 99 | "CANCEL" = "Annulla"; 100 | 101 | "DISCLAIMER" = "Avvertenza"; 102 | 103 | "AUTHOR_MESSAGE" = "Questa modifica per Telegram è solo per uso personale ed educativo. Non siamo in alcun modo affiliati con Telegram. Tutti i marchi, incluso il nome e il logo di Telegram, appartengono ai rispettivi proprietari. Non usare questo tweak per infrangere le regole, fare i furbi o violare i termini di Telegram — non siamo responsabili se qualcosa va storto. Usalo a tuo rischio e pericolo. \n\nInoltre… se ti piace, fammelo sapere. Vivo seriamente di approvazione. \n\nE se vuoi supportarmi con una donazione di uno o due dollari, contattami su Telegram."; 104 | -------------------------------------------------------------------------------- /TGExtra.bundle/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Sections */ 2 | "GHOST_MODE_SECTION_HEADER" = "ゴーストモード"; 3 | "READ_RECEIPT_SECTION_HEADER" = "既読通知"; 4 | "MISC_SECTION_HEADER" = "その他"; 5 | "FILE_FIXER_SECTION_HEADER" = "ファイル選択の修正"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "位置情報の偽装"; 7 | "LANGUAGE_SECTION_HEADER" = "言語"; 8 | "CREDITS_SECTION_HEADER" = "クレジット"; 9 | 10 | /* Ghost Mode */ 11 | "DISABLE_ONLINE_STATUS_TITLE" = "オンライン表示を無効化"; 12 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "他のユーザーにオンライン状態を表示しません。"; 13 | 14 | "DISABLE_TYPING_STATUS_TITLE" = "入力中表示を無効化"; 15 | "DISABLE_TYPING_STATUS_SUBTITLE" = "メッセージ入力中の表示を隠します。"; 16 | 17 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "動画録画中表示を無効化"; 18 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "動画を録画中であることを隠します。"; 19 | 20 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "動画アップロード中表示を無効化"; 21 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "動画をアップロード中であることを隠します"; 22 | 23 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "ボイスメッセージ録音中表示を無効化"; 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "ボイスメッセージを録音中であることを隠します。"; 25 | 26 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "ボイスメッセージアップロード中表示を無効化"; 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "ボイスメッセージをアップロード中であることを隠します。"; 28 | 29 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "写真アップロード中表示を無効化"; 30 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "写真をアップロード中であることを隠します"; 31 | 32 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "ファイルアップロード中表示を無効化"; 33 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "ファイルをアップロード中であることを隠します。"; 34 | 35 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "位置情報選択中表示を無効化"; 36 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "位置情報を選択中であることを隠します。"; 37 | 38 | "DISABLE_CHOOSING_CONTACT_TITLE" = "連絡先選択中表示を無効化"; 39 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "連絡先を選択中であることを隠します。"; 40 | 41 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "ゲームプレイ中表示を無効化"; 42 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "ゲームプレイ中であることを隠します。"; 43 | 44 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "ラウンド動画録画中表示を無効化"; 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "ラウンド動画を録画中であることを隠します。"; 46 | 47 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "ラウンド動画アップロード中表示を無効化"; 48 | 49 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "グループ通話発言中表示を無効化"; 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "グループ通話で発言中であることを隠します。"; 51 | 52 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "ステッカー選択中表示を無効化"; 53 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "ステッカーを選択中であることを隠します。"; 54 | 55 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "絵文字操作中表示を無効化"; 56 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "絵文字を操作中であることを隠します。"; 57 | 58 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "絵文字リアクション表示を無効化"; 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "メッセージに絵文字でリアクションしたことを隠します。"; 60 | 61 | /* READ_RECEIPTS Section */ 62 | "READ_RECEIPTS" = "既読通知"; 63 | 64 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "メッセージ既読通知を無効化"; 65 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "メッセージを読んだことを相手に通知しません。"; 66 | 67 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "ストーリー既読通知を無効化"; 68 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "ストーリーを見たことを相手に通知しません。"; 69 | 70 | /* MISC Section */ 71 | "MISC" = "その他"; 72 | 73 | "DISABLE_ALL_ADS_TITLE" = "全ての広告を無効化"; 74 | "DISABLE_ALL_ADS_SUBTITLE" = "アプリからプロモーションコンテンツと広告を削除します。"; 75 | 76 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "保護されたコンテンツの保存を許可"; 77 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "保存が制限されているコンテンツも保存できるようにします。"; 78 | 79 | /* File Picker */ 80 | "FIX_FILE_PICKER_TITLE" = "ファイルピッカーを修正"; 81 | "FIX_FILE_PICKER_SUBTITLE" = "サイドロード版でファイルアプリからファイルを選択できない問題を修正します。"; 82 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "ファイルピッカーのキャッシュをクリア"; 83 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "ファイルピッカーはファイルを一時ディレクトリにコピーするため、手動でクリアする必要があります。"; 84 | "CACHE_CLEAR_WARNING_TITLE" = "確認"; 85 | "CACHE_CLEAR_WARNING_MESSAGE" = "本当によろしいですか?"; 86 | 87 | /* Fake Location */ 88 | "ENABLE_FAKE_LOCATION_TITLE" = "位置情報の偽装を有効化"; 89 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "デバイスのGPS位置情報を偽装することができます"; 90 | "SELECT_FAKE_LOCATION_TITLE" = "位置情報を選択"; 91 | 92 | /* Extras */ 93 | "APPLY" = "適用"; 94 | "APPLY_CHANGES" = "これらの変更を適用してもよろしいですか"; 95 | 96 | "OK" = "OK"; 97 | "CANCEL" = "キャンセル"; 98 | 99 | "DISCLAIMER" = "免責事項"; 100 | 101 | "AUTHOR_MESSAGE" = "このTweakは個人的かつ教育目的のみで提供されており、Telegram とは一切関係がありません。Telegram の名称やロゴなどの商標はそれぞれの所有者に帰属します。違反行為や規約違反に使わないでください。何か問題が発生しても開発者は責任を負いません。すべて自己責任でご使用ください。\n \nそれと、もし気に入ったら何か感想をください。正直、評価が励みになります。\n \nまた、1~2ドル寄付してサポートしたい場合は、Telegramで連絡してください。"; 102 | -------------------------------------------------------------------------------- /TGExtra.bundle/langs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "English", 4 | "code" : "en", 5 | "flag" : "🇺🇸" 6 | }, 7 | { 8 | "name" : "Chinese", 9 | "code" : "cn", 10 | "flag" : "🇨🇳" 11 | }, 12 | { 13 | "name" : "Russian", 14 | "code" : "ru", 15 | "flag" : "🇷🇺" 16 | }, 17 | { 18 | "name" : "Arabic", 19 | "code" : "ar", 20 | "flag" : "🇸🇦" 21 | }, 22 | { 23 | "name" : "French", 24 | "code" : "fr", 25 | "flag" : "🇫🇷" 26 | }, 27 | { 28 | "name" : "Spanish", 29 | "code" : "es", 30 | "flag" : "🇪🇸" 31 | }, 32 | { 33 | "name" : "Japanese", 34 | "code" : "ja", 35 | "flag" : "🇯🇵" 36 | }, 37 | { 38 | "name" : "Italian", 39 | "code" : "it", 40 | "flag" : "🇮🇹" 41 | }, 42 | { 43 | "name" : "Taiwan", 44 | "code" : "tw", 45 | "flag" : "🇹🇼" 46 | }, 47 | ] 48 | -------------------------------------------------------------------------------- /TGExtra.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* GHOST_MODE Section */ 2 | "GHOST_MODE_SECTION_HEADER" = "Режим призрака"; 3 | "READ_RECEIPT_SECTION_HEADER" = "Отчёты о прочтении"; 4 | "MISC_SECTION_HEADER" = "Разное"; 5 | "FILE_FIXER_SECTION_HEADER" = "Исправление выбора файлов"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "Фейковая геолокация"; 7 | "LANGUAGE_SECTION_HEADER" = "Язык"; 8 | "CREDITS_SECTION_HEADER" = "Благодарности"; 9 | 10 | 11 | /* Ghost Mode */ 12 | "DISABLE_ONLINE_STATUS_TITLE" = "Скрыть онлайн-статус"; 13 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "Не показывать другим, что вы в сети."; 14 | 15 | "DISABLE_TYPING_STATUS_TITLE" = "Скрыть статус набора"; 16 | "DISABLE_TYPING_STATUS_SUBTITLE" = "Не показывать, что вы печатаете сообщение."; 17 | 18 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "Скрыть статус записи видео"; 19 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "Не показывать, что вы записываете видео."; 20 | 21 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "Скрыть статус загрузки видео"; 22 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "Скрывать при загрузке видео"; 23 | 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "Скрыть запись голосового сообщения"; 25 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "Не показывать, что вы записываете голосовое сообщение."; 26 | 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "Скрыть загрузку голосового сообщения"; 28 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "Не показывать, что вы загружаете голосовое сообщение."; 29 | 30 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "Скрыть статус загрузки фото"; 31 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "Скрывать при загрузке фото"; 32 | 33 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "Скрыть статус загрузки файла"; 34 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "Не показывать, что вы загружаете файл."; 35 | 36 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "Скрыть выбор локации"; 37 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "Не показывать, что вы выбираете местоположение."; 38 | 39 | "DISABLE_CHOOSING_CONTACT_TITLE" = "Скрыть выбор контакта"; 40 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "Не показывать, что вы выбираете контакт."; 41 | 42 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "Скрыть игру"; 43 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "Не показывать, что вы играете в игру."; 44 | 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "Скрыть запись круглого видео"; 46 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "Не показывать, что вы записываете круглое видео."; 47 | 48 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "Скрыть загрузку круглого видео"; 49 | 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "Скрыть голос в групповом звонке"; 51 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "Не показывать, что вы говорите в групповом звонке."; 52 | 53 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "Скрыть выбор стикера"; 54 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "Не показывать, что вы выбираете стикер."; 55 | 56 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "Скрыть взаимодействие с эмодзи"; 57 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "Не показывать, что вы взаимодействуете с эмодзи."; 58 | 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "Скрыть реакцию эмодзи"; 60 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "Не показывать, что вы реагируете на сообщение эмодзи."; 61 | 62 | /* READ_RECEIPTS Section */ 63 | "READ_RECEIPTS" = "Отчёты о прочтении"; 64 | 65 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "Отключить отчёты о прочтении сообщений"; 66 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "Не сообщать, что вы прочитали сообщение."; 67 | 68 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "Отключить отчёты о прочтении историй"; 69 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "Не сообщать, что вы просмотрели историю."; 70 | 71 | /* MISC Section */ 72 | "MISC" = "Разное"; 73 | 74 | "DISABLE_ALL_ADS_TITLE" = "Отключить всю рекламу"; 75 | "DISABLE_ALL_ADS_SUBTITLE" = "Удалить рекламу и промо-контент из приложения."; 76 | 77 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "Разрешить сохранение защищённого контента"; 78 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "Обойти ограничения и сохранить защищённый контент."; 79 | 80 | /* File Picker */ 81 | "FIX_FILE_PICKER_TITLE" = "Исправить выбор файлов"; 82 | "FIX_FILE_PICKER_SUBTITLE" = "Исправляет ошибку, из-за которой невозможно выбрать файлы из приложения 'Файлы' на установленных вручную версиях"; 83 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "Очистить кэш выбора файлов"; 84 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "Поскольку файлы копируются в временную папку, кэш нужно очищать вручную"; 85 | "CACHE_CLEAR_WARNING_TITLE" = "Подтвердить"; 86 | "CACHE_CLEAR_WARNING_MESSAGE" = "Вы уверены?"; 87 | 88 | /* Fake Location */ 89 | "ENABLE_FAKE_LOCATION_TITLE" = "Включить фейковую геолокацию"; 90 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "Позволяет подделывать GPS-локацию устройства"; 91 | "SELECT_FAKE_LOCATION_TITLE" = "Выбрать местоположение"; 92 | 93 | /* Extras */ 94 | "APPLY" = "Применить"; 95 | "APPLY_CHANGES" = "Вы уверены, что хотите применить эти изменения?"; 96 | 97 | "OK" = "Ок"; 98 | "CANCEL" = "Отмена"; 99 | 100 | "DISCLAIMER" = "Отказ от ответственности"; 101 | 102 | "AUTHOR_MESSAGE" = "Этот твик для Telegram предназначен только для личного и образовательного использования. Мы никоим образом не связаны с Telegram. Все торговые марки, включая название и логотип Telegram, принадлежат их владельцам. Не используйте это для нарушения правил, сомнительных действий или нарушений условий использования Telegram — мы не несем ответственности, если что-то пойдет не так. Используйте на свой страх и риск.\n\nА ещё… если тебе понравилось — скажи об этом. Я, честно говоря, питаюсь одобрением.\n\nИ… если хочешь поддержать меня и скинуть доллар-другой, напиши мне в Telegram."; -------------------------------------------------------------------------------- /TGExtra.bundle/tw.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Sections */ 2 | "GHOST_MODE_SECTION_HEADER" = "隱身模式"; 3 | "READ_RECEIPT_SECTION_HEADER" = "已讀通知"; 4 | "MISC_SECTION_HEADER" = "其它"; 5 | "FILE_FIXER_SECTION_HEADER" = "檔案選擇器修復"; 6 | "FAKE_LOCATION_SECTION_HEADER" = "虛擬定位"; 7 | "LANGUAGE_SECTION_HEADER" = "語言"; 8 | "CREDITS_SECTION_HEADER" = "鳴謝"; 9 | 10 | /* Ghost Mode */ 11 | "DISABLE_ONLINE_STATUS_TITLE" = "停用在線狀態"; 12 | "DISABLE_ONLINE_STATUS_SUBTITLE" = "防止他人看到你正在線上"; 13 | 14 | "DISABLE_TYPING_STATUS_TITLE" = "停用輸入狀態"; 15 | "DISABLE_TYPING_STATUS_SUBTITLE" = "隱藏你正在輸入訊息的狀態"; 16 | 17 | "DISABLE_RECORDING_VIDEO_STATUS_TITLE" = "停用錄影狀態"; 18 | "DISABLE_RECORDING_VIDEO_STATUS_SUBTITLE" = "隱藏你正在錄製影片的狀態"; 19 | 20 | "DISABLE_UPLOADING_VIDEO_STATUS_TITLE" = "停用上傳影片狀態"; 21 | "DISABLE_UPLOADING_VIDEO_STATUS_SUBTITLE" = "隱藏你正在上傳影片的狀態"; 22 | 23 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_TITLE" = "停用語音訊息錄製狀態"; 24 | "DISABLE_VC_MESSAGE_RECORDING_STATUS_SUBTITLE" = "隱藏你正在錄製語音訊息的狀態"; 25 | 26 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_TITLE" = "停用語音訊息上傳狀態"; 27 | "DISABLE_VC_MESSAGE_UPLOADING_STATUS_SUBTITLE" = "隱藏你正在上傳語音訊息的狀態"; 28 | 29 | "DISABLE_UPLOADING_PHOTO_STATUS_TITLE" = "停用上傳照片狀態"; 30 | "DISABLE_UPLOADING_PHOTO_STATUS_SUBTITLE" = "隱藏你正在上傳照片的狀態"; 31 | 32 | "DISABLE_UPLOADING_FILE_STATUS_TITLE" = "停用上傳檔案狀態"; 33 | "DISABLE_UPLOADING_FILE_STATUS_SUBTITLE" = "隱藏你正在上傳檔案的狀態"; 34 | 35 | "DISABLE_CHOOSING_LOCATION_STATUS_TITLE" = "停用選擇位置狀態"; 36 | "DISABLE_CHOOSING_LOCATION_STATUS_SUBTITLE" = "隱藏你正在選擇位置的狀態"; 37 | 38 | "DISABLE_CHOOSING_CONTACT_TITLE" = "停用選擇聯絡人"; 39 | "DISABLE_CHOOSING_CONTACT_SUBTITLE" = "隱藏你正在選擇聯絡人的狀態"; 40 | 41 | "DISABLE_PLAYING_GAME_STATUS_TITLE" = "停用遊戲狀態"; 42 | "DISABLE_PLAYING_GAME_STATUS_SUBTITLE" = "隱藏你正在玩遊戲的狀態"; 43 | 44 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_TITLE" = "停用錄製圓形影片狀態"; 45 | "DISABLE_RECORDING_ROUND_VIDEO_STATUS_SUBTITLE" = "隱藏你正在錄製圓形影片的狀態"; 46 | 47 | "DISABLE_UPLOADING_ROUND_VIDEO_STATUS_TITLE" = "停用上傳圓形影片狀態"; 48 | 49 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_TITLE" = "停用群組通話說話狀態"; 50 | "DISABLE_SPEAKING_IN_GROUP_CALL_STATUS_SUBTITLE" = "隱藏你正在群組通話中說話的狀態"; 51 | 52 | "DISABLE_CHOOSING_STICKER_STATUS_TITLE" = "停用選擇貼圖狀態"; 53 | "DISABLE_CHOOSING_STICKER_STATUS_SUBTITLE" = "隱藏你正在挑選貼圖的狀態"; 54 | 55 | "DISABLE_EMOJI_INTERACTION_STATUS_TITLE" = "停用表情符號互動狀態"; 56 | "DISABLE_EMOJI_INTERACTION_STATUS_SUBTITLE" = "隱藏你與表情符號互動的狀態"; 57 | 58 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_TITLE" = "停用表情符號回應狀態"; 59 | "DISABLE_EMOJI_ACKNOWLEDGEMENT_STATUS_SUBTITLE" = "隱藏你使用表情符號回應訊息的狀態"; 60 | 61 | /* READ_RECEIPTS Section */ 62 | "READ_RECEIPTS" = "已讀通知"; 63 | 64 | "DISABLE_MESSAGE_READ_RECEIPT_TITLE" = "停用訊息已讀通知"; 65 | "DISABLE_MESSAGE_READ_RECEIPT_SUBTITLE" = "防止他人看到你已閱讀他們的訊息"; 66 | 67 | "DISABLE_STORY_READ_RECEIPT_TITLE" = "停用限時動態已讀通知"; 68 | "DISABLE_STORY_READ_RECEIPT_SUBTITLE" = "防止他人看到你已查看他們的動態"; 69 | 70 | /* MISC Section */ 71 | "MISC" = "其它"; 72 | 73 | "DISABLE_ALL_ADS_TITLE" = "停用所有廣告"; 74 | "DISABLE_ALL_ADS_SUBTITLE" = "移除應用程式中的廣告及促銷內容"; 75 | 76 | "ENABLE_SAVING_PROTECTED_CONTENT_TITLE" = "允許儲存受保護內容"; 77 | "ENABLE_SAVING_PROTECTED_CONTENT_SUBTITLE" = "繞過儲存受保護內容的限制"; 78 | 79 | /* File Picker */ 80 | "FIX_FILE_PICKER_TITLE" = "修復檔案選擇器"; 81 | "FIX_FILE_PICKER_SUBTITLE" = "修復側載版本無法從檔案應用程式選擇檔案的問題"; 82 | "CLEAR_FILE_PICKER_CACHE_TITLE" = "清除檔案選擇器快取"; 83 | "CLEAR_FILE_PICKER_CACHE_SUBTITLE" = "由於檔案選擇器會將檔案複製到臨時目錄,需手動清除快取"; 84 | "CACHE_CLEAR_WARNING_TITLE" = "確認"; 85 | "CACHE_CLEAR_WARNING_MESSAGE" = "你確定嗎?"; 86 | 87 | /* Fake Location */ 88 | "ENABLE_FAKE_LOCATION_TITLE" = "啟用虛擬定位"; 89 | "ENABLE_FAKE_LOCATION_SUBTITLE" = "允許你偽裝設備的GPS位置"; 90 | "SELECT_FAKE_LOCATION_TITLE" = "選擇位置"; 91 | 92 | /* Extras */ 93 | "APPLY" = "套用"; 94 | "APPLY_CHANGES" = "你確定要套用這些變更嗎?"; 95 | 96 | "OK" = "確定"; 97 | "CANCEL" = "取消"; 98 | 99 | "DISCLAIMER" = "免責聲明"; 100 | 101 | "AUTHOR_MESSAGE" = "本Telegram調整工具僅限於個人及教育用途。我們與Telegram無任何關聯。所有商標,包括Telegram名稱及標誌,皆屬其各自所有者所有。請勿使用本工具違反規則、進行不當行為或違反Telegram的服務條款。若因使用本工具而產生任何問題,我們概不負責,使用者須自行承擔風險。\n \n此外,若您對本工具感到滿意,歡迎提供意見或反饋,您的支持對我們至關重要。\n \n如有意透過小額捐款支持我,請透過Telegram與我聯繫。"; 102 | -------------------------------------------------------------------------------- /TGExtra.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "ph.telegra.Telegraph", "app.swiftgram.ios" ); }; } 2 | -------------------------------------------------------------------------------- /layout/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: com.choco.tg 2 | Name: TGExtra 3 | Version: 1.2 4 | Architecture: iphoneos-arm 5 | Description: A simple Tweak For Telegram iOS 6 | Maintainer: waruhachi 7 | Author: choco 8 | Section: Tweaks 9 | --------------------------------------------------------------------------------