├── .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 |
5 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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