├── .github ├── dependabot.yml └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── AndroidProjectSystem.xml ├── appInsightsSettings.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── Theme.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── business │ └── res │ │ └── values │ │ └── arrays.xml │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── wmods │ │ └── wppenhacer │ │ └── xposed │ │ └── bridge │ │ └── WaeIIFace.aidl │ ├── assets │ ├── css_editor.html │ ├── www │ │ ├── index.js │ │ ├── prism-live.css │ │ ├── prism.css │ │ ├── prism.js │ │ └── src │ │ │ ├── defaults.js │ │ │ ├── dom.js │ │ │ ├── editing.js │ │ │ ├── env.js │ │ │ ├── languages │ │ │ ├── css.mjs │ │ │ ├── javascript.mjs │ │ │ └── markup.mjs │ │ │ ├── prism-live.js │ │ │ ├── prism-live.mjs │ │ │ └── util.js │ └── xposed_init │ ├── java │ └── com │ │ └── wmods │ │ └── wppenhacer │ │ ├── App.java │ │ ├── UpdateChecker.java │ │ ├── WppXposed.java │ │ ├── activities │ │ ├── AboutActivity.java │ │ ├── ForceStartActivity.java │ │ ├── MainActivity.java │ │ ├── TextEditorActivity.java │ │ └── base │ │ │ └── BaseActivity.java │ │ ├── adapter │ │ ├── CustomPrivacyAdapter.java │ │ ├── IGStatusAdapter.java │ │ ├── MainPagerAdapter.java │ │ └── MessageAdapter.java │ │ ├── listeners │ │ └── OnMultiClickListener.java │ │ ├── preference │ │ ├── ContactPickerPreference.java │ │ ├── FileReaderPreference.java │ │ ├── FileSelectPreference.java │ │ ├── FloatSeekBarPreference.java │ │ ├── LimitedEditTextPreference.java │ │ └── ThemePreference.java │ │ ├── receivers │ │ └── WAFReceiver.java │ │ ├── ui │ │ └── fragments │ │ │ ├── CustomizationFragment.java │ │ │ ├── GeneralFragment.java │ │ │ ├── HomeFragment.java │ │ │ ├── MediaFragment.java │ │ │ ├── PrivacyFragment.java │ │ │ └── base │ │ │ ├── BaseFragment.java │ │ │ └── BasePreferenceFragment.java │ │ ├── utils │ │ ├── ColorReplacement.java │ │ ├── DrawableColors.java │ │ ├── FilePicker.java │ │ ├── IColors.java │ │ ├── RealPathUtil.java │ │ └── StateListDrawableCompact.java │ │ ├── views │ │ ├── HorizontalListView.java │ │ ├── IGStatusView.java │ │ ├── NoScrollListView.java │ │ ├── WallpaperView.java │ │ └── dialog │ │ │ ├── BottomDialogWpp.java │ │ │ ├── SimpleColorPickerDialog.java │ │ │ └── TabDialogContent.java │ │ └── xposed │ │ ├── AntiUpdater.java │ │ ├── bridge │ │ ├── ScopeHook.java │ │ ├── client │ │ │ ├── BaseClient.java │ │ │ ├── BridgeClient.java │ │ │ └── ProviderClient.java │ │ ├── providers │ │ │ └── HookProvider.java │ │ └── service │ │ │ ├── BridgeService.java │ │ │ └── HookBinder.java │ │ ├── core │ │ ├── Feature.java │ │ ├── FeatureLoader.java │ │ ├── WaCallback.java │ │ ├── WppCore.java │ │ ├── components │ │ │ ├── AlertDialogWpp.java │ │ │ ├── FMessageWpp.java │ │ │ └── SharedPreferencesWrapper.java │ │ ├── db │ │ │ ├── DelMessageStore.java │ │ │ ├── MessageHistory.java │ │ │ └── MessageStore.java │ │ └── devkit │ │ │ ├── Unobfuscator.java │ │ │ └── UnobfuscatorCache.java │ │ ├── downgrade │ │ └── Patch.java │ │ ├── features │ │ ├── customization │ │ │ ├── BubbleColors.java │ │ │ ├── CustomThemeV2.java │ │ │ ├── CustomTime.java │ │ │ ├── CustomToolbar.java │ │ │ ├── CustomView.java │ │ │ ├── FilterGroups.java │ │ │ ├── HideSeenView.java │ │ │ ├── HideTabs.java │ │ │ ├── IGStatus.java │ │ │ ├── SeparateGroup.java │ │ │ └── ShowOnline.java │ │ ├── general │ │ │ ├── AntiRevoke.java │ │ │ ├── CallType.java │ │ │ ├── ChatLimit.java │ │ │ ├── DeleteStatus.java │ │ │ ├── LiteMode.java │ │ │ ├── MenuStatus.java │ │ │ ├── NewChat.java │ │ │ ├── Others.java │ │ │ ├── PinnedLimit.java │ │ │ ├── SeenTick.java │ │ │ ├── ShareLimit.java │ │ │ ├── ShowEditMessage.java │ │ │ └── Tasker.java │ │ ├── media │ │ │ ├── DownloadProfile.java │ │ │ ├── DownloadViewOnce.java │ │ │ ├── MediaPreview.java │ │ │ ├── MediaQuality.java │ │ │ └── StatusDownload.java │ │ ├── others │ │ │ ├── ActivityController.java │ │ │ ├── AudioTranscript.java │ │ │ ├── Channels.java │ │ │ ├── ChatFilters.java │ │ │ ├── CopyStatus.java │ │ │ ├── DebugFeature.java │ │ │ ├── GoogleTranslate.java │ │ │ ├── GroupAdmin.java │ │ │ ├── MenuHome.java │ │ │ ├── Stickers.java │ │ │ ├── TextStatusComposer.java │ │ │ └── ToastViewer.java │ │ └── privacy │ │ │ ├── AntiWa.java │ │ │ ├── CallPrivacy.java │ │ │ ├── CustomPrivacy.java │ │ │ ├── DndMode.java │ │ │ ├── FreezeLastSeen.java │ │ │ ├── HideChat.java │ │ │ ├── HideReceipt.java │ │ │ ├── HideSeen.java │ │ │ ├── TagMessage.java │ │ │ ├── TypingPrivacy.java │ │ │ └── ViewOnce.java │ │ ├── permission │ │ └── AndroidPermissions.java │ │ ├── spoofer │ │ └── HookBL.java │ │ └── utils │ │ ├── AnimationUtil.java │ │ ├── DebugUtils.java │ │ ├── DesignUtils.java │ │ ├── HKDF.java │ │ ├── HKDFv3.java │ │ ├── MimeTypeUtils.java │ │ ├── ReflectionUtils.java │ │ ├── ResId.java │ │ ├── RunCatchingUtil.java │ │ ├── TimeoutUtil.java │ │ └── Utils.java │ └── res │ ├── drawable │ ├── about.xml │ ├── admin.xml │ ├── airplane_disabled.xml │ ├── airplane_enabled.xml │ ├── border_background.xml │ ├── camera.xml │ ├── deleted.xml │ ├── diagonal_line.xml │ ├── download.xml │ ├── edit.xml │ ├── edit2.xml │ ├── eye_disabled.xml │ ├── eye_enabled.xml │ ├── ghost_disabled.xml │ ├── ghost_enabled.xml │ ├── ic_baseline_arrow_back_24.xml │ ├── ic_dashboard_black_24dp.xml │ ├── ic_donate.xml │ ├── ic_general.xml │ ├── ic_github.xml │ ├── ic_home_black_24dp.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ ├── ic_media.xml │ ├── ic_notifications_black_24dp.xml │ ├── ic_privacy.xml │ ├── ic_reload.png │ ├── ic_round_bug_report_24.xml │ ├── ic_round_check_circle_24.xml │ ├── ic_round_error_outline_24.xml │ ├── ic_round_settings_24.xml │ ├── ic_round_update_24.xml │ ├── ic_round_warning_24.xml │ ├── ic_telegram.xml │ ├── online.xml │ ├── preview_eye.xml │ └── refresh.xml │ ├── layout │ ├── activity_about.xml │ ├── activity_contact_picker.xml │ ├── activity_main.xml │ ├── base_fragment.xml │ ├── fragment_home.xml │ ├── item_folder.xml │ ├── list_item_suggestion.xml │ ├── pref_float_seekbar.xml │ └── preference_theme.xml │ ├── menu │ ├── bottom_nav_menu.xml │ ├── css_editor_menu.xml │ └── header_menu.xml │ ├── mipmap-hdpi │ └── launcher.png │ ├── mipmap-mdpi │ └── launcher.png │ ├── mipmap-xhdpi │ └── launcher.png │ ├── mipmap-xxhdpi │ └── launcher.png │ ├── mipmap-xxxhdpi │ └── launcher.png │ ├── mipmap │ └── launcher.png │ ├── values-ar │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-id │ └── strings.xml │ ├── values-in │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-iw │ └── strings.xml │ ├── values-night │ ├── colors.xml │ └── theme.xml │ ├── values-pt │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-tr │ └── strings.xml │ ├── values-zh │ ├── arrays.xml │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── integer.xml │ ├── strings.xml │ ├── styles.xml │ ├── theme_customs.xml │ ├── themes.xml │ ├── themes_overlay.xml │ └── themes_override.xml │ └── xml │ ├── fragment_customization.xml │ ├── fragment_general.xml │ ├── fragment_media.xml │ ├── fragment_privacy.xml │ ├── preference_general_conversation.xml │ ├── preference_general_home.xml │ └── preference_general_homescreen.xml ├── build.gradle.kts ├── changelog.txt ├── crowdin.yml ├── docs ├── README.md └── README.pt-BR.md ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | groups: 8 | actions: 9 | patterns: 10 | - "*" 11 | 12 | - package-ecosystem: gradle 13 | directory: / 14 | schedule: 15 | interval: daily 16 | groups: 17 | maven: 18 | patterns: 19 | - "*" 20 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | jobs: 7 | build: 8 | permissions: write-all 9 | runs-on: ubuntu-latest 10 | if: github.event_name == 'push' && !contains(github.event.head_commit.message, 'Merge') 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: set up JDK 17 14 | uses: actions/setup-java@v4 15 | with: 16 | java-version: '17' 17 | distribution: 'temurin' 18 | cache: gradle 19 | 20 | - name: Write key 21 | if: github.event_name != 'pull_request' 22 | run: | 23 | if [ ! -z "${{ secrets.KEY_STORE }}" ]; then 24 | echo androidStorePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> gradle.properties 25 | echo androidKeyAlias='${{ secrets.ALIAS }}' >> gradle.properties 26 | echo androidKeyPassword='${{ secrets.KEY_PASSWORD }}' >> gradle.properties 27 | echo androidStoreFile='key.jks' >> gradle.properties 28 | echo ${{ secrets.KEY_STORE }} | base64 --decode > key.jks 29 | fi 30 | 31 | - name: Grant execute permission for gradlew 32 | run: chmod +x gradlew 33 | 34 | - name: Build all flavor variants 35 | run: | 36 | ./gradlew assembleWhatsappDebug assembleBusinessDebug -Pminify=true 37 | 38 | - name: Get short SHA 39 | run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV 40 | 41 | - name: Upload WhatsApp flavor artifacts 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: whatsapp-debug-app-${{ env.SHORT_SHA }} 45 | path: app/build/outputs/apk/whatsapp/debug/app-whatsapp-debug.apk 46 | 47 | - name: Upload Business flavor artifacts 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: business-debug-app-${{ env.SHORT_SHA }} 51 | path: app/build/outputs/apk/business/debug/app-business-debug.apk 52 | 53 | - name: Post to Telegram channel 54 | if: ${{ success() && github.ref == 'refs/heads/master' && github.ref_type != 'tag' }} 55 | env: 56 | CHANNEL_ID: ${{ secrets.CHANNEL_ID }} 57 | BOT_TOKEN: ${{ secrets.BOT_TOKEN }} 58 | COMMIT_MESSAGE: ${{ github.event.head_commit.message }} 59 | COMMIT_URL: ${{ github.event.head_commit.url }} 60 | run: | 61 | if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then 62 | GITHUB_SHA_SHORT=$(git rev-parse --short HEAD) 63 | export output_whatsapp=app/WaEnhancer_$GITHUB_SHA_SHORT.apk 64 | export output_business=app/WaEnhancer_Business_$GITHUB_SHA_SHORT.apk 65 | cp app/build/outputs/apk/whatsapp/debug/app-whatsapp-debug.apk $output_whatsapp 66 | cp app/build/outputs/apk/business/debug/app-business-debug.apk $output_business 67 | export LOG=$(cat changelog.txt) 68 | ESCAPED=$(python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.getenv("LOG")); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.getenv("COMMIT_URL"))))') 69 | 70 | # Send WhatsApp variant 71 | curl "https://api.telegram.org/bot${BOT_TOKEN}/sendMediaGroup?chat_id=${CHANNEL_ID}&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fwa%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fw4b%22%2C%22caption%22%3A${ESCAPED}%7D%5D" -F wa="@$output_whatsapp" -F w4b="@$output_business" 72 | fi 73 | 74 | - name: Upload to GitHub release 75 | uses: softprops/action-gh-release@v2 76 | with: 77 | token: ${{ secrets.GITHUB_TOKEN }} 78 | name: WaEnhancer ${{ env.SHORT_SHA }} 79 | body_path: changelog.txt 80 | files: | 81 | app/build/outputs/apk/whatsapp/debug/app-whatsapp-debug.apk 82 | app/build/outputs/apk/business/debug/app-business-debug.apk 83 | tag_name: debug-${{ env.SHORT_SHA }} 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | key.jks 12 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | /CrowdinSettingsPlugin.xml 5 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Wa Enhancer -------------------------------------------------------------------------------- /.idea/AndroidProjectSystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/appInsightsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -dontwarn * 23 | 24 | -keepclasseswithmembers class com.wmods.** { 25 | *; 26 | } 27 | 28 | -keepclasseswithmembernames class com.wmods.** 29 | 30 | -keepclasseswithmembers class cz.vutbr.** { 31 | *; 32 | } 33 | 34 | -keepclasseswithmembers class com.assemblyai.api.** { 35 | *; 36 | } -------------------------------------------------------------------------------- /app/src/business/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.whatsapp.w4b 5 | android 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/aidl/com/wmods/wppenhacer/xposed/bridge/WaeIIFace.aidl: -------------------------------------------------------------------------------- 1 | // WaeIIFace.aidl 2 | package com.wmods.wppenhacer.xposed.bridge; 3 | 4 | import android.os.ParcelFileDescriptor; 5 | import java.util.List; 6 | 7 | // Declare any non-default types here with import statements 8 | 9 | interface WaeIIFace { 10 | /** 11 | * Demonstrates some basic types that you can use as parameters 12 | * and return values in AIDL. 13 | */ 14 | ParcelFileDescriptor openFile(String path, boolean create); 15 | 16 | boolean createDir(String path); 17 | 18 | List listFiles(String path); 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/assets/css_editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 | 22 | CSS Editor 23 | 24 | 25 | 26 | 28 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/assets/www/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JS for Prism Live’s page, not part of the actual editor 3 | */ 4 | 5 | (async function($, $$) { 6 | 7 | $$("textarea.language-html.fill").forEach(t => t.value = document.head.outerHTML); 8 | 9 | var css = await fetch("prism-live.css"); 10 | css = await css.text(); 11 | 12 | $$("textarea.language-css.fill").forEach(t => { 13 | t.value = css; 14 | t.dispatchEvent(new InputEvent("input")); 15 | }); 16 | 17 | var js = await fetch("src/prism-live.mjs"); 18 | js = await js.text(); 19 | 20 | $$("textarea.language-js.fill").forEach(t => { 21 | t.value = js; 22 | t.dispatchEvent(new InputEvent("input")); 23 | }); 24 | 25 | 26 | })(Bliss, Bliss.$); 27 | -------------------------------------------------------------------------------- /app/src/main/assets/www/prism-live.css: -------------------------------------------------------------------------------- 1 | div.prism-live { 2 | position: relative; 3 | box-sizing: border-box; 4 | display: flex; 5 | flex-flow: column; 6 | } 7 | 8 | textarea.prism-live, 9 | pre.prism-live { 10 | padding: .2rem .5rem; 11 | box-sizing: border-box; 12 | margin: 0; 13 | } 14 | 15 | textarea.prism-live { 16 | position: absolute; 17 | top: 0; 18 | right: 0; 19 | width: 100%; 20 | height: 100%; 21 | z-index: 1; 22 | color: transparent; 23 | /* color: hsla(0,0%,50%,.4); */ 24 | cursor: text; 25 | white-space: pre; 26 | border: 0; 27 | outline: none; 28 | background: transparent; 29 | resize: none; 30 | --selection-background: hsl(320, 80%, 25%); 31 | --selection-color: hsla(0, 0%, 100%, .8); 32 | } 33 | 34 | @supports (not (caret-color: black)) and (-webkit-text-fill-color: black) { 35 | textarea.prism-live { 36 | color: inherit; 37 | -webkit-text-fill-color: transparent; 38 | } 39 | } 40 | 41 | /* Setting specific colors is needed 42 | * because otherwise Firefox shows blank text */ 43 | textarea.prism-live::-moz-selection { 44 | background: var(--selection-background); 45 | color: var(--selection-color); 46 | } 47 | 48 | textarea.prism-live::selection { 49 | background: var(--selection-background); 50 | color: var(--selection-color); 51 | } 52 | 53 | pre.prism-live { 54 | flex: 1; 55 | position: relative; 56 | pointer-events: none; 57 | overflow: hidden; 58 | max-height: 100%; 59 | --scrollbar-width: 17px; 60 | padding-bottom: var(--scrollbar-width); 61 | padding-right: var(--scrollbar-width); 62 | } 63 | 64 | pre.prism-live > code:empty::before { 65 | content: " " 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/assets/www/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.29.0 2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+css-extras&plugins=inline-color */ 3 | code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} 4 | span.inline-color-wrapper{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=);background-position:center;background-size:110%;display:inline-block;height:1.333ch;width:1.333ch;margin:0 .333ch;box-sizing:border-box;border:1px solid #fff;outline:1px solid rgba(0,0,0,.5);overflow:hidden}span.inline-color{display:block;height:120%;width:120%} 5 | -------------------------------------------------------------------------------- /app/src/main/assets/www/src/defaults.js: -------------------------------------------------------------------------------- 1 | export const indent = "\t"; 2 | 3 | export const caretIndicator = /(^|[^\\])\$(\d+)/g; -------------------------------------------------------------------------------- /app/src/main/assets/www/src/dom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the text node at a given chracter offset 3 | */ 4 | export function getNode(offset, container) { 5 | var node, sum = 0; 6 | var walk = document.createTreeWalker(container, NodeFilter.SHOW_TEXT); 7 | 8 | while (node = walk.nextNode()) { 9 | sum += node.data.length; 10 | 11 | if (sum >= offset) { 12 | return node; 13 | } 14 | } 15 | 16 | // if here, offset is larger than maximum 17 | return null; 18 | } 19 | 20 | /** 21 | * Get the character offset of a given node in the highlighted source 22 | */ 23 | export function getOffset (node, container) { 24 | var range = document.createRange(); 25 | range.selectNodeContents(container); 26 | range.setEnd(node, 0); 27 | return range.toString().length; 28 | } -------------------------------------------------------------------------------- /app/src/main/assets/www/src/editing.js: -------------------------------------------------------------------------------- 1 | import * as env from "./env.js"; 2 | import * as defaults from "./defaults.js"; 3 | import {regexp} from "./util.js"; 4 | 5 | export function checkShortcut (shortcut, evt) { 6 | return shortcut.trim().split(/\s*\+\s*/).every(key => { 7 | switch (key) { 8 | case "Cmd": return evt[env.superKey]; 9 | case "Ctrl": return evt.ctrlKey; 10 | case "Shift": return evt.shiftKey; 11 | case "Alt": return evt.altKey; 12 | default: return evt.key === key; 13 | } 14 | }); 15 | } 16 | 17 | const LF = "\n"; 18 | const CR = "\r"; 19 | 20 | export function getLineBounds (context = this) { 21 | var value = context.value; 22 | var start, end, char; 23 | 24 | for (var start = context.selectionStart; char = value[start]; start--) { 25 | if (char === LF || char === CR || !start) { 26 | break; 27 | } 28 | } 29 | 30 | for (var end = context.selectionStart; char = value[end]; end++) { 31 | if (char === LF || char === CR) { 32 | break; 33 | } 34 | } 35 | 36 | return {start, end}; 37 | } 38 | 39 | export function beforeCaretIndex (until = "", context = this) { 40 | return context.value.lastIndexOf(until, context.selectionStart); 41 | } 42 | 43 | export function afterCaretIndex (until = "", context = this) { 44 | return context.value.indexOf(until, context.selectionEnd); 45 | } 46 | 47 | export function beforeCaret (until = "", context = this) { 48 | var index = beforeCaretIndex(until, context); 49 | 50 | if (index === -1 || !until) { 51 | index = 0; 52 | } 53 | 54 | return context.value.slice(index, context.selectionStart); 55 | }; 56 | 57 | export function afterCaret (until = "", context = this) { 58 | var index = afterCaretIndex(until); 59 | 60 | if (index === -1 || !until) { 61 | index = undefined; 62 | } 63 | 64 | return this.value.slice(context.selectionEnd, index); 65 | } 66 | 67 | export function setCaret (pos, context = this) { 68 | context.selectionStart = context.selectionEnd = pos; 69 | } 70 | 71 | export function moveCaret (chars, context = this) { 72 | if (chars) { 73 | context.setCaret(context.selectionEnd + chars); 74 | } 75 | } 76 | 77 | export function deleteText (characters, {selectionStart, selectionEnd, forward, pos} = {}) { 78 | var i = characters = characters > 0? characters : (characters + "").length; 79 | let ret = { selectionStart, selectionEnd }; 80 | 81 | if (pos) { 82 | ret.selectionStart = pos; 83 | ret.selectionEnd = pos + ret.selectionEnd - selectionStart; 84 | } 85 | 86 | while (i--) { 87 | document.execCommand(forward? "forwardDelete" : "delete"); 88 | } 89 | 90 | if (pos) { 91 | // Restore caret 92 | ret.selectionStart = selectionStart - characters; 93 | ret.selectionEnd = ret.selectionEnd - pos + ret.selectionStart; 94 | } 95 | 96 | return ret; 97 | } 98 | 99 | export function matchIndentation (text, currentIndent) { 100 | // FIXME this assumes that text has no indentation of its own 101 | // to make this more generally useful beyond snippets, we should first 102 | // strip text's own indentation. 103 | text = text.replace(/\r?\n/g, "$&" + currentIndent); 104 | } 105 | 106 | export function adjustIndentation (text, {indentation, relative = true, indent = defaults.indent}) { 107 | if (!relative) { 108 | // First strip min indentation 109 | let minIndent = text.match(regexp.gm`^(${indent})+`).sort()[0]; 110 | 111 | if (minIndent) { 112 | text.replace(regexp.gm`^${minIndent}`, ""); 113 | } 114 | } 115 | 116 | if (indentation < 0) { 117 | return text.replace(regexp.gm`^${indent}`, ""); 118 | } 119 | else if (indentation > 0) { // Indent 120 | return text.replace(/^/gm, indent); 121 | } 122 | } -------------------------------------------------------------------------------- /app/src/main/assets/www/src/env.js: -------------------------------------------------------------------------------- 1 | export const superKey = navigator.platform.indexOf("Mac") === 0? "metaKey" : "ctrlKey"; -------------------------------------------------------------------------------- /app/src/main/assets/www/src/languages/css.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | id: "css", 3 | snippets: { 4 | custom: function (property) { 5 | // Expand property 6 | var style = document.documentElement.style; 7 | 8 | if (!/^--/.test(property) && !(property in style)) { 9 | // Nonexistent property, try as a shortcut 10 | var allProperties = Object.keys(style) 11 | .map(a => a.replace(/[A-Z]/g, $0 => "-" + $0.toLowerCase())); 12 | var properties = allProperties.filter(p => { 13 | return p.indexOf(property) === 0 // starts with 14 | || p.split("-").map(b => b[0]).join("") === property; // abbreviation 15 | }).sort((a, b) => a.length - b.length); 16 | 17 | if (properties.length) { 18 | if (properties.length > 1) { 19 | // Many options, don't add the 1.5 colons (mimic terminal autocomplete) 20 | return properties[0]; 21 | } 22 | 23 | property = properties[0]; 24 | } 25 | } 26 | 27 | return `${property}: $1;`; 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /app/src/main/assets/www/src/languages/javascript.mjs: -------------------------------------------------------------------------------- 1 | export const clike = { 2 | id: "clike", 3 | comments: { 4 | singleline: "//", 5 | multiline: ["/*", "*/"] 6 | }, 7 | snippets: { 8 | if: `if ($1) { 9 | $2 10 | }` 11 | } 12 | }; 13 | 14 | export const javascript = { 15 | id: "javascript", 16 | snippets: { 17 | log: "console.log($1)", 18 | } 19 | }; -------------------------------------------------------------------------------- /app/src/main/assets/www/src/languages/markup.mjs: -------------------------------------------------------------------------------- 1 | const selfClosing = ["input", "img", "link", "meta", "base", "br", "hr"]; 2 | 3 | export default { 4 | id: "markup", 5 | comments: { 6 | multiline: [""] 7 | }, 8 | selfClosing, 9 | snippets: { 10 | "submit": '', 11 | custom: function (selector) { 12 | var isName = /^[\w:-]+$/.test(selector); 13 | var isSnippet = isName || selector.match(/^[.#\w:-]+(\{.+?\})?(\*\d+)?$/); 14 | var node = this.getNode(); 15 | var inTag = node.parentNode.closest(".token.tag"); 16 | 17 | if (isName && inTag) { 18 | // Attribute 19 | return `${selector}="$1"`; 20 | } 21 | else if (isSnippet) { 22 | var times = 1; 23 | var content = ""; 24 | 25 | if (isSnippet[1]) { 26 | // Content 27 | content = isSnippet[1].slice(1, -1); 28 | } 29 | 30 | if (isSnippet[2]) { // Times 31 | times = isSnippet[2].slice(1); 32 | } 33 | 34 | var tag = selector.match(/^[\w:-]+/)?.[0] ?? "div"; 35 | var html = `<${tag}`; 36 | var id = selector.match(/#([\w-]+)/)?.[1]; 37 | 38 | if (id) { 39 | html += ` id="${id}"`; 40 | } 41 | 42 | var classes = selector.match(/\.[\w-]+/g); 43 | 44 | if (classes) { 45 | classes = classes.map(x => x.slice(1)); 46 | html += ` class="${classes.join(" ")}"`; 47 | } 48 | 49 | var selfClosing = selfClosing.indexOf(tag) > -1; 50 | 51 | html += selfClosing? "$1 />$2" : ">$1"; 52 | 53 | var tagLength = html.length; 54 | var ret = ""; 55 | 56 | for (var i=0; i`; 63 | } 64 | 65 | if (times > 1 && i + 1 < times) { 66 | ret += "\n"; 67 | } 68 | } 69 | 70 | return ret; 71 | } 72 | } 73 | } 74 | }; -------------------------------------------------------------------------------- /app/src/main/assets/www/src/prism-live.js: -------------------------------------------------------------------------------- 1 | 2 | { 3 | let url, importURL = "./prism-live.mjs"; 4 | // Fall back to loading all languages 5 | let search = "?load=css,javascript,markup"; 6 | 7 | try { 8 | url = document.currentScript?.src ?? eval("import.meta.url"); 9 | } 10 | catch(e) {} 11 | 12 | if (url) { 13 | importURL = new URL(importURL, url); 14 | importURL.search = new URL(url).search ?? search; 15 | } 16 | 17 | import(importURL).then(m => Prism.Live = m.default); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/assets/www/src/util.js: -------------------------------------------------------------------------------- 1 | import $ from "https://v2.blissfuljs.com/src/$.js"; 2 | import $$ from "https://v2.blissfuljs.com/src/$$.js"; 3 | import create from "https://v2.blissfuljs.com/src/dom/create.js"; 4 | import bind from "https://v2.blissfuljs.com/src/events/bind.js"; 5 | import load from "https://v2.blissfuljs.com/src/async/load.js"; 6 | 7 | Object.assign($, {create, bind, load}); 8 | export { $, $$, create, bind, load}; 9 | 10 | /** 11 | * Utility for regexp construction 12 | * @param {*} s 13 | * @returns 14 | */ 15 | let escape = s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); 16 | let _regexp = (flags, strings, ...values) => { 17 | let pattern = strings[0] + values.map((v, i) => escape(v) + strings[i + 1]).join(""); 18 | return RegExp(pattern, flags); 19 | }; 20 | let cache = {}; 21 | 22 | export const regexp = new Proxy(_regexp.bind(this, ""), { 23 | get: (t, property) => { 24 | return t[property] || cache[property] 25 | || (cache[property] = _regexp.bind(this, property)); 26 | } 27 | }); 28 | 29 | export function loadLanguages (ids, PrismLive) { 30 | ids = Array.isArray(ids) ? ids : ids.split(/,/); 31 | return ids.map(c => import(`./languages/${c}.mjs`).then(m => { 32 | if (m.default) { 33 | PrismLive.registerLanguage(m.default.id, m.default); 34 | } 35 | else { 36 | // Many languages 37 | for (let id in m) { 38 | if (PrismLive.languages[id]) { 39 | // Already registered, augment it 40 | Object.assign(PrismLive.languages[id], m[id]); 41 | } 42 | else { 43 | PrismLive.registerLanguage(id, m[id]); 44 | } 45 | 46 | } 47 | } 48 | })); 49 | } -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.wmods.wppenhacer.WppXposed -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/UpdateChecker.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.text.Html; 6 | 7 | import com.wmods.wppenhacer.xposed.core.WppCore; 8 | import com.wmods.wppenhacer.xposed.utils.Utils; 9 | 10 | import java.util.Objects; 11 | 12 | import okhttp3.OkHttpClient; 13 | 14 | public class UpdateChecker implements Runnable { 15 | 16 | private final Activity mActivity; 17 | 18 | public UpdateChecker(Activity activity) { 19 | this.mActivity = activity; 20 | } 21 | 22 | 23 | @Override 24 | public void run() { 25 | try { 26 | var client = new OkHttpClient(); 27 | var request = new okhttp3.Request.Builder() 28 | .url("https://t.me/s/waenhancher") 29 | .build(); 30 | var response = client.newCall(request).execute(); 31 | var body = response.body(); 32 | if (body == null) return; 33 | var content = body.string(); 34 | var findText = "WaEnhancer_Business_"; 35 | var indexHash = content.lastIndexOf(findText); 36 | var lastindexHash = content.indexOf(".apk", indexHash); 37 | var hash = content.substring(indexHash + findText.length(), lastindexHash); 38 | var appInfo = mActivity.getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, 0); 39 | if (!appInfo.versionName.toLowerCase().contains(hash.toLowerCase().trim()) && !Objects.equals(WppCore.getPrivString("ignored_version", ""), hash)) { 40 | var changelogIndex = content.indexOf("
", lastindexHash); 41 | var closeTag = content.indexOf("
", changelogIndex); 42 | var changelogText = content.substring(changelogIndex, closeTag + 6); 43 | var changelog = Html.fromHtml(changelogText, Html.FROM_HTML_MODE_COMPACT).toString(); 44 | mActivity.runOnUiThread(() -> { 45 | AlertDialog.Builder dialog = new AlertDialog.Builder(mActivity); 46 | dialog.setTitle("WAE - New version available!"); 47 | dialog.setMessage("Changelog:\n\n" + changelog); 48 | dialog.setNegativeButton("Ignore", (dialog1, which) -> { 49 | WppCore.setPrivString("ignored_version", hash); 50 | dialog1.dismiss(); 51 | }); 52 | dialog.setPositiveButton("Update", (dialog1, which) -> { 53 | Utils.openLink(mActivity, "https://t.me/waenhancher"); 54 | dialog1.dismiss(); 55 | }); 56 | dialog.show(); 57 | }); 58 | } 59 | } catch (Exception ignored) { 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/activities/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.activities; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import com.wmods.wppenhacer.activities.base.BaseActivity; 10 | import com.wmods.wppenhacer.databinding.ActivityAboutBinding; 11 | 12 | public class AboutActivity extends BaseActivity { 13 | 14 | 15 | @Override 16 | protected void onCreate(@Nullable Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | com.wmods.wppenhacer.databinding.ActivityAboutBinding binding = ActivityAboutBinding.inflate(getLayoutInflater()); 19 | setContentView(binding.getRoot()); 20 | binding.btnTelegram.setOnClickListener(v -> { 21 | Intent intent = new Intent(); 22 | intent.setAction(Intent.ACTION_VIEW); 23 | intent.setData(Uri.parse("https://t.me/waenhancer")); 24 | startActivity(intent); 25 | }); 26 | binding.btnGithub.setOnClickListener(view -> { 27 | Intent intent = new Intent(); 28 | intent.setAction(Intent.ACTION_VIEW); 29 | intent.setData(Uri.parse("https://github.com/Dev4Mod/waenhancer")); 30 | startActivity(intent); 31 | }); 32 | binding.btnDonate.setOnClickListener(view -> { 33 | Intent intent = new Intent(); 34 | intent.setAction(Intent.ACTION_VIEW); 35 | intent.setData(Uri.parse("https://coindrop.to/dev4mod")); 36 | startActivity(intent); 37 | }); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/activities/ForceStartActivity.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.activities; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | public class ForceStartActivity extends Activity { 10 | 11 | @Override 12 | protected void onCreate(@Nullable Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | var packageManager = getPackageManager(); 15 | var pkg = getIntent().getStringExtra("pkg"); 16 | if (pkg != null) { 17 | var intent = packageManager.getLaunchIntentForPackage(pkg); 18 | if (intent != null) { 19 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 20 | startActivity(intent); 21 | } 22 | } 23 | finish(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/activities/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.activities.base; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import com.wmods.wppenhacer.R; 9 | 10 | public class BaseActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(@Nullable Bundle savedInstanceState) { 14 | setTheme(R.style.AppTheme); 15 | getTheme().applyStyle(rikka.material.preference.R.style.ThemeOverlay_Rikka_Material3_Preference, true); 16 | getTheme().applyStyle(R.style.ThemeOverlay, true); 17 | getTheme().applyStyle(R.style.ThemeOverlay_MaterialGreen, true); 18 | super.onCreate(savedInstanceState); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/adapter/MainPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.adapter; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.fragment.app.Fragment; 5 | import androidx.fragment.app.FragmentActivity; 6 | import androidx.viewpager2.adapter.FragmentStateAdapter; 7 | 8 | import com.wmods.wppenhacer.ui.fragments.CustomizationFragment; 9 | import com.wmods.wppenhacer.ui.fragments.GeneralFragment; 10 | import com.wmods.wppenhacer.ui.fragments.HomeFragment; 11 | import com.wmods.wppenhacer.ui.fragments.MediaFragment; 12 | import com.wmods.wppenhacer.ui.fragments.PrivacyFragment; 13 | 14 | public class MainPagerAdapter extends FragmentStateAdapter { 15 | 16 | public MainPagerAdapter(@NonNull FragmentActivity fragmentActivity) { 17 | super(fragmentActivity); 18 | } 19 | 20 | @NonNull 21 | @Override 22 | public Fragment createFragment(int position) { 23 | return switch (position) { 24 | case 0 -> new GeneralFragment(); 25 | case 1 -> new PrivacyFragment(); 26 | case 3 -> new MediaFragment(); 27 | case 4 -> new CustomizationFragment(); 28 | default -> new HomeFragment(); 29 | }; 30 | } 31 | 32 | @Override 33 | public int getItemCount() { 34 | return 5; // Number of fragments 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/adapter/MessageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.adapter; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.TextView; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | import com.wmods.wppenhacer.xposed.core.db.MessageHistory; 13 | import com.wmods.wppenhacer.xposed.utils.DesignUtils; 14 | import com.wmods.wppenhacer.xposed.utils.ResId; 15 | import com.wmods.wppenhacer.xposed.utils.Utils; 16 | 17 | import java.util.List; 18 | 19 | public class MessageAdapter extends ArrayAdapter { 20 | private final Context context; 21 | private final List items; 22 | 23 | public MessageAdapter(Context context, List items) { 24 | super(context, android.R.layout.simple_list_item_2, android.R.id.text1, items); 25 | this.context = context; 26 | this.items = items; 27 | } 28 | 29 | @Override 30 | public int getCount() { 31 | return items.size(); 32 | } 33 | 34 | @Override 35 | public MessageHistory.MessageItem getItem(int position) { 36 | return items.get(position); 37 | } 38 | 39 | @Override 40 | public long getItemId(int position) { 41 | return position; 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public View getView(int position, View convertView, @NonNull ViewGroup parent) { 47 | View view1 = super.getView(position, convertView, parent); 48 | TextView textView0 = view1.findViewById(android.R.id.text1); 49 | textView0.setTextSize(14.0f); 50 | textView0.setTextColor(DesignUtils.getPrimaryTextColor()); 51 | textView0.setText(this.items.get(position).message); 52 | TextView textView1 = view1.findViewById(android.R.id.text2); 53 | textView1.setTextSize(12.0f); 54 | textView1.setAlpha(0.75f); 55 | textView1.setTypeface(null, Typeface.ITALIC); 56 | textView1.setTextColor(DesignUtils.getPrimaryTextColor()); 57 | var timestamp = this.items.get(position).timestamp; 58 | textView1.setText((timestamp == 0L ? context.getString(ResId.string.message_original) : "✏️ " + Utils.getDateTimeFromMillis(timestamp))); 59 | return view1; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/listeners/OnMultiClickListener.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.listeners; 2 | 3 | import android.view.View; 4 | 5 | public abstract class OnMultiClickListener implements View.OnClickListener { 6 | 7 | private final int targetClicks; 8 | private final long delay; 9 | private long lastClick = 0; 10 | private int clicks = 0; 11 | 12 | public OnMultiClickListener(int targetClicks, long delay) { 13 | if (targetClicks < 2) { 14 | throw new IllegalArgumentException("targetClicks must be greater than 1"); 15 | } 16 | this.targetClicks = targetClicks; 17 | this.delay = delay; 18 | } 19 | 20 | @Override 21 | public void onClick(View v) { 22 | if (this.lastClick == 0 || System.currentTimeMillis() - this.lastClick < this.delay) { 23 | this.lastClick = System.currentTimeMillis(); 24 | this.clicks++; 25 | } else { 26 | this.lastClick = 0; 27 | this.clicks = 0; 28 | } 29 | if (this.clicks >= this.targetClicks) { 30 | this.clicks = 0; 31 | this.lastClick = 0; 32 | onMultiClick(v); 33 | } 34 | } 35 | 36 | abstract public void onMultiClick(View v); 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/preference/LimitedEditTextPreference.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.preference; 2 | 3 | import android.content.Context; 4 | import android.text.InputFilter; 5 | import android.util.AttributeSet; 6 | import android.widget.EditText; 7 | 8 | import androidx.preference.EditTextPreference; 9 | 10 | import com.wmods.wppenhacer.R; 11 | 12 | public class LimitedEditTextPreference extends EditTextPreference { 13 | 14 | private static final int DEFAULT_MAX_LENGTH = 10; // Limite padrão de caracteres 15 | private int maxLength; 16 | 17 | public LimitedEditTextPreference(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | init(attrs); 20 | } 21 | 22 | public LimitedEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { 23 | super(context, attrs, defStyleAttr); 24 | init(attrs); 25 | } 26 | 27 | private void init(AttributeSet attrs) { 28 | if (attrs != null) { 29 | var a = getContext().obtainStyledAttributes(attrs, com.wmods.wppenhacer.R.styleable.LimitedEditTextPreference); 30 | maxLength = a.getInt(R.styleable.LimitedEditTextPreference_maxLength, DEFAULT_MAX_LENGTH); 31 | a.recycle(); 32 | } else { 33 | maxLength = DEFAULT_MAX_LENGTH; 34 | } 35 | 36 | setOnBindEditTextListener(this::setMaxLength); 37 | } 38 | 39 | private void setMaxLength(EditText editText) { 40 | InputFilter[] filters = new InputFilter[1]; 41 | filters[0] = new InputFilter.LengthFilter(maxLength); 42 | editText.setFilters(filters); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/receivers/WAFReceiver.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.receivers; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | public class WAFReceiver extends BroadcastReceiver { 8 | @Override 9 | public void onReceive(Context context, Intent intent) { 10 | // nothing here, just a placeholder 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/ui/fragments/CustomizationFragment.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.ui.fragments; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.wmods.wppenhacer.R; 8 | import com.wmods.wppenhacer.ui.fragments.base.BasePreferenceFragment; 9 | 10 | public class CustomizationFragment extends BasePreferenceFragment { 11 | @Override 12 | public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { 13 | super.onCreatePreferences(savedInstanceState, rootKey); 14 | setPreferencesFromResource(R.xml.fragment_customization, rootKey); 15 | } 16 | 17 | @Override 18 | public void onResume() { 19 | super.onResume(); 20 | setDisplayHomeAsUpEnabled(false); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/ui/fragments/GeneralFragment.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.ui.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import com.wmods.wppenhacer.R; 12 | import com.wmods.wppenhacer.ui.fragments.base.BaseFragment; 13 | import com.wmods.wppenhacer.ui.fragments.base.BasePreferenceFragment; 14 | 15 | public class GeneralFragment extends BaseFragment { 16 | 17 | @Nullable 18 | @Override 19 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 20 | var root = super.onCreateView(inflater, container, savedInstanceState); 21 | if (savedInstanceState == null) { 22 | getChildFragmentManager().beginTransaction().add(R.id.frag_container, new GeneralPreferenceFragment()).commitNow(); 23 | } 24 | return root; 25 | } 26 | 27 | public static class GeneralPreferenceFragment extends BasePreferenceFragment { 28 | @Override 29 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 30 | super.onCreatePreferences(savedInstanceState, rootKey); 31 | setPreferencesFromResource(R.xml.fragment_general, rootKey); 32 | } 33 | 34 | @Override 35 | public void onResume() { 36 | super.onResume(); 37 | setDisplayHomeAsUpEnabled(false); 38 | } 39 | } 40 | 41 | public static class HomeGeneralPreference extends BasePreferenceFragment { 42 | @Override 43 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 44 | super.onCreatePreferences(savedInstanceState, rootKey); 45 | setPreferencesFromResource(R.xml.preference_general_home, rootKey); 46 | setDisplayHomeAsUpEnabled(true); 47 | } 48 | } 49 | 50 | public static class HomeScreenGeneralPreference extends BasePreferenceFragment { 51 | @Override 52 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 53 | super.onCreatePreferences(savedInstanceState, rootKey); 54 | setPreferencesFromResource(R.xml.preference_general_homescreen, rootKey); 55 | setDisplayHomeAsUpEnabled(true); 56 | } 57 | } 58 | 59 | public static class ConversationGeneralPreference extends BasePreferenceFragment { 60 | @Override 61 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 62 | super.onCreatePreferences(savedInstanceState, rootKey); 63 | setPreferencesFromResource(R.xml.preference_general_conversation, rootKey); 64 | setDisplayHomeAsUpEnabled(true); 65 | } 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/ui/fragments/MediaFragment.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.ui.fragments; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.wmods.wppenhacer.R; 8 | import com.wmods.wppenhacer.ui.fragments.base.BasePreferenceFragment; 9 | 10 | public class MediaFragment extends BasePreferenceFragment { 11 | 12 | @Override 13 | public void onCreate(@Nullable Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | } 16 | 17 | @Override 18 | public void onResume() { 19 | super.onResume(); 20 | setDisplayHomeAsUpEnabled(false); 21 | } 22 | 23 | 24 | @Override 25 | public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { 26 | super.onCreatePreferences(savedInstanceState, rootKey); 27 | setPreferencesFromResource(R.xml.fragment_media, rootKey); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/ui/fragments/PrivacyFragment.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.ui.fragments; 2 | 3 | import static com.wmods.wppenhacer.preference.ContactPickerPreference.REQUEST_CONTACT_PICKER; 4 | 5 | import android.app.Activity; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import com.wmods.wppenhacer.R; 12 | import com.wmods.wppenhacer.preference.ContactPickerPreference; 13 | import com.wmods.wppenhacer.preference.FileSelectPreference; 14 | import com.wmods.wppenhacer.ui.fragments.base.BasePreferenceFragment; 15 | import com.wmods.wppenhacer.xposed.features.general.LiteMode; 16 | 17 | public class PrivacyFragment extends BasePreferenceFragment { 18 | 19 | @Override 20 | public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { 21 | super.onCreatePreferences(savedInstanceState, rootKey); 22 | setPreferencesFromResource(R.xml.fragment_privacy, rootKey); 23 | } 24 | 25 | @Override 26 | public void onResume() { 27 | super.onResume(); 28 | setDisplayHomeAsUpEnabled(false); 29 | } 30 | 31 | 32 | @Override 33 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 34 | super.onActivityResult(requestCode, resultCode, data); 35 | System.out.println("onActivityResult: " + requestCode + " " + resultCode + " " + data); 36 | if (requestCode == REQUEST_CONTACT_PICKER && resultCode == Activity.RESULT_OK) { 37 | ContactPickerPreference contactPickerPref = findPreference(data.getStringExtra("key")); 38 | if (contactPickerPref != null) { 39 | contactPickerPref.handleActivityResult(requestCode, resultCode, data); 40 | } 41 | } else if (requestCode == LiteMode.REQUEST_FOLDER && resultCode == Activity.RESULT_OK) { 42 | FileSelectPreference fileSelectPreference = findPreference(data.getStringExtra("key")); 43 | if (fileSelectPreference != null) { 44 | fileSelectPreference.handleActivityResult(requestCode, resultCode, data); 45 | } 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/utils/ColorReplacement.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.utils; 2 | 3 | import static com.wmods.wppenhacer.utils.DrawableColors.replaceColor; 4 | 5 | import android.graphics.PorterDuffColorFilter; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.ViewStub; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.wmods.wppenhacer.xposed.utils.DesignUtils; 13 | import com.wmods.wppenhacer.xposed.utils.Utils; 14 | 15 | import java.util.HashMap; 16 | 17 | import de.robv.android.xposed.XposedHelpers; 18 | 19 | public class ColorReplacement { 20 | public static void replaceColors(View view, HashMap colors) { 21 | if (view == null) return; 22 | if (view instanceof ImageView imageView) { 23 | Image.replace(imageView, colors); 24 | } else if (view instanceof TextView textView) { 25 | Text.replace(textView, colors); 26 | } else if (view instanceof ViewGroup viewGroup) { 27 | Group.replace(viewGroup, colors); 28 | } else if (view instanceof ViewStub viewStub) { 29 | replaceColor(viewStub.getBackground(), colors); 30 | } 31 | } 32 | 33 | public static class Image { 34 | static void replace(ImageView view, HashMap colors) { 35 | replaceColor(view.getBackground(), colors); 36 | var colorFilter = view.getColorFilter(); 37 | if (colorFilter == null) return; 38 | if (colorFilter instanceof PorterDuffColorFilter filter) { 39 | var color = (int) XposedHelpers.callMethod(filter, "getColor"); 40 | var sColor = IColors.toString(color); 41 | var newColor = colors.get(sColor); 42 | if (newColor != null) { 43 | view.setColorFilter(IColors.parseColor(newColor)); 44 | } else { 45 | if (!sColor.startsWith("#ff") && !sColor.startsWith("#0")) { 46 | var sColorSub = sColor.substring(0, 3); 47 | newColor = colors.get(sColor.substring(3)); 48 | if (newColor != null) 49 | view.setColorFilter(IColors.parseColor(sColorSub + newColor)); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | public static class Text { 57 | static void replace(TextView view, HashMap colors) { 58 | var color = view.getCurrentTextColor(); 59 | var sColor = IColors.toString(color); 60 | if (sColor.equals("#ffffffff") && !DesignUtils.isNightMode()) { 61 | return; 62 | } 63 | replaceColor(view.getBackground(), colors); 64 | var newColor = colors.get(sColor); 65 | if (newColor != null) { 66 | view.setTextColor(IColors.parseColor(newColor)); 67 | } else { 68 | if (!sColor.startsWith("#ff") && !sColor.startsWith("#0")) { 69 | var sColorSub = sColor.substring(0, 3); 70 | newColor = colors.get(sColor.substring(3)); 71 | if (newColor != null) { 72 | view.setTextColor(IColors.parseColor(sColorSub + newColor)); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | public static class Group { 80 | static void replace(ViewGroup view, HashMap colors) { 81 | var bg = view.getBackground(); 82 | var count = view.getChildCount(); 83 | for (int i = 0; i < count; i++) { 84 | var child = view.getChildAt(i); 85 | replaceColors(child, colors); 86 | } 87 | replaceColor(bg, colors); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/utils/FilePicker.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.utils; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.activity.result.ActivityResultLauncher; 6 | import androidx.activity.result.PickVisualMediaRequest; 7 | import androidx.activity.result.contract.ActivityResultContracts; 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | import java.io.File; 11 | 12 | public class FilePicker { 13 | 14 | private static OnFilePickedListener mOnFilePickedListener; 15 | private static AppCompatActivity mActivity; 16 | public static ActivityResultLauncher fileSalve; 17 | private static OnUriPickedListener mOnUriPickedListener; 18 | public static ActivityResultLauncher fileCapture; 19 | public static ActivityResultLauncher directoryCapture; 20 | public static ActivityResultLauncher imageCapture; 21 | 22 | public static void registerFilePicker(AppCompatActivity activity) { 23 | mActivity = activity; 24 | fileCapture = activity.registerForActivityResult(new ActivityResultContracts.OpenDocument(), FilePicker::setFile); 25 | imageCapture = activity.registerForActivityResult(new ActivityResultContracts.PickVisualMedia(), FilePicker::setFile); 26 | directoryCapture = activity.registerForActivityResult(new ActivityResultContracts.OpenDocumentTree(), FilePicker::setDirectory); 27 | fileSalve = activity.registerForActivityResult(new ActivityResultContracts.CreateDocument("*/*"), FilePicker::setFile); 28 | } 29 | 30 | private static void setFile(Uri uri) { 31 | if (uri == null) return; 32 | 33 | if (mOnUriPickedListener != null) { 34 | mOnUriPickedListener.onUriPicked(uri); 35 | mOnUriPickedListener = null; 36 | } 37 | 38 | if (mOnFilePickedListener != null) { 39 | String realPath = null; 40 | try { 41 | realPath = RealPathUtil.getRealFilePath(mActivity, uri); 42 | }catch (Exception ignored) { 43 | } 44 | if (realPath == null) return; 45 | mOnFilePickedListener.onFilePicked(new File(realPath)); 46 | mOnFilePickedListener = null; 47 | } 48 | } 49 | 50 | private static void setDirectory(Uri uri) { 51 | if (uri == null) return; 52 | 53 | if (mOnFilePickedListener == null) { 54 | mOnUriPickedListener.onUriPicked(uri); 55 | mOnUriPickedListener = null; 56 | } 57 | 58 | if (mOnFilePickedListener != null) { 59 | String realPath = null; 60 | try { 61 | realPath = RealPathUtil.getRealFolderPath(mActivity, uri); 62 | } catch (Exception ignored) { 63 | } 64 | if (realPath == null) return; 65 | mOnFilePickedListener.onFilePicked(new File(realPath)); 66 | mOnFilePickedListener = null; 67 | } 68 | } 69 | 70 | public static void setOnFilePickedListener(OnFilePickedListener onFilePickedListener) { 71 | mOnFilePickedListener = onFilePickedListener; 72 | mOnUriPickedListener = null; 73 | } 74 | 75 | public static void setOnUriPickedListener(OnUriPickedListener onFilePickedListener) { 76 | mOnUriPickedListener = onFilePickedListener; 77 | mOnFilePickedListener = null; 78 | } 79 | 80 | 81 | public interface OnFilePickedListener { 82 | void onFilePicked(File file); 83 | } 84 | 85 | public interface OnUriPickedListener { 86 | void onUriPicked(Uri uri); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/utils/IColors.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.utils; 2 | 3 | import android.graphics.Color; 4 | 5 | import java.util.HashMap; 6 | 7 | public class IColors { 8 | public static HashMap colors = new HashMap<>(); 9 | 10 | 11 | public static HashMap alphacolors = new HashMap<>(); 12 | 13 | public static final HashMap backgroundColors = new HashMap<>(); 14 | public static final HashMap primaryColors = new HashMap<>(); 15 | public static final HashMap textColors = new HashMap<>(); 16 | 17 | public static int parseColor(String str) { 18 | return Color.parseColor(str); 19 | } 20 | 21 | public static String toString(int i) { 22 | var color = Integer.toHexString(i); 23 | if (color.length() == 7) { 24 | color = "0" + color; 25 | } else if (color.length() == 1) { 26 | color = "00000000"; 27 | } 28 | return "#" + color; 29 | } 30 | 31 | 32 | public static int getFromIntColor(int color, HashMap colors) { 33 | var sColor = IColors.toString(color); 34 | var newColor = colors.get(sColor); 35 | if (newColor != null && newColor.length() == 9) { 36 | return IColors.parseColor(newColor); 37 | } else { 38 | if (!sColor.startsWith("#ff")) { 39 | var sColorSub = sColor.substring(0, 3); 40 | newColor = colors.get(sColor.substring(3)); 41 | if (newColor != null) { 42 | return IColors.parseColor(sColorSub + newColor); 43 | } 44 | } 45 | } 46 | return color; 47 | } 48 | 49 | public static void initColors() { 50 | primaryColors.clear(); 51 | textColors.clear(); 52 | backgroundColors.clear(); 53 | colors.clear(); 54 | 55 | // primary colors 56 | primaryColors.put("00a884", "00a884"); 57 | primaryColors.put("1da457", "1da457"); 58 | primaryColors.put("21c063", "21c063"); 59 | primaryColors.put("d9fdd3", "d9fdd3"); 60 | primaryColors.put("#ff00a884", "#ff00a884"); 61 | primaryColors.put("#ff1da457", "#ff1da457"); 62 | primaryColors.put("#ff21c063", "#ff21c063"); 63 | primaryColors.put("#ff1daa61", "#ff1daa61"); 64 | primaryColors.put("#ff25d366", "#ff25d366"); 65 | primaryColors.put("#ffd9fdd3", "#ffd9fdd3"); 66 | primaryColors.put("#ff1b864b", "#ff1b864b"); 67 | 68 | primaryColors.put("#ff144d37", "#ff144d37"); 69 | primaryColors.put("#ff1b8755", "#ff1b8755"); 70 | primaryColors.put("#ff15603e", "#ff15603e"); 71 | primaryColors.put("#ff103529", "#c0103529"); 72 | 73 | // text colors 74 | textColors.put("#ffeaedee", "#ffeaedee"); 75 | textColors.put("#fff7f8fa", "#fff7f8fa"); 76 | 77 | // background colors 78 | backgroundColors.put("0b141a", "0a1014"); 79 | backgroundColors.put("#ff0b141a", "#ff111b21"); 80 | backgroundColors.put("#ff111b21", "#ff111b21"); 81 | backgroundColors.put("#ff000000", "#ff000000"); 82 | backgroundColors.put("#ff0a1014", "#ff0a1014"); 83 | backgroundColors.put("#ff10161a", "#ff10161a"); 84 | backgroundColors.put("#ff12181c", "#ff12181c"); 85 | backgroundColors.put("#ff20272b", "#ff20272b"); 86 | 87 | // Alpha colors 88 | alphacolors.put("#ff15603e", "#8015603e"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/utils/StateListDrawableCompact.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.utils; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.graphics.drawable.StateListDrawable; 5 | import android.os.Build; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | import de.robv.android.xposed.XposedBridge; 12 | import de.robv.android.xposed.XposedHelpers; 13 | 14 | public class StateListDrawableCompact { 15 | private static final Class mClass = StateListDrawable.class; 16 | 17 | private StateListDrawableCompact() { 18 | } 19 | 20 | public static int getStateCount(StateListDrawable stateListDrawable) { 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 22 | return stateListDrawable.getStateCount(); 23 | } else { 24 | try { 25 | Method method = XposedHelpers.findMethodBestMatch(mClass, "getStateCount"); 26 | if (method != null) { 27 | Object invoke = method.invoke(stateListDrawable); 28 | if (invoke instanceof Integer) { 29 | return (Integer) invoke; 30 | } 31 | } 32 | } catch (Exception e) { 33 | XposedBridge.log(e); 34 | } 35 | } 36 | return 0; 37 | } 38 | 39 | @Nullable 40 | public static Drawable getStateDrawable(StateListDrawable stateListDrawable, int i) { 41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 42 | return stateListDrawable.getStateDrawable(i); 43 | } else { 44 | try { 45 | Method method = XposedHelpers.findMethodBestMatch(mClass, "getStateDrawable", Integer.TYPE); 46 | if (method != null) { 47 | Object invoke = method.invoke(stateListDrawable, i); 48 | if (invoke instanceof Drawable) { 49 | return (Drawable) invoke; 50 | } 51 | } 52 | } catch (Exception e) { 53 | XposedBridge.log(e); 54 | } 55 | } 56 | return null; 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/views/IGStatusView.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.views; 2 | 3 | import android.content.Context; 4 | import android.widget.FrameLayout; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.wmods.wppenhacer.adapter.IGStatusAdapter; 9 | 10 | public class IGStatusView extends FrameLayout { 11 | public HorizontalListView mStatusListView; 12 | 13 | public IGStatusAdapter mStatusAdapter; 14 | private int mFragmentId; 15 | 16 | public IGStatusView(@NonNull Context context) { 17 | super(context); 18 | init(context); 19 | } 20 | 21 | public int getFragmentId() { 22 | return mFragmentId; 23 | } 24 | 25 | public void setFragmentId(int id) { 26 | mFragmentId = id; 27 | } 28 | 29 | private void init(Context context) { 30 | mStatusListView = new HorizontalListView(context); 31 | var layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 32 | // layoutParams.setMargins(Utils.dipToPixels(4), Utils.dipToPixels(10), 0, 0); 33 | mStatusListView.setLayoutParams(layoutParams); 34 | addView(mStatusListView); 35 | } 36 | 37 | @Override 38 | public void setTranslationY(float f) { 39 | if (this.getHeight() > 0) { 40 | int v = f > ((float) this.getHeight()) ? GONE : VISIBLE; 41 | if (v == VISIBLE) { 42 | super.setTranslationY(f); 43 | } 44 | this.setVisibility(v); 45 | } 46 | } 47 | 48 | 49 | public void updateList() { 50 | post(() -> { 51 | if (mStatusAdapter == null) return; 52 | mStatusAdapter.notifyDataSetChanged(); 53 | this.invalidate(); 54 | }); 55 | } 56 | 57 | public void setAdapter(IGStatusAdapter mStatusAdapter) { 58 | this.mStatusAdapter = mStatusAdapter; 59 | mStatusListView.setAdapter(mStatusAdapter); 60 | } 61 | 62 | public IGStatusAdapter getAdapter() { 63 | return mStatusAdapter; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/views/NoScrollListView.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.views; 2 | 3 | import android.content.Context; 4 | import android.view.ViewGroup; 5 | import android.widget.ListView; 6 | 7 | public class NoScrollListView extends ListView { 8 | public NoScrollListView(Context context) { 9 | super(context); 10 | } 11 | 12 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 13 | super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0x1FFFFFFF, MeasureSpec.AT_MOST)); 14 | ViewGroup.LayoutParams layoutParams = this.getLayoutParams(); 15 | layoutParams.height = this.getMeasuredHeight(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/views/dialog/BottomDialogWpp.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.views.dialog; 2 | 3 | import android.app.Dialog; 4 | import android.graphics.Color; 5 | import android.view.View; 6 | import android.view.WindowManager; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.wmods.wppenhacer.xposed.utils.Utils; 11 | 12 | public class BottomDialogWpp { 13 | 14 | private final Dialog dialog; 15 | 16 | public BottomDialogWpp(@NonNull Dialog dialog) { 17 | this.dialog = dialog; 18 | } 19 | 20 | public void dismissDialog() { 21 | dialog.dismiss(); 22 | } 23 | 24 | public void showDialog() { 25 | dialog.show(); 26 | if (dialog.getWindow() != null) { 27 | dialog.getWindow().setBackgroundDrawable(null); 28 | dialog.getWindow().setDimAmount(0); 29 | var view = dialog.getWindow().getDecorView(); 30 | view.findViewById(Utils.getID("design_bottom_sheet", "id")).setBackgroundColor(Color.TRANSPARENT); 31 | dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); 32 | } 33 | } 34 | 35 | public void setContentView(View view) { 36 | dialog.setContentView(view); 37 | } 38 | 39 | 40 | public void setCanceledOnTouchOutside(boolean b) { 41 | dialog.setCanceledOnTouchOutside(b); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/AntiUpdater.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed; 2 | 3 | import android.content.pm.PackageInstaller; 4 | 5 | import com.wmods.wppenhacer.xposed.core.FeatureLoader; 6 | 7 | import java.io.IOException; 8 | 9 | import de.robv.android.xposed.XC_MethodHook; 10 | import de.robv.android.xposed.XposedBridge; 11 | import de.robv.android.xposed.XposedHelpers; 12 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 13 | 14 | public class AntiUpdater { 15 | 16 | public static void hookSession(XC_LoadPackage.LoadPackageParam lpparam) { 17 | if (lpparam.packageName.equals("android")) return; 18 | XposedBridge.hookAllMethods(PackageInstaller.class, "createSession", new XC_MethodHook() { 19 | @Override 20 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 21 | var session = (PackageInstaller.SessionParams) param.args[0]; 22 | var packageName = XposedHelpers.getObjectField(session, "mPackageName"); 23 | if (packageName.equals(FeatureLoader.PACKAGE_WPP) || packageName.equals(FeatureLoader.PACKAGE_BUSINESS)) { 24 | param.setThrowable(new IOException("UPDATE LOCKED BY WAENHANCER")); 25 | } 26 | } 27 | }); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/bridge/client/BaseClient.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.bridge.client; 2 | 3 | import com.wmods.wppenhacer.xposed.bridge.WaeIIFace; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | public abstract class BaseClient { 8 | 9 | public abstract WaeIIFace getService(); 10 | 11 | public abstract CompletableFuture connect(); 12 | 13 | public abstract void tryReconnect(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/bridge/providers/HookProvider.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.bridge.providers; 2 | 3 | import android.content.ContentProvider; 4 | import android.content.ContentValues; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | import com.wmods.wppenhacer.xposed.bridge.service.HookBinder; 13 | 14 | public class HookProvider extends ContentProvider { 15 | 16 | @Override 17 | public boolean onCreate() { 18 | return false; 19 | } 20 | 21 | @Nullable 22 | @Override 23 | public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { 24 | if (method.equals("getHookBinder")) { 25 | Bundle result = new Bundle(); 26 | result.putBinder("binder", HookBinder.getInstance()); 27 | return result; 28 | } 29 | return null; 30 | } 31 | 32 | @Nullable 33 | @Override 34 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { 35 | return null; 36 | } 37 | 38 | @Nullable 39 | @Override 40 | public String getType(@NonNull Uri uri) { 41 | return ""; 42 | } 43 | 44 | @Nullable 45 | @Override 46 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 47 | return null; 48 | } 49 | 50 | @Override 51 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { 52 | return 0; 53 | } 54 | 55 | @Override 56 | public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { 57 | return 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/bridge/service/BridgeService.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.bridge.service; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | public class BridgeService extends Service { 10 | 11 | @Nullable 12 | @Override 13 | public IBinder onBind(Intent intent) { 14 | return HookBinder.getInstance(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/bridge/service/HookBinder.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.bridge.service; 2 | 3 | import android.os.ParcelFileDescriptor; 4 | import android.os.RemoteException; 5 | 6 | import com.wmods.wppenhacer.xposed.bridge.WaeIIFace; 7 | 8 | import java.io.File; 9 | import java.io.FileNotFoundException; 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class HookBinder extends WaeIIFace.Stub { 15 | 16 | private static HookBinder mInstance; 17 | 18 | public static HookBinder getInstance() { 19 | if (mInstance == null) { 20 | mInstance = new HookBinder(); 21 | } 22 | return mInstance; 23 | } 24 | 25 | @Override 26 | public ParcelFileDescriptor openFile(String path, boolean create) throws RemoteException { 27 | File file = new File(path); 28 | if (!file.exists() && create) { 29 | try { 30 | file.createNewFile(); 31 | } catch (Exception ignored) { 32 | return null; 33 | } 34 | } 35 | try { 36 | return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); 37 | } catch (FileNotFoundException e) { 38 | return null; 39 | } 40 | } 41 | 42 | @Override 43 | public boolean createDir(String path) throws RemoteException { 44 | File file = new File(path); 45 | return file.mkdirs(); 46 | } 47 | 48 | @Override 49 | public List listFiles(String path) throws RemoteException { 50 | var files = new File(path).listFiles(); 51 | if (files == null) { 52 | return Collections.emptyList(); 53 | } 54 | return Arrays.asList(files); 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/core/Feature.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.core; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import de.robv.android.xposed.XSharedPreferences; 8 | import de.robv.android.xposed.XposedBridge; 9 | 10 | public abstract class Feature { 11 | 12 | public final ClassLoader classLoader; 13 | public final XSharedPreferences prefs; 14 | public static boolean DEBUG = false; 15 | 16 | public Feature(@NonNull ClassLoader classLoader, @NonNull XSharedPreferences preferences) { 17 | this.classLoader = classLoader; 18 | this.prefs = preferences; 19 | } 20 | 21 | public abstract void doHook() throws Throwable; 22 | 23 | @NonNull 24 | public abstract String getPluginName(); 25 | 26 | public void logDebug(Object object) { 27 | if (!DEBUG) return; 28 | log(object); 29 | if (object instanceof Throwable th) { 30 | Log.i("WAE", this.getPluginName() + "-> " + th.getMessage(), th); 31 | } else { 32 | Log.i("WAE", this.getPluginName() + "-> " + object); 33 | } 34 | } 35 | 36 | public void logDebug(String title, Object object) { 37 | if (!DEBUG) return; 38 | log(title + ": " + object); 39 | if (object instanceof Throwable th) { 40 | Log.i("WAE", this.getPluginName() + "-> " + title + ": " + th.getMessage(), th); 41 | } else { 42 | Log.i("WAE", this.getPluginName() + "-> " + title + ": " + object); 43 | } 44 | } 45 | 46 | 47 | public void log(Object object) { 48 | if (object instanceof Throwable) { 49 | XposedBridge.log(String.format("[%s] Error:", this.getPluginName())); 50 | XposedBridge.log((Throwable) object); 51 | } else { 52 | XposedBridge.log(String.format("[%s] %s", this.getPluginName(), object)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/core/WaCallback.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.core; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.app.Application; 6 | import android.os.Bundle; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | public class WaCallback implements Application.ActivityLifecycleCallbacks { 12 | private static void triggerActivityState(@NonNull Activity activity, WppCore.ActivityChangeState.ChangeType type) { 13 | WppCore.listenerAcitivity.forEach((listener) -> listener.onChange(activity, type)); 14 | } 15 | 16 | @Override 17 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) { 18 | WppCore.mCurrentActivity = activity; 19 | triggerActivityState(activity, WppCore.ActivityChangeState.ChangeType.CREATED); 20 | WppCore.activities.add(activity); 21 | } 22 | 23 | @Override 24 | public void onActivityStarted(@NonNull Activity activity) { 25 | WppCore.mCurrentActivity = activity; 26 | triggerActivityState(activity, WppCore.ActivityChangeState.ChangeType.STARTED); 27 | WppCore.activities.add(activity); 28 | } 29 | 30 | @SuppressLint("ApplySharedPref") 31 | @Override 32 | public void onActivityResumed(@NonNull Activity activity) { 33 | WppCore.mCurrentActivity = activity; 34 | WppCore.activities.add(activity); 35 | triggerActivityState(activity, WppCore.ActivityChangeState.ChangeType.RESUMED); 36 | } 37 | 38 | @Override 39 | public void onActivityPaused(@NonNull Activity activity) { 40 | triggerActivityState(activity, WppCore.ActivityChangeState.ChangeType.PAUSED); 41 | } 42 | 43 | @Override 44 | public void onActivityStopped(@NonNull Activity activity) { 45 | triggerActivityState(activity, WppCore.ActivityChangeState.ChangeType.ENDED); 46 | WppCore.activities.remove(activity); 47 | } 48 | 49 | @Override 50 | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) { 51 | 52 | } 53 | 54 | @Override 55 | public void onActivityDestroyed(@NonNull Activity activity) { 56 | WppCore.activities.remove(activity); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/core/db/DelMessageStore.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.core.db; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.database.sqlite.SQLiteOpenHelper; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | import java.util.HashSet; 12 | 13 | public class DelMessageStore extends SQLiteOpenHelper { 14 | private static DelMessageStore mInstance; 15 | 16 | private DelMessageStore(@NonNull Context context) { 17 | super(context, "delmessages.db", null, 4); 18 | } 19 | 20 | public static DelMessageStore getInstance(Context ctx) { 21 | synchronized (DelMessageStore.class) { 22 | if (mInstance == null || !mInstance.getWritableDatabase().isOpen()) { 23 | mInstance = new DelMessageStore(ctx); 24 | } 25 | } 26 | return mInstance; 27 | } 28 | 29 | @Override 30 | public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { 31 | if (oldVersion < 4) { 32 | if (!checkColumnExists(sqLiteDatabase, "delmessages", "timestamp")) { 33 | sqLiteDatabase.execSQL("ALTER TABLE delmessages ADD COLUMN timestamp INTEGER DEFAULT 0;"); 34 | } 35 | } 36 | } 37 | 38 | public void insertMessage(String jid, String msgid, long timestamp) { 39 | try (SQLiteDatabase dbWrite = this.getWritableDatabase()) { 40 | ContentValues values = new ContentValues(); 41 | values.put("jid", jid); 42 | values.put("msgid", msgid); 43 | values.put("timestamp", timestamp); 44 | dbWrite.insert("delmessages", null, values); 45 | } 46 | } 47 | 48 | public HashSet getMessagesByJid(String jid) { 49 | SQLiteDatabase dbReader = this.getReadableDatabase(); 50 | Cursor query = dbReader.query("delmessages", new String[]{"_id", "jid", "msgid"}, "jid=?", new String[]{jid}, null, null, null); 51 | HashSet messages = new HashSet<>(); 52 | try { 53 | if (query.moveToFirst()) { 54 | do { 55 | messages.add(query.getString(query.getColumnIndexOrThrow("msgid"))); 56 | } while (query.moveToNext()); 57 | } 58 | } finally { 59 | query.close(); 60 | dbReader.close(); 61 | } 62 | return messages; 63 | } 64 | 65 | @Override 66 | public void onCreate(SQLiteDatabase sqLiteDatabase) { 67 | sqLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS delmessages (_id INTEGER PRIMARY KEY AUTOINCREMENT, jid TEXT, msgid TEXT, timestamp INTEGER DEFAULT 0, UNIQUE(jid, msgid))"); 68 | } 69 | 70 | public long getTimestampByMessageId(String msgid) { 71 | SQLiteDatabase dbReader = this.getReadableDatabase(); 72 | try (dbReader; Cursor query = dbReader.query("delmessages", new String[]{"timestamp"}, "msgid=?", new String[]{msgid}, null, null, null)) { 73 | if (query.moveToFirst()) { 74 | return query.getLong(query.getColumnIndexOrThrow("timestamp")); 75 | } 76 | return 0; 77 | } 78 | } 79 | 80 | private boolean checkColumnExists(SQLiteDatabase db, String tableName, String columnName) { 81 | try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + tableName + ")", null)) { 82 | if (cursor != null) { 83 | int nameIndex = cursor.getColumnIndex("name"); 84 | while (cursor.moveToNext()) { 85 | String currentColumnName = cursor.getString(nameIndex); 86 | if (columnName.equals(currentColumnName)) { 87 | return true; 88 | } 89 | } 90 | } 91 | } catch (Exception ignored) { 92 | } 93 | return false; 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/customization/CustomTime.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.customization; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 7 | 8 | import java.text.SimpleDateFormat; 9 | import java.util.Date; 10 | import java.util.Locale; 11 | 12 | import de.robv.android.xposed.XC_MethodHook; 13 | import de.robv.android.xposed.XSharedPreferences; 14 | import de.robv.android.xposed.XposedBridge; 15 | 16 | public class CustomTime extends Feature { 17 | 18 | public CustomTime(ClassLoader loader, XSharedPreferences preferences) { 19 | super(loader, preferences); 20 | } 21 | 22 | @Override 23 | public void doHook() throws Exception { 24 | var secondsToTime = prefs.getBoolean("segundos", false); 25 | var ampm = prefs.getBoolean("ampm", false); 26 | var secondsToTimeMethod = Unobfuscator.loadTimeToSecondsMethod(classLoader); 27 | logDebug(Unobfuscator.getMethodDescriptor(secondsToTimeMethod)); 28 | 29 | XposedBridge.hookMethod(secondsToTimeMethod, new XC_MethodHook() { 30 | 31 | @Override 32 | protected void afterHookedMethod(MethodHookParam param) { 33 | var timestamp = (long) param.args[1]; 34 | var date = new Date(timestamp); 35 | var patternDefault = "HH:mm"; 36 | var patternSeconds = "HH:mm:ss"; 37 | if (ampm) { 38 | patternDefault = "hh:mm a"; 39 | patternSeconds = "hh:mm:ss a"; 40 | } 41 | var pattern = secondsToTime ? patternSeconds : patternDefault; 42 | var formattedDate = new SimpleDateFormat(pattern, Locale.US).format(date); 43 | 44 | param.setResult(getTextInHour(formattedDate)); 45 | } 46 | }); 47 | } 48 | 49 | @NonNull 50 | @Override 51 | public String getPluginName() { 52 | return "Seconds To Time"; 53 | } 54 | 55 | private String getTextInHour(String date) { 56 | var summary = prefs.getString("secondstotime", ""); 57 | if (summary == null) return date; 58 | else return date + " " + summary; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/general/DeleteStatus.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.general; 2 | 3 | import static com.wmods.wppenhacer.xposed.features.general.MenuStatus.menuStatuses; 4 | 5 | import android.os.Bundle; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | import com.wmods.wppenhacer.xposed.core.Feature; 12 | import com.wmods.wppenhacer.xposed.core.WppCore; 13 | import com.wmods.wppenhacer.xposed.core.components.FMessageWpp; 14 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 15 | import com.wmods.wppenhacer.xposed.utils.ReflectionUtils; 16 | import com.wmods.wppenhacer.xposed.utils.ResId; 17 | 18 | import java.lang.reflect.Field; 19 | 20 | import de.robv.android.xposed.XSharedPreferences; 21 | 22 | public class DeleteStatus extends Feature { 23 | 24 | 25 | public DeleteStatus(@NonNull ClassLoader classLoader, @NonNull XSharedPreferences preferences) { 26 | super(classLoader, preferences); 27 | } 28 | 29 | @Override 30 | public void doHook() throws Throwable { 31 | 32 | var fragmentloader = Unobfuscator.loadFragmentLoader(classLoader); 33 | var showDialogStatus = Unobfuscator.loadShowDialogStatusMethod(classLoader); 34 | Class StatusDeleteDialogFragmentClass = classLoader.loadClass("com.whatsapp.status.StatusDeleteDialogFragment"); 35 | Field fieldBundle = ReflectionUtils.getFieldByType(fragmentloader, Bundle.class); 36 | 37 | var item = new MenuStatus.MenuItemStatus() { 38 | 39 | @Override 40 | public MenuItem addMenu(Menu menu, FMessageWpp fMessage) { 41 | if (menu.findItem(ResId.string.delete_for_me) != null) return null; 42 | if (fMessage.getKey().isFromMe) return null; 43 | return menu.add(0, ResId.string.delete_for_me, 0, ResId.string.delete_for_me); 44 | } 45 | 46 | @Override 47 | public void onClick(MenuItem item, Object fragmentInstance, FMessageWpp fMessage) { 48 | try { 49 | var status = StatusDeleteDialogFragmentClass.newInstance(); 50 | var key = fMessage.getKey(); 51 | var bundle = getBundle(key); 52 | WppCore.setPrivBoolean(key.messageID + "_delpass", true); 53 | fieldBundle.set(status, bundle); 54 | showDialogStatus.invoke(null, status, fragmentInstance); 55 | } catch (Exception e) { 56 | logDebug(e); 57 | } 58 | } 59 | }; 60 | menuStatuses.add(item); 61 | } 62 | 63 | @NonNull 64 | @Override 65 | public String getPluginName() { 66 | return "Delete Status"; 67 | } 68 | 69 | @NonNull 70 | private static Bundle getBundle(FMessageWpp.Key key) { 71 | var bundle = new Bundle(); 72 | bundle.putString("fMessageKeyJid", WppCore.getRawString(key.remoteJid)); 73 | bundle.putBoolean("fMessageKeyFromMe", key.isFromMe); 74 | bundle.putString("fMessageKeyId", key.messageID); 75 | return bundle; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/general/ShareLimit.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.general; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 7 | 8 | import java.util.HashMap; 9 | 10 | import de.robv.android.xposed.XC_MethodHook; 11 | import de.robv.android.xposed.XSharedPreferences; 12 | import de.robv.android.xposed.XposedBridge; 13 | 14 | public class ShareLimit extends Feature { 15 | public ShareLimit(ClassLoader loader, XSharedPreferences preferences) { 16 | super(loader, preferences); 17 | } 18 | 19 | public void doHook() throws Exception { 20 | if (!prefs.getBoolean("removeforwardlimit", false)) return; 21 | var shareLimitMethod = Unobfuscator.loadShareLimitMethod(classLoader); 22 | logDebug(Unobfuscator.getMethodDescriptor(shareLimitMethod)); 23 | var shareItemField = Unobfuscator.loadShareMapItemField(classLoader); 24 | logDebug(Unobfuscator.getFieldDescriptor(shareItemField)); 25 | 26 | XposedBridge.hookMethod( 27 | shareLimitMethod, 28 | new XC_MethodHook() { 29 | private HashMap fakeMap; 30 | private HashMap mMap; 31 | 32 | /** 33 | * @noinspection unchecked 34 | */ 35 | @Override 36 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 37 | fakeMap = new HashMap<>(); 38 | mMap = (HashMap) shareItemField.get(param.thisObject); 39 | shareItemField.set(param.thisObject, fakeMap); 40 | } 41 | 42 | @Override 43 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 44 | mMap.putAll(fakeMap); 45 | shareItemField.set(param.thisObject, mMap); 46 | fakeMap.clear(); 47 | } 48 | }); 49 | 50 | 51 | } 52 | 53 | @NonNull 54 | @Override 55 | public String getPluginName() { 56 | return "Share Limit"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/media/DownloadProfile.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.media; 2 | 3 | import android.text.TextUtils; 4 | import android.view.Menu; 5 | import android.view.MenuItem; 6 | import android.widget.Toast; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.wmods.wppenhacer.xposed.core.Feature; 11 | import com.wmods.wppenhacer.xposed.core.WppCore; 12 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 13 | import com.wmods.wppenhacer.xposed.utils.ReflectionUtils; 14 | import com.wmods.wppenhacer.xposed.utils.ResId; 15 | import com.wmods.wppenhacer.xposed.utils.Utils; 16 | 17 | import de.robv.android.xposed.XC_MethodHook; 18 | import de.robv.android.xposed.XSharedPreferences; 19 | import de.robv.android.xposed.XposedHelpers; 20 | 21 | public class DownloadProfile extends Feature { 22 | 23 | public DownloadProfile(@NonNull ClassLoader classLoader, @NonNull XSharedPreferences preferences) { 24 | super(classLoader, preferences); 25 | } 26 | 27 | @Override 28 | public void doHook() throws Throwable { 29 | var loadProfileInfoField = Unobfuscator.loadProfileInfoField(classLoader); 30 | XposedHelpers.findAndHookMethod("com.whatsapp.profile.ViewProfilePhoto", classLoader, "onCreateOptionsMenu", Menu.class, new XC_MethodHook() { 31 | @Override 32 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 33 | var menu = (Menu) param.args[0]; 34 | var item = menu.add(0, 0, 0, ResId.string.download); 35 | item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 36 | item.setIcon(ResId.drawable.download); 37 | item.setOnMenuItemClickListener(menuItem -> { 38 | var subCls = param.thisObject.getClass().getSuperclass(); 39 | if (subCls == null) { 40 | log(new Exception("SubClass is null")); 41 | return true; 42 | } 43 | var field = ReflectionUtils.getFieldByType(subCls, loadProfileInfoField.getDeclaringClass()); 44 | var jidObj = ReflectionUtils.getObjectField(loadProfileInfoField, ReflectionUtils.getObjectField(field, param.thisObject)); 45 | var jid = WppCore.stripJID(WppCore.getRawString(jidObj)); 46 | var file = WppCore.getContactPhotoFile(jid); 47 | String destPath; 48 | try { 49 | destPath = Utils.getDestination("Profile Photo"); 50 | } catch (Exception e) { 51 | Utils.showToast(e.toString(), 1); 52 | return true; 53 | } 54 | var name = Utils.generateName(jidObj, "jpg"); 55 | var error = Utils.copyFile(file, destPath, name); 56 | if (TextUtils.isEmpty(error)) { 57 | Toast.makeText(Utils.getApplication(), Utils.getApplication().getString(ResId.string.saved_to) + destPath, Toast.LENGTH_LONG).show(); 58 | } else { 59 | Toast.makeText(Utils.getApplication(), Utils.getApplication().getString(ResId.string.error_when_saving_try_again) + " " + error, Toast.LENGTH_LONG).show(); 60 | } 61 | return true; 62 | }); 63 | } 64 | }); 65 | } 66 | 67 | @NonNull 68 | @Override 69 | public String getPluginName() { 70 | return "Download Profile Picture"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/others/ChatFilters.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.others; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 7 | import com.wmods.wppenhacer.xposed.utils.ReflectionUtils; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import de.robv.android.xposed.XC_MethodHook; 13 | import de.robv.android.xposed.XSharedPreferences; 14 | import de.robv.android.xposed.XposedBridge; 15 | import de.robv.android.xposed.XposedHelpers; 16 | 17 | public class ChatFilters extends Feature { 18 | public ChatFilters(@NonNull ClassLoader classLoader, @NonNull XSharedPreferences preferences) { 19 | super(classLoader, preferences); 20 | } 21 | 22 | @Override 23 | public void doHook() throws Throwable { 24 | if (!prefs.getBoolean("separategroups", false)) return; 25 | 26 | var filterAdaperClass = Unobfuscator.loadFilterAdaperClass(classLoader); 27 | XposedBridge.hookAllConstructors(filterAdaperClass, new XC_MethodHook() { 28 | @Override 29 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 30 | var list = ReflectionUtils.findArrayOfType(param.args, List.class); 31 | if (!list.isEmpty()) { 32 | var argResult = list.get(0); 33 | var newList = new ArrayList((List) argResult.second); 34 | newList.removeIf(item -> { 35 | var name = XposedHelpers.getObjectField(item, "A01"); 36 | return name == null || name == "CONTACTS_FILTER" || name == "GROUP_FILTER"; 37 | }); 38 | param.args[argResult.first] = newList; 39 | } 40 | } 41 | }); 42 | var methodSetFilter = ReflectionUtils.findMethodUsingFilter(filterAdaperClass, method -> method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(int.class)); 43 | 44 | XposedBridge.hookMethod(methodSetFilter, new XC_MethodHook() { 45 | @Override 46 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 47 | var index = (int) param.args[0]; 48 | var field = ReflectionUtils.getFieldByType(methodSetFilter.getDeclaringClass(), List.class); 49 | var list = (List) field.get(param.thisObject); 50 | if (list == null || index >= list.size()) { 51 | param.setResult(null); 52 | } 53 | } 54 | }); 55 | } 56 | 57 | @NonNull 58 | @Override 59 | public String getPluginName() { 60 | return "Chat Filters"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/others/CopyStatus.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.others; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | import android.widget.Toast; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.wmods.wppenhacer.xposed.core.Feature; 10 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 11 | import com.wmods.wppenhacer.xposed.utils.ResId; 12 | import com.wmods.wppenhacer.xposed.utils.Utils; 13 | 14 | import de.robv.android.xposed.XC_MethodHook; 15 | import de.robv.android.xposed.XSharedPreferences; 16 | import de.robv.android.xposed.XposedBridge; 17 | 18 | public class CopyStatus extends Feature { 19 | public CopyStatus(@NonNull ClassLoader classLoader, @NonNull XSharedPreferences preferences) { 20 | super(classLoader, preferences); 21 | } 22 | 23 | @Override 24 | public void doHook() throws Throwable { 25 | 26 | if (!prefs.getBoolean("copystatus", false)) return; 27 | 28 | var viewButtonMethod = Unobfuscator.loadBlueOnReplayViewButtonMethod(classLoader); 29 | logDebug(Unobfuscator.getMethodDescriptor(viewButtonMethod)); 30 | 31 | XposedBridge.hookMethod(viewButtonMethod, new XC_MethodHook() { 32 | @Override 33 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 34 | var view = (View) param.getResult(); 35 | var caption = (TextView) view.findViewById(Utils.getID("caption", "id")); 36 | if (caption != null) { 37 | caption.setOnLongClickListener((view1 -> { 38 | Utils.setToClipboard(caption.getText().toString()); 39 | Utils.showToast(Utils.getApplication().getString(ResId.string.copied_to_clipboard), Toast.LENGTH_LONG); 40 | return true; 41 | })); 42 | } 43 | 44 | } 45 | }); 46 | 47 | var viewStatusMethod = Unobfuscator.loadBlueOnReplayStatusViewMethod(classLoader); 48 | XposedBridge.hookMethod(viewStatusMethod, new XC_MethodHook() { 49 | @Override 50 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 51 | var view = (View) param.args[0]; 52 | var text = (TextView) view.findViewById(Utils.getID("message_text", "id")); 53 | if (text != null) { 54 | text.setOnLongClickListener((view1 -> { 55 | Utils.setToClipboard(text.getText().toString()); 56 | Utils.showToast(Utils.getApplication().getString(ResId.string.copied_to_clipboard), Toast.LENGTH_LONG); 57 | return true; 58 | })); 59 | } 60 | } 61 | }); 62 | } 63 | 64 | @NonNull 65 | @Override 66 | public String getPluginName() { 67 | return ""; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/others/DebugFeature.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.others; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | 7 | import de.robv.android.xposed.XSharedPreferences; 8 | 9 | public class DebugFeature extends Feature { 10 | 11 | 12 | public DebugFeature(@NonNull ClassLoader classLoader, @NonNull XSharedPreferences preferences) { 13 | super(classLoader, preferences); 14 | } 15 | 16 | @Override 17 | public void doHook() throws Throwable { 18 | 19 | } 20 | 21 | 22 | @NonNull 23 | @Override 24 | public String getPluginName() { 25 | return "Debug Feature"; 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/privacy/AntiWa.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.privacy; 2 | 3 | import android.content.ContentResolver; 4 | import android.provider.Settings; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.wmods.wppenhacer.xposed.core.Feature; 9 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 10 | 11 | import java.io.File; 12 | 13 | import de.robv.android.xposed.XC_MethodHook; 14 | import de.robv.android.xposed.XC_MethodReplacement; 15 | import de.robv.android.xposed.XSharedPreferences; 16 | import de.robv.android.xposed.XposedBridge; 17 | 18 | public class AntiWa extends Feature { 19 | public AntiWa(@NonNull ClassLoader classLoader, @NonNull XSharedPreferences preferences) { 20 | super(classLoader, preferences); 21 | } 22 | 23 | @Override 24 | public void doHook() throws Throwable { 25 | if (!prefs.getBoolean("bootloader_spoofer", false)) return; 26 | var rootDetector = Unobfuscator.loadRootDetector(classLoader); 27 | for (var detector : rootDetector) { 28 | logDebug("Root", detector); 29 | XposedBridge.hookMethod(detector, XC_MethodReplacement.returnConstant(false)); 30 | } 31 | var settingsGetInt = Settings.Global.class.getDeclaredMethod("getInt", ContentResolver.class, String.class, int.class); 32 | logDebug("Adb", settingsGetInt); 33 | XposedBridge.hookMethod(settingsGetInt, new XC_MethodHook() { 34 | @Override 35 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 36 | var key = (String) param.args[1]; 37 | if (key.equals("adb_enabled")) { 38 | param.setResult(0); 39 | } 40 | } 41 | }); 42 | var checkEmulator = Unobfuscator.loadCheckEmulator(classLoader); 43 | logDebug("Emulator", checkEmulator); 44 | XposedBridge.hookMethod(checkEmulator, XC_MethodReplacement.returnConstant(false)); 45 | // File Check 46 | var FileConstructor = File.class.getConstructor(String.class); 47 | XposedBridge.hookMethod(FileConstructor, new XC_MethodHook() { 48 | @Override 49 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 50 | var path = (String) param.args[0]; 51 | var fakePath = "/data/fakepath"; 52 | if (path.contains("qemu") || path.contains("superuser")) { 53 | param.args[0] = fakePath; 54 | } 55 | 56 | } 57 | }); 58 | 59 | var checkCustomRom = Unobfuscator.loadCheckCustomRom(classLoader); 60 | logDebug("CustomRom", checkCustomRom); 61 | XposedBridge.hookMethod(checkCustomRom, XC_MethodReplacement.returnConstant(false)); 62 | } 63 | 64 | @NonNull 65 | @Override 66 | public String getPluginName() { 67 | return "AntiDetector"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/privacy/DndMode.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.privacy; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | import com.wmods.wppenhacer.xposed.core.WppCore; 7 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 8 | 9 | import de.robv.android.xposed.XC_MethodReplacement; 10 | import de.robv.android.xposed.XSharedPreferences; 11 | import de.robv.android.xposed.XposedBridge; 12 | 13 | public class DndMode extends Feature { 14 | public DndMode(ClassLoader loader, XSharedPreferences preferences) { 15 | super(loader, preferences); 16 | } 17 | 18 | @Override 19 | public void doHook() throws Exception { 20 | if (!WppCore.getPrivBoolean("dndmode",false)) return; 21 | var dndMethod = Unobfuscator.loadDndModeMethod(classLoader); 22 | logDebug(Unobfuscator.getMethodDescriptor(dndMethod)); 23 | XposedBridge.hookMethod(dndMethod, XC_MethodReplacement.DO_NOTHING); 24 | } 25 | 26 | @NonNull 27 | @Override 28 | public String getPluginName() { 29 | return "Dnd Mode"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/privacy/FreezeLastSeen.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.privacy; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | import com.wmods.wppenhacer.xposed.core.WppCore; 7 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 8 | 9 | import de.robv.android.xposed.XC_MethodReplacement; 10 | import de.robv.android.xposed.XSharedPreferences; 11 | import de.robv.android.xposed.XposedBridge; 12 | 13 | public class FreezeLastSeen extends Feature { 14 | public FreezeLastSeen(ClassLoader loader, XSharedPreferences preferences) { 15 | super(loader, preferences); 16 | } 17 | 18 | @Override 19 | public void doHook() throws Exception { 20 | var freezeLastSeen = prefs.getBoolean("freezelastseen", false); 21 | var freezeLastSeenOption = WppCore.getPrivBoolean("freezelastseen", false); 22 | var ghostmode = WppCore.getPrivBoolean("ghostmode", false) && prefs.getBoolean("ghostmode", false); 23 | 24 | if (freezeLastSeen || freezeLastSeenOption || ghostmode) { 25 | var method = Unobfuscator.loadFreezeSeenMethod(classLoader); 26 | logDebug(Unobfuscator.getMethodDescriptor(method)); 27 | XposedBridge.hookMethod(method, XC_MethodReplacement.DO_NOTHING); 28 | } 29 | } 30 | 31 | @NonNull 32 | @Override 33 | public String getPluginName() { 34 | return "Freeze Last Seen"; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/privacy/HideReceipt.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.privacy; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | import com.wmods.wppenhacer.xposed.core.WppCore; 7 | import com.wmods.wppenhacer.xposed.core.components.FMessageWpp; 8 | import com.wmods.wppenhacer.xposed.core.db.MessageHistory; 9 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 10 | import com.wmods.wppenhacer.xposed.features.customization.HideSeenView; 11 | import com.wmods.wppenhacer.xposed.utils.ReflectionUtils; 12 | 13 | import de.robv.android.xposed.XC_MethodHook; 14 | import de.robv.android.xposed.XSharedPreferences; 15 | import de.robv.android.xposed.XposedBridge; 16 | 17 | public class HideReceipt extends Feature { 18 | public HideReceipt(ClassLoader loader, XSharedPreferences preferences) { 19 | super(loader, preferences); 20 | } 21 | 22 | @Override 23 | public void doHook() throws Exception { 24 | var hideReceipt = prefs.getBoolean("hidereceipt", false); 25 | var ghostmode = WppCore.getPrivBoolean("ghostmode", false); 26 | var hideread = prefs.getBoolean("hideread", false); 27 | 28 | var method = Unobfuscator.loadReceiptMethod(classLoader); 29 | logDebug("hook method:" + Unobfuscator.getMethodDescriptor(method)); 30 | var method2 = Unobfuscator.loadReceiptOutsideChat(classLoader); 31 | logDebug("Outside Chat: " + Unobfuscator.getMethodDescriptor(method2)); 32 | var mInChat = Unobfuscator.loadReceiptInChat(classLoader); 33 | logDebug("In Chat: " + Unobfuscator.getMethodDescriptor(mInChat)); 34 | 35 | XposedBridge.hookMethod(method, new XC_MethodHook() { 36 | @Override 37 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 38 | if (!ReflectionUtils.isCalledFromMethod(method2) && !ReflectionUtils.isCalledFromMethod(mInChat)) 39 | return; 40 | var key = ReflectionUtils.getArg(param.args, FMessageWpp.Key.TYPE, 0); 41 | var messageKey = new FMessageWpp.Key(key); 42 | var userJid = messageKey.remoteJid; 43 | var rawJid = WppCore.getRawString(userJid); 44 | var number = WppCore.stripJID(rawJid); 45 | var privacy = CustomPrivacy.getJSON(number); 46 | var customHideReceipt = privacy.optBoolean("HideReceipt", hideReceipt); 47 | var customHideRead = privacy.optBoolean("HideSeen", hideread); 48 | if (param.args[4] != "sender" && (customHideReceipt || ghostmode)) { 49 | if (!ReflectionUtils.isCalledFromMethod(method2) && ReflectionUtils.isCalledFromMethod(mInChat) && !customHideRead) { 50 | return; 51 | } 52 | param.args[4] = "inactive"; 53 | } 54 | logDebug(param.args[4]); 55 | if (param.args[4] == "inactive") { 56 | Object fmessageObj = WppCore.getFMessageFromKey(key); 57 | var fmessage = new FMessageWpp(fmessageObj); 58 | var messageId = fmessage.getKey().messageID; 59 | MessageHistory.getInstance().insertHideSeenMessage(rawJid, messageId, MessageHistory.MessageType.MESSAGE_TYPE, false); 60 | if (fmessage.isViewOnce()) { 61 | MessageHistory.getInstance().insertHideSeenMessage(rawJid, messageId, MessageHistory.MessageType.VIEW_ONCE_TYPE, false); 62 | } 63 | HideSeenView.updateAllBubbleViews(); 64 | } 65 | } 66 | }); 67 | } 68 | 69 | @NonNull 70 | @Override 71 | public String getPluginName() { 72 | return "Hide Receipt"; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/privacy/TagMessage.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.privacy; 2 | 3 | import android.view.ViewGroup; 4 | import android.widget.ImageView; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.wmods.wppenhacer.xposed.core.Feature; 9 | import com.wmods.wppenhacer.xposed.core.components.FMessageWpp; 10 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 11 | import com.wmods.wppenhacer.xposed.utils.ReflectionUtils; 12 | import com.wmods.wppenhacer.xposed.utils.Utils; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | import de.robv.android.xposed.XC_MethodHook; 17 | import de.robv.android.xposed.XSharedPreferences; 18 | import de.robv.android.xposed.XposedBridge; 19 | 20 | public class TagMessage extends Feature { 21 | public TagMessage(ClassLoader loader, XSharedPreferences preferences) { 22 | super(loader, preferences); 23 | } 24 | 25 | @Override 26 | public void doHook() throws Exception { 27 | 28 | Method method = Unobfuscator.loadForwardTagMethod(classLoader); 29 | logDebug(Unobfuscator.getMethodDescriptor(method)); 30 | Class forwardClass = Unobfuscator.loadForwardClassMethod(classLoader); 31 | logDebug("ForwardClass: " + forwardClass.getName()); 32 | 33 | XposedBridge.hookMethod(method, new XC_MethodHook() { 34 | @Override 35 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 36 | if (!prefs.getBoolean("hidetag", false)) return; 37 | var arg = (long) param.args[0]; 38 | if (arg == 1) { 39 | if (ReflectionUtils.isCalledFromClass(forwardClass)) { 40 | param.args[0] = 0; 41 | } 42 | } 43 | } 44 | }); 45 | 46 | if (prefs.getBoolean("broadcast_tag", false)) { 47 | hookBroadcastView(); 48 | } 49 | } 50 | 51 | private void hookBroadcastView() throws Exception { 52 | Method method1 = Unobfuscator.loadBroadcastTagMethod(classLoader); 53 | 54 | XposedBridge.hookMethod(method1, new XC_MethodHook() { 55 | private FMessageWpp.Key keyObj; 56 | 57 | @Override 58 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 59 | keyObj = null; 60 | var fmessage = new FMessageWpp(param.args[0]); 61 | var key = fmessage.getKey(); 62 | if (!key.isFromMe && fmessage.isBroadcast()) { 63 | var view = (ViewGroup) param.thisObject; 64 | var res = view.findViewById((int) param.args[1]); 65 | if (res == null) { 66 | var dateWrapper = (ViewGroup) view.findViewById(Utils.getID("date_wrapper", "id")); 67 | var broadcast = new ImageView(view.getContext()); 68 | broadcast.setId((int) param.args[1]); 69 | dateWrapper.addView(broadcast, 0); 70 | } 71 | key.setIsFromMe(true); 72 | keyObj = key; 73 | } 74 | } 75 | 76 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 77 | if (keyObj != null) { 78 | keyObj.setIsFromMe(false); 79 | } 80 | } 81 | 82 | }); 83 | } 84 | 85 | @NonNull 86 | @Override 87 | public String getPluginName() { 88 | return "Tag Message"; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/privacy/TypingPrivacy.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.privacy; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.wmods.wppenhacer.xposed.core.Feature; 6 | import com.wmods.wppenhacer.xposed.core.WppCore; 7 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | import de.robv.android.xposed.XC_MethodHook; 12 | import de.robv.android.xposed.XSharedPreferences; 13 | import de.robv.android.xposed.XposedBridge; 14 | 15 | public class TypingPrivacy extends Feature { 16 | 17 | public TypingPrivacy(ClassLoader loader, XSharedPreferences preferences) { 18 | super(loader, preferences); 19 | } 20 | 21 | @Override 22 | public void doHook() throws Throwable { 23 | var ghostmode = WppCore.getPrivBoolean("ghostmode", false); 24 | var ghostmode_t = prefs.getBoolean("ghostmode_t", false); 25 | var ghostmode_r = prefs.getBoolean("ghostmode_r", false); 26 | Method method = Unobfuscator.loadGhostModeMethod(classLoader); 27 | logDebug(Unobfuscator.getMethodDescriptor(method)); 28 | XposedBridge.hookMethod(method, new XC_MethodHook() { 29 | @Override 30 | protected void beforeHookedMethod(MethodHookParam param) { 31 | var p1 = (int) param.args[2]; 32 | var userJid = param.args[1]; 33 | var number = WppCore.stripJID(WppCore.getRawString(userJid)); 34 | var privacy = CustomPrivacy.getJSON(number); 35 | var customHideTyping = privacy.optBoolean("HideTyping", ghostmode_t); 36 | var customHideRecording = privacy.optBoolean("HideRecording", ghostmode_r); 37 | if ((p1 == 1 && (customHideRecording || ghostmode)) || (p1 == 0 && (customHideTyping || ghostmode))) { 38 | param.setResult(null); 39 | } 40 | } 41 | }); 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public String getPluginName() { 47 | return "Typing Privacy"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/features/privacy/ViewOnce.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.features.privacy; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.wmods.wppenhacer.xposed.core.Feature; 7 | import com.wmods.wppenhacer.xposed.core.components.FMessageWpp; 8 | import com.wmods.wppenhacer.xposed.core.devkit.Unobfuscator; 9 | 10 | import de.robv.android.xposed.XC_MethodHook; 11 | import de.robv.android.xposed.XSharedPreferences; 12 | import de.robv.android.xposed.XposedBridge; 13 | 14 | public class ViewOnce extends Feature { 15 | 16 | public ViewOnce(ClassLoader loader, XSharedPreferences preferences) { 17 | super(loader, preferences); 18 | } 19 | 20 | @Override 21 | public void doHook() throws Exception { 22 | if (!prefs.getBoolean("viewonce", false)) return; 23 | 24 | var methods = Unobfuscator.loadViewOnceMethod(classLoader); 25 | 26 | for (var method : methods) { 27 | logDebug(Unobfuscator.getMethodDescriptor(method)); 28 | XposedBridge.hookMethod(method, new XC_MethodHook() { 29 | @Override 30 | protected void beforeHookedMethod(MethodHookParam param) { 31 | int returnValue = (int) param.args[0]; 32 | var fMessage = new FMessageWpp(param.thisObject); 33 | if (returnValue == 1 && !fMessage.getKey().isFromMe) { 34 | param.args[0] = 0; 35 | } 36 | } 37 | }); 38 | } 39 | } 40 | 41 | @NonNull 42 | @Override 43 | public String getPluginName() { 44 | return "View Once"; 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/utils/HKDF.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.utils; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.security.InvalidKeyException; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | import javax.crypto.Mac; 8 | import javax.crypto.spec.SecretKeySpec; 9 | 10 | public abstract class HKDF { 11 | public static HKDF createFor(int version) { 12 | if (version == 3) { 13 | return new HKDFv3(); 14 | } 15 | throw new AssertionError("Unknown version: " + version); 16 | } 17 | 18 | public byte[] deriveSecrets(byte[] arr_b, byte[] arr_b1, int v) { 19 | return this.deriveSecrets(arr_b, new byte[0x20], arr_b1, v); 20 | } 21 | 22 | public byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info, int outputLength) { 23 | byte[] derivedKey; 24 | try { 25 | Mac mac = Mac.getInstance("HmacSHA256"); 26 | mac.init(new SecretKeySpec(salt, "HmacSHA256")); 27 | derivedKey = mac.doFinal(inputKeyMaterial); 28 | } catch (InvalidKeyException | NoSuchAlgorithmException e) { 29 | throw new AssertionError(e); 30 | } 31 | 32 | try { 33 | int iterations = (int) Math.ceil(((double) outputLength) / 32.0); 34 | byte[] outputKey = new byte[0]; 35 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 36 | for (int i = getIterationStartOffset(); i < getIterationStartOffset() + iterations; ++i) { 37 | Mac macIteration = Mac.getInstance("HmacSHA256"); 38 | macIteration.init(new SecretKeySpec(derivedKey, "HmacSHA256")); 39 | macIteration.update(outputKey); 40 | if (info != null) { 41 | macIteration.update(info); 42 | } 43 | macIteration.update((byte) i); 44 | outputKey = macIteration.doFinal(); 45 | int remainingLength = Math.min(outputLength, outputKey.length); 46 | outputStream.write(outputKey, 0, remainingLength); 47 | outputLength -= remainingLength; 48 | } 49 | return outputStream.toByteArray(); 50 | } catch (InvalidKeyException | NoSuchAlgorithmException ex) { 51 | throw new AssertionError(ex); 52 | } 53 | } 54 | 55 | protected abstract int getIterationStartOffset(); 56 | 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/utils/HKDFv3.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.utils; 2 | 3 | public class HKDFv3 extends HKDF { 4 | @Override 5 | protected int getIterationStartOffset() { 6 | return 1; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/utils/MimeTypeUtils.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.utils; 2 | 3 | import android.webkit.MimeTypeMap; 4 | 5 | public class MimeTypeUtils { 6 | public static String getMimeTypeFromExtension(String url) { 7 | String type = ""; 8 | String extension = MimeTypeMap.getFileExtensionFromUrl(url); 9 | if (extension != null) { 10 | type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 11 | } 12 | return type; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/utils/RunCatchingUtil.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.utils; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Supplier; 5 | 6 | public class RunCatchingUtil { 7 | 8 | public static Result runCatching(Supplier block) { 9 | try { 10 | return Result.success(block.get()); 11 | } catch (Throwable e) { 12 | return Result.failure(e); 13 | } 14 | } 15 | 16 | public static class Result { 17 | private final T value; 18 | private final Throwable exception; 19 | 20 | private Result(T value, Throwable exception) { 21 | this.value = value; 22 | this.exception = exception; 23 | } 24 | 25 | public static Result success(T value) { 26 | return new Result<>(value, null); 27 | } 28 | 29 | public static Result failure(Throwable exception) { 30 | return new Result<>(null, exception); 31 | } 32 | 33 | public Optional getValue() { 34 | return Optional.ofNullable(value); 35 | } 36 | 37 | public Optional getException() { 38 | return Optional.ofNullable(exception); 39 | } 40 | 41 | public boolean isSuccess() { 42 | return exception == null; 43 | } 44 | 45 | public boolean isFailure() { 46 | return exception != null; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wmods/wppenhacer/xposed/utils/TimeoutUtil.java: -------------------------------------------------------------------------------- 1 | package com.wmods.wppenhacer.xposed.utils; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.ScheduledExecutorService; 5 | import java.util.concurrent.ScheduledThreadPoolExecutor; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class TimeoutUtil { 9 | 10 | private static final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1); 11 | 12 | /** 13 | * Adds a timeout to a CompletableFuture 14 | * @param future The original CompletableFuture 15 | * @param timeout Timeout duration 16 | * @param unit Time unit 17 | * @param Type of the result 18 | * @return New CompletableFuture with timeout 19 | */ 20 | public static CompletableFuture withTimeout(CompletableFuture future, long timeout, TimeUnit unit) { 21 | CompletableFuture timeoutFuture = new CompletableFuture<>(); 22 | 23 | // Schedules a task to complete the future with an exception after the timeout 24 | scheduler.schedule(() -> { 25 | timeoutFuture.completeExceptionally( 26 | new java.util.concurrent.TimeoutException("Operation exceeded the time limit of " + timeout + " " + unit)); 27 | }, timeout, unit); 28 | 29 | // Returns the first to complete (either the original or the timeout) 30 | return CompletableFuture.anyOf(future, timeoutFuture) 31 | .thenApply(result -> (T) result) 32 | .exceptionally(ex -> { 33 | // Cancels the original future if a timeout occurs 34 | future.cancel(true); 35 | throw new RuntimeException(ex); 36 | }); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/about.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/airplane_disabled.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/airplane_enabled.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/border_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/deleted.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/diagonal_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/download.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit2.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/eye_disabled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/eye_enabled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ghost_disabled.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ghost_enabled.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_back_24.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_donate.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_general.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 20 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_media.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_privacy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev4Mod/WaEnhancer/c88484bc953636558dbbca71340207c77bf5e09d/app/src/main/res/drawable/ic_reload.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_bug_report_24.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_check_circle_24.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_error_outline_24.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_settings_24.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_update_24.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_warning_24.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_telegram.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/online.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/preview_eye.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_contact_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 21 | 22 | 25 | 26 |