├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── opencsv-3.8.jar ├── proguard-rules.pro ├── release │ └── app-release.apk └── src │ ├── androidTest │ └── java │ │ └── ga │ │ └── nikhilkumar │ │ └── whatsappsender │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── ga │ │ │ └── nikhilkumar │ │ │ └── whatsappsender │ │ │ ├── Sender.java │ │ │ ├── WASenderAccSvc.java │ │ │ ├── WASenderFgSvc.java │ │ │ ├── sender │ │ │ ├── Utils.java │ │ │ ├── WhatsappApi.java │ │ │ ├── exception │ │ │ │ ├── RootNotAvailableException.java │ │ │ │ └── WhatsappNotInstalledException.java │ │ │ ├── liseteners │ │ │ │ ├── GetContactsListener.java │ │ │ │ └── SendMessageListener.java │ │ │ └── model │ │ │ │ ├── WContact.java │ │ │ │ └── WMessage.java │ │ │ └── whatsapp │ │ │ └── MediaData.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_sender.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── backup_config.xml │ │ └── service_config.xml │ └── test │ └── java │ └── ga │ └── nikhilkumar │ └── whatsappsender │ └── ExampleUnitTest.java ├── build.gradle ├── chrome ├── background.js ├── html │ ├── popup.html │ └── popup.js ├── js │ ├── api.js │ ├── content.js │ ├── jquery.js │ └── whatsapp.js ├── logo │ ├── 128.png │ ├── 16.png │ └── 48.png └── manifest.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── samples ├── sample.csv ├── sample.json └── tutorial.gif └── settings.gradle /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.me/nikhilmuz']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | # Uncomment the following line in case you need and you don't have the release build type files in your app 17 | # release/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # IntelliJ 39 | *.iml 40 | .idea/workspace.xml 41 | .idea/tasks.xml 42 | .idea/gradle.xml 43 | .idea/assetWizardSettings.xml 44 | .idea/dictionaries 45 | .idea/libraries 46 | # Android Studio 3 in .gitignore file. 47 | .idea/caches 48 | .idea/modules.xml 49 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 50 | .idea/navEditor.xml 51 | 52 | # Keystore files 53 | # Uncomment the following lines if you do not want to check your keystore files in. 54 | #*.jks 55 | #*.keystore 56 | 57 | # External native build folder generated in Android Studio 2.2 and later 58 | .externalNativeBuild 59 | 60 | # Google Services (e.g. APIs or Firebase) 61 | # google-services.json 62 | 63 | # Freeline 64 | freeline.py 65 | freeline/ 66 | freeline_project_description.json 67 | 68 | # fastlane 69 | fastlane/report.xml 70 | fastlane/Preview.html 71 | fastlane/screenshots 72 | fastlane/test_output 73 | fastlane/readme.md 74 | 75 | # Version control 76 | vcs.xml 77 | 78 | # lint 79 | lint/intermediates/ 80 | lint/generated/ 81 | lint/outputs/ 82 | lint/tmp/ 83 | # lint/reports/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at admin@nikhilkumar.cf. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nikhil Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WhatsApp-Bulk-Sender 2 | [![Platform](https://img.shields.io/badge/platform-Android%7CChrome-yellow.svg)](https://www.android.com) 3 | ![GitHub](https://img.shields.io/github/license/nikhilmuz/WhatsApp-Bulk-Sender.svg) 4 | ![Bitcoin](https://img.shields.io/keybase/btc/nikhilkr.svg) 5 | 6 | Send bulk messages right from your WhatsApp Android app or WhatsApp Web 7 | 8 | This application is meant for sending bulk messages through WhatsApp client on Android or Chrome Web Browser.
9 |
10 | To send messages using Android Application:
11 |

Tutorial


12 |
13 |
    14 |
  1. Open this app and click on "Settings" button to launch Android Setting app so that you may allow accesibility service for this app which is required to auto send messages. If device is rooted (not recommended) there is no need to do same. Directly go to third step.
  2. 15 |
  3. Go back to app activity.
  4. 16 |
  5. Click "Browse" button to browse *.csv file having phone number in first column in format <COUNTRY CODE><MSISDN> and message to send in second column.
  6. 17 |
  7. Hit "Send" button.
  8. 18 |
  9. Sit back and relax.
  10. 19 |
20 |
21 | P.S. Add the contacts list on your device and sync it with WhatsApp before sending bulk SMSes on non-rooted phones use any utilities like [Google Contacts](https://contacts.google.com) for the same in order to import contacts from *.csv. It is recomended for rooted phones also due to WhatsApp policies.
22 |
23 | Find sample csv and json in "samples" folder after cloning the repository 24 |
25 | To send messages via WhatsApp Web on Chrome:
26 |
27 | Deprecated in latest WhatsApp web. Last worked on : 24th April, 2019 28 |
29 |
    30 |
  1. Open chrome browser and navigate to chrome://extensions/.
  2. 31 |
  3. Enable Developer mode from top right.
  4. 32 |
  5. Click "Load unpacked" and browse for chrome folder in this repo.
  6. 33 |
  7. Open WhatsApp Web at https://web.whatsapp.com and login.
  8. 34 |
  9. Click the extension icon from right side of omnibar on WhatsApp tab and click Upload and browse for json file containing numbers and messages as in folder samples.
  10. 35 |
  11. Click on Submit to start sending messages.
  12. 36 |
  13. Wait for alert on completion of task.
  14. 37 |
  15. Get logs in the browser console.
  16. 38 |
39 |
40 | You may create json from csv sheet having the column msisdn and message using any online tool like http://www.convertcsv.com/csv-to-json.htm just make sure to enter the contact numbers including country code without any + sign.
41 | P.S. This method only works with the contacts you have initiated chat with. It will support other contacts too in future versions.

42 | 43 | **Disclaimer: This software have potential to send bulk messages via WhatsApp. However author or contributer of this repository doesn't promote any practices involves abusing of any third party service and this software runs independently on Android APIs and nowhere affiliated to WhatsApp. By using WhatsApp you are bound to their customer policy, terms of service, fair usage policies and any similar policies which may change over time and reserves right to take any action accordingly. Do check with WhatsApp before using this product.** 44 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 1.0.0 | :white_check_mark: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Email any vulnerability to admin@nikhilkumar.cf 15 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "ga.nikhilkumar.whatsappsender" 7 | minSdkVersion 19 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | implementation files('libs/opencsv-3.8.jar') 29 | implementation 'com.android.volley:volley:1.1.1' 30 | implementation 'eu.chainfire:libsuperuser:1.0.0.201704021214' 31 | implementation 'org.apache.commons:commons-lang3:3.4' 32 | implementation 'commons-io:commons-io:2.5' 33 | } 34 | -------------------------------------------------------------------------------- /app/libs/opencsv-3.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/libs/opencsv-3.8.jar -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/release/app-release.apk -------------------------------------------------------------------------------- /app/src/androidTest/java/ga/nikhilkumar/whatsappsender/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("ga.nikhilkumar.whatsappsender", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/Sender.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.preference.PreferenceManager; 11 | import android.provider.Settings; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.view.View; 14 | import android.widget.Button; 15 | import android.widget.Toast; 16 | 17 | import com.android.volley.RequestQueue; 18 | import com.android.volley.Response; 19 | import com.android.volley.VolleyError; 20 | import com.android.volley.toolbox.StringRequest; 21 | import com.android.volley.toolbox.Volley; 22 | 23 | import org.json.JSONException; 24 | import org.json.JSONObject; 25 | 26 | import ga.nikhilkumar.whatsappsender.sender.WhatsappApi; 27 | 28 | 29 | public class Sender extends AppCompatActivity { 30 | 31 | Uri uri = null; 32 | SharedPreferences sp; 33 | Context activityContext = this; 34 | Button browsebtn, sendbtn, accbtn; 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_sender); 39 | sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 40 | stopFgService(); 41 | browsebtn = findViewById(R.id.browsebtn); 42 | sendbtn = findViewById(R.id.sendbtn); 43 | accbtn = findViewById(R.id.accbtn); 44 | setDefaultOnClick(browsebtn); 45 | setDefaultOnClick(sendbtn); 46 | setDefaultOnClick(accbtn); 47 | checkUpdate(); 48 | } 49 | private void setOnClick(View v){ 50 | 51 | switch (v.getId()){ 52 | 53 | case R.id.browsebtn: 54 | v.setOnClickListener(new View.OnClickListener() { 55 | @Override 56 | public void onClick(View view) { 57 | Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 58 | intent.addCategory(Intent.CATEGORY_OPENABLE); 59 | intent.setType("*/*"); 60 | startActivityForResult(intent, 7); 61 | } 62 | }); 63 | break; 64 | 65 | case R.id.sendbtn: 66 | v.setOnClickListener(new View.OnClickListener() { 67 | @SuppressLint("ApplySharedPref") 68 | @Override 69 | public void onClick(View view) { 70 | if (uri == null) { 71 | Toast.makeText(activityContext, "No file selected :(", Toast.LENGTH_SHORT).show(); 72 | } else { 73 | Toast.makeText(activityContext, "Checking for permissions", Toast.LENGTH_SHORT).show(); 74 | Intent intent = new Intent(activityContext, WASenderFgSvc.class); 75 | intent.putExtra("start", true); 76 | intent.putExtra("uri", uri); 77 | if (WhatsappApi.getInstance().isRootAvailable()) { 78 | Toast.makeText(activityContext, "Root Privileges Detected Switching to advanced mode :)", Toast.LENGTH_SHORT).show(); 79 | intent.putExtra("rooted", true); 80 | sp.edit().putBoolean("running", true).commit(); 81 | startService(intent); 82 | } else { 83 | Toast.makeText(activityContext, "Oh no root detected continuing with usual privileges :(", Toast.LENGTH_SHORT).show(); 84 | intent.putExtra("rooted", false); 85 | sp.edit().putBoolean("running", true).commit(); 86 | startService(intent); 87 | } 88 | } 89 | } 90 | }); 91 | break; 92 | 93 | case R.id.accbtn: 94 | v.setOnClickListener(new View.OnClickListener() { 95 | @Override 96 | public void onClick(View view) { 97 | startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); 98 | } 99 | }); 100 | break; 101 | } 102 | } 103 | 104 | @Override 105 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 106 | 107 | if (requestCode == 7 && resultCode == Activity.RESULT_OK) { 108 | if (data != null) { 109 | uri = data.getData(); 110 | } 111 | } 112 | } 113 | 114 | @Override 115 | public void onResume() { 116 | stopFgService(); 117 | super.onResume(); 118 | } 119 | 120 | private void checkUpdate() { 121 | RequestQueue mRequestQueue = Volley.newRequestQueue(this); 122 | StringRequest request = new StringRequest("https://api.nikhilkumar.ga/version/whatsappsender/", new Response.Listener() { 123 | @Override 124 | public void onResponse(String response) { 125 | JSONObject jsonObject; 126 | try { 127 | jsonObject = new JSONObject(response); 128 | if (jsonObject.getInt("latest") == 2) { 129 | setOnClick(browsebtn); 130 | setOnClick(sendbtn); 131 | setOnClick(accbtn); 132 | } else { 133 | Toast.makeText(activityContext, "Update to proceed :(", Toast.LENGTH_SHORT).show(); 134 | } 135 | } catch (JSONException e) { 136 | Toast.makeText(activityContext, "Server Error :( Try again later", Toast.LENGTH_SHORT).show(); 137 | e.printStackTrace(); 138 | } 139 | } 140 | }, new Response.ErrorListener() { 141 | @Override 142 | public void onErrorResponse(VolleyError error) { 143 | Toast.makeText(activityContext, "Connectivity Error :(", Toast.LENGTH_SHORT).show(); 144 | } 145 | }); 146 | request.setShouldCache(false); 147 | mRequestQueue.add(request); 148 | } 149 | 150 | private void stopFgService() { 151 | Intent intent = new Intent(this, WASenderFgSvc.class); 152 | stopService(intent); 153 | sp.edit().putBoolean("running", false).apply(); 154 | } 155 | 156 | private void setDefaultOnClick(View view) { 157 | View.OnClickListener onClickListener = new View.OnClickListener() { 158 | @Override 159 | public void onClick(View v) { 160 | Toast.makeText(activityContext, "Not Permitted :(", Toast.LENGTH_SHORT).show(); 161 | checkUpdate(); 162 | } 163 | }; 164 | view.setOnClickListener(onClickListener); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/WASenderAccSvc.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.content.Intent; 5 | import android.preference.PreferenceManager; 6 | import android.view.accessibility.AccessibilityEvent; 7 | import android.view.accessibility.AccessibilityNodeInfo; 8 | import android.widget.Toast; 9 | 10 | import java.util.List; 11 | 12 | import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; 13 | 14 | public class WASenderAccSvc extends AccessibilityService { 15 | 16 | @Override 17 | public void onAccessibilityEvent(AccessibilityEvent event) { 18 | if (!PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean("running", false)) { 19 | return; 20 | } 21 | if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event.getEventType()) { 22 | String actname = event.getClassName().toString(); 23 | if (actname.equals("com.whatsapp.Conversation")) { 24 | List nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.whatsapp:id/send"); 25 | if (nodes.size()>0) { 26 | nodes.get(0).performAction(ACTION_CLICK); 27 | } 28 | performGlobalAction(GLOBAL_ACTION_BACK); 29 | } else if (actname.equals("com.whatsapp.HomeActivity")) { 30 | sendNext(); 31 | } else if (actname.equals("com.whatsapp.ContactPicker")) { 32 | Toast.makeText(this,"Unable to find contacts in your list! Skipping!!!", Toast.LENGTH_SHORT).show(); 33 | performGlobalAction(GLOBAL_ACTION_BACK); 34 | } 35 | } 36 | } 37 | 38 | @Override 39 | public void onInterrupt() { 40 | Toast.makeText(this, "Please allow accessibility permission to WhatsApp Sender", Toast.LENGTH_SHORT).show(); 41 | } 42 | 43 | private void sendNext() { 44 | Intent intent = new Intent(this, WASenderFgSvc.class); 45 | intent.putExtra("start", false); 46 | startService(intent); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/WASenderFgSvc.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.annotation.TargetApi; 5 | import android.app.Notification; 6 | import android.app.NotificationChannel; 7 | import android.app.NotificationManager; 8 | import android.app.Service; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.SharedPreferences; 12 | import android.graphics.Color; 13 | import android.net.Uri; 14 | import android.os.Build; 15 | import android.os.IBinder; 16 | import android.preference.PreferenceManager; 17 | import android.support.v4.app.NotificationCompat; 18 | import android.widget.Toast; 19 | 20 | import com.opencsv.CSVReader; 21 | 22 | import java.io.FileNotFoundException; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.InputStreamReader; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | import ga.nikhilkumar.whatsappsender.sender.WhatsappApi; 30 | import ga.nikhilkumar.whatsappsender.sender.exception.WhatsappNotInstalledException; 31 | import ga.nikhilkumar.whatsappsender.sender.liseteners.SendMessageListener; 32 | import ga.nikhilkumar.whatsappsender.sender.model.WContact; 33 | import ga.nikhilkumar.whatsappsender.sender.model.WMessage; 34 | 35 | public class WASenderFgSvc extends Service { 36 | 37 | private static final int NOTIFICATION_ID = 12; 38 | SharedPreferences sp; 39 | Integer progress = 0; 40 | List recipientList = new ArrayList<>(); 41 | 42 | @Override 43 | public IBinder onBind(Intent intent) { 44 | throw new UnsupportedOperationException("Not yet implemented"); 45 | } 46 | 47 | @Override 48 | public int onStartCommand(Intent intent, int flags, int startId) { 49 | Boolean start = intent.getBooleanExtra("start", true); 50 | Boolean rooted = intent.getBooleanExtra("rooted", false); 51 | if (start) { 52 | progress = 0; 53 | recipientList.clear(); 54 | Uri uri = intent.getParcelableExtra("uri"); 55 | try { 56 | InputStream file = getContentResolver().openInputStream(uri); 57 | CSVReader csvReader = new CSVReader(new InputStreamReader(file)); 58 | recipientList = csvReader.readAll(); 59 | } catch (FileNotFoundException e) { 60 | Toast.makeText(this, "File not Found", Toast.LENGTH_SHORT).show(); 61 | e.printStackTrace(); 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | Toast.makeText(this, "Not a CSV file", Toast.LENGTH_SHORT).show(); 65 | } 66 | sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 67 | if (rooted) { 68 | List wContactList = new ArrayList<>(); 69 | List wMessageList = new ArrayList<>(); 70 | for (String[] recepient : recipientList) { 71 | wContactList.add(new WContact(recepient[0], recepient[0] + "@s.whatsapp.net")); 72 | wMessageList.add(new WMessage(recepient[1], null, this)); 73 | } 74 | try { 75 | WhatsappApi.getInstance().sendMessage(wContactList, wMessageList, this, new SendMessageListener() { 76 | @SuppressLint("ApplySharedPref") 77 | @Override 78 | public void finishSendWMessage(List contact, List messages) { 79 | Toast.makeText(WASenderFgSvc.this, "Task Completed", Toast.LENGTH_SHORT).show(); 80 | sp.edit().putBoolean("running", false).commit(); 81 | } 82 | }); 83 | } catch (WhatsappNotInstalledException e) { 84 | e.printStackTrace(); 85 | Toast.makeText(this, "Whatsapp not Installed", Toast.LENGTH_SHORT).show(); 86 | } 87 | } else { 88 | send(); 89 | } 90 | } else { 91 | send(); 92 | } 93 | return START_STICKY; 94 | } 95 | 96 | @SuppressLint("ApplySharedPref") 97 | private void send() { 98 | if (progress >= recipientList.size()) { 99 | Toast.makeText(this, "Task Completed", Toast.LENGTH_SHORT).show(); 100 | sp.edit().putBoolean("running", false).commit(); 101 | return; 102 | } 103 | String recipient = recipientList.get(progress)[0]; 104 | String message = recipientList.get(progress)[1]; 105 | Intent sendIntent = new Intent(); 106 | sendIntent.setPackage("com.whatsapp"); 107 | sendIntent.setAction("android.intent.action.SEND"); 108 | sendIntent.setType("text/plain"); 109 | sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 110 | sendIntent.putExtra(Intent.EXTRA_TEXT, message); 111 | sendIntent.putExtra("jid", recipient + "@s.whatsapp.net"); 112 | progress++; 113 | startActivity(sendIntent); 114 | } 115 | 116 | @Override 117 | public void onCreate(){ 118 | super.onCreate(); 119 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 120 | startMyOwnForeground(); 121 | } 122 | else{ 123 | Notification.Builder notificationBuilder = new Notification.Builder(this); 124 | notificationBuilder.setSmallIcon(R.mipmap.ic_launcher); 125 | notificationBuilder.setContentText("App Running in Background"); 126 | Notification not = notificationBuilder.build(); 127 | startForeground(NOTIFICATION_ID, not); 128 | } 129 | } 130 | 131 | @TargetApi(Build.VERSION_CODES.O) 132 | private void startMyOwnForeground(){ 133 | String NOTIFICATION_CHANNEL_ID = "ga.nikhilkumar.whatsappsender"; 134 | String channelName = "Background Service"; 135 | NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE); 136 | chan.setLightColor(Color.GREEN); 137 | chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); 138 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 139 | assert manager != null; 140 | manager.createNotificationChannel(chan); 141 | 142 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); 143 | Notification notification = notificationBuilder.setOngoing(true) 144 | .setSmallIcon(R.mipmap.ic_launcher) 145 | .setContentTitle("App Running in Background") 146 | .setPriority(NotificationManager.IMPORTANCE_MIN) 147 | .setCategory(Notification.CATEGORY_SERVICE) 148 | .build(); 149 | startForeground(NOTIFICATION_ID, notification); 150 | } 151 | } -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/Utils.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.net.Uri; 6 | import android.webkit.MimeTypeMap; 7 | 8 | import java.io.File; 9 | 10 | public class Utils { 11 | 12 | public static String getMimeType(Context context, File file) { 13 | String mimeType = null; 14 | Uri uri = Uri.fromFile(file); 15 | if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { 16 | ContentResolver cr = context.getContentResolver(); 17 | mimeType = cr.getType(uri); 18 | } else { 19 | String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri 20 | .toString()); 21 | mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( 22 | fileExtension.toLowerCase()); 23 | } 24 | return mimeType; 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/WhatsappApi.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.database.Cursor; 9 | import android.database.sqlite.SQLiteDatabase; 10 | import android.graphics.Bitmap; 11 | import android.graphics.BitmapFactory; 12 | import android.media.ThumbnailUtils; 13 | import android.os.AsyncTask; 14 | import android.os.Environment; 15 | import android.provider.MediaStore; 16 | import android.text.TextUtils; 17 | import android.util.Log; 18 | 19 | import org.apache.commons.io.FilenameUtils; 20 | import org.apache.commons.lang3.SerializationUtils; 21 | 22 | import java.io.ByteArrayOutputStream; 23 | import java.io.File; 24 | import java.io.FileInputStream; 25 | import java.io.FileOutputStream; 26 | import java.io.IOException; 27 | import java.nio.channels.FileChannel; 28 | import java.text.SimpleDateFormat; 29 | import java.util.Calendar; 30 | import java.util.LinkedList; 31 | import java.util.List; 32 | import java.util.Random; 33 | 34 | import eu.chainfire.libsuperuser.Shell; 35 | import ga.nikhilkumar.whatsappsender.sender.exception.WhatsappNotInstalledException; 36 | import ga.nikhilkumar.whatsappsender.sender.liseteners.GetContactsListener; 37 | import ga.nikhilkumar.whatsappsender.sender.liseteners.SendMessageListener; 38 | import ga.nikhilkumar.whatsappsender.sender.model.WContact; 39 | import ga.nikhilkumar.whatsappsender.sender.model.WMessage; 40 | import ga.nikhilkumar.whatsappsender.whatsapp.MediaData; 41 | 42 | public class WhatsappApi { 43 | Context launchContext = null; 44 | private static WhatsappApi instance; 45 | private static String imgFolder = Environment.getExternalStorageDirectory().getAbsolutePath() + "/WhatsApp/Media/WhatsApp Images/Sent"; 46 | private static String vidFolder = Environment.getExternalStorageDirectory().getAbsolutePath() + "/WhatsApp/Media/WhatsApp Video/Sent"; 47 | private static String audFolder = Environment.getExternalStorageDirectory().getAbsolutePath() + "/WhatsApp/Media/WhatsApp Audio/Sent"; 48 | boolean isRootAvailable; 49 | private SQLiteDatabase db; 50 | 51 | private WhatsappApi() { 52 | 53 | boolean suAvailable = Shell.SU.available(); 54 | if (suAvailable) { 55 | Shell.SU.run("am force-stop com.whatsapp"); 56 | Shell.SU.run("mount -o -R rw,remount /data/data/com.whatsapp"); 57 | Shell.SU.run("mount -o rw,remount /data/data/com.whatsapp/databases"); 58 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases"); 59 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/files"); 60 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/shared_prefs"); 61 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases/msgstore.db"); 62 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases/msgstore.db-wal"); 63 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases/msgstore.db-shm"); 64 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases/wa.db"); 65 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases/wa.db-wal"); 66 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases/wa.db-shm"); 67 | Shell.SU.run("ls -l /data/data/com.whatsapp/databases/msgstore.db-shm"); 68 | isRootAvailable = true; 69 | 70 | } else { 71 | 72 | isRootAvailable = false; 73 | } 74 | 75 | 76 | } 77 | 78 | public static WhatsappApi getInstance() { 79 | if (instance == null) 80 | instance = new WhatsappApi(); 81 | return instance; 82 | } 83 | 84 | public boolean isWhatsappInstalled() { 85 | File file = new File("/data/data/com.whatsapp/"); 86 | return file.exists(); 87 | } 88 | 89 | public void sendMessage(WContact contact, WMessage message, Context context, SendMessageListener listener) throws WhatsappNotInstalledException { 90 | List contacts = new LinkedList<>(); 91 | contacts.add(contact); 92 | List messages = new LinkedList<>(); 93 | messages.add(message); 94 | sendMessage(contacts, messages, context, listener); 95 | } 96 | 97 | @SuppressLint("StaticFieldLeak") 98 | public synchronized void sendMessage(final List contacts, final List messages, final Context context, final SendMessageListener listener) throws WhatsappNotInstalledException { 99 | launchContext = context; 100 | if (isWhatsappInstalled()) { 101 | new AsyncTask() { 102 | @Override 103 | protected Boolean doInBackground(Void... params) { 104 | boolean result = true; 105 | Shell.SU.run("am force-stop com.whatsapp"); 106 | Shell.SU.run("mv /data/data/com.whatsapp/databases/msgstore.db " + context.getDatabasePath("msgstore.db").getAbsolutePath()); 107 | // Shell.SU.run("mv /data/data/com.whatsapp/databases/msgstore.db-wal " + context.getDatabasePath("msgstore.db").getAbsolutePath() + "-wal"); 108 | // Shell.SU.run("mv /data/data/com.whatsapp/databases/msgstore.db-shm " + context.getDatabasePath("msgstore.db").getAbsolutePath() + "-shm"); 109 | Shell.SU.run("chmod 777 " + context.getDatabasePath("msgstore.db").getAbsolutePath()); 110 | // Shell.SU.run("chmod 777 " + context.getDatabasePath("msgstore.db").getAbsolutePath() + "-wal"); 111 | // Shell.SU.run("chmod 777 " + context.getDatabasePath("msgstore.db").getAbsolutePath() + "-shm"); 112 | try { 113 | db = SQLiteDatabase.openOrCreateDatabase(new File(context.getDatabasePath("msgstore.db").getAbsolutePath()), null); 114 | for (int i = 0; i < contacts.size(); i++) { 115 | try { 116 | Log.e("MSG", contacts.get(i).toString()); 117 | sendMessage(contacts.get(i), messages.get(i)); 118 | } catch (IOException e) { 119 | e.printStackTrace(); 120 | } 121 | } 122 | db.close(); 123 | } catch (Exception e) { 124 | Log.e("SQL", e.getMessage()); 125 | result = false; 126 | } 127 | Shell.SU.run("mv " + context.getDatabasePath("msgstore.db").getAbsolutePath() + " /data/data/com.whatsapp/databases/msgstore.db"); 128 | // Shell.SU.run("mv " + context.getDatabasePath("msgstore.db").getAbsolutePath() + "-wal /data/data/com.whatsapp/databases/msgstore.db-wal"); 129 | // Shell.SU.run("mv " + context.getDatabasePath("msgstore.db").getAbsolutePath() + "-shm /data/data/com.whatsapp/databases/msgstore.db-shm"); 130 | Shell.SU.run("chmod 777 /data/data/com.whatsapp/databases/msgstore.db"); 131 | Shell.SU.run("rm /data/data/com.whatsapp/databases/msgstore.db-wal"); 132 | Shell.SU.run("rm /data/data/com.whatsapp/databases/msgstore.db-shm"); 133 | PackageManager pm = context.getPackageManager(); 134 | Intent intent = pm.getLaunchIntentForPackage("com.whatsapp"); 135 | context.startActivity(intent); 136 | return result; 137 | } 138 | 139 | @Override 140 | protected void onPostExecute(Boolean finish) { 141 | super.onPostExecute(finish); 142 | if (listener != null) { 143 | listener.finishSendWMessage(contacts, messages); 144 | } 145 | } 146 | }.execute(); 147 | } else 148 | throw new WhatsappNotInstalledException(); 149 | } 150 | 151 | private void sendMessage(WContact contact, WMessage message) throws IOException { 152 | 153 | String name = null; 154 | Calendar c = null; 155 | String formattedDate = null; 156 | SimpleDateFormat df = null; 157 | File source = null; 158 | Random rand = null; 159 | File destination = null; 160 | 161 | switch (message.getType()) { 162 | case TEXT: 163 | break; 164 | case VIDEO: 165 | name = message.getFile().getPath(); 166 | c = Calendar.getInstance(); 167 | df = new SimpleDateFormat("yyyyMMMdd"); 168 | formattedDate = df.format(c.getTime()); 169 | source = new File(name); 170 | rand = new Random(); 171 | destination = new File(vidFolder, "VID-" + formattedDate + "-WA" + (rand.nextInt(100) + rand.nextInt(75) + rand.nextInt(50)) + "." + FilenameUtils.getExtension(message.getFile().getName())); 172 | if (source.exists()) { 173 | FileChannel src = new FileInputStream(source).getChannel(); 174 | FileChannel dst = new FileOutputStream(destination).getChannel(); 175 | dst.transferFrom(src, 0, src.size()); 176 | src.close(); 177 | dst.close(); 178 | } 179 | name = destination.getName(); 180 | break; 181 | case IMAGE: 182 | name = message.getFile().getPath(); 183 | c = Calendar.getInstance(); 184 | df = new SimpleDateFormat("yyyyMMdd"); 185 | formattedDate = df.format(c.getTime()); 186 | source = new File(name); 187 | rand = new Random(); 188 | destination = new File(imgFolder, "IMG-" + formattedDate + "-WA" + (rand.nextInt(100) + rand.nextInt(75) + rand.nextInt(50)) + "." + FilenameUtils.getExtension(message.getFile().getName())); 189 | if (source.exists()) { 190 | FileChannel src = new FileInputStream(source).getChannel(); 191 | FileChannel dst = new FileOutputStream(destination).getChannel(); 192 | dst.transferFrom(src, 0, src.size()); 193 | src.close(); 194 | dst.close(); 195 | } 196 | name = destination.getName(); 197 | break; 198 | case AUDIO: 199 | name = message.getFile().getPath(); 200 | c = Calendar.getInstance(); 201 | df = new SimpleDateFormat("yyyyMMdd"); 202 | formattedDate = df.format(c.getTime()); 203 | source = new File(name); 204 | rand = new Random(); 205 | destination = new File(audFolder, "AUD-" + formattedDate + "-WA" + (rand.nextInt(100) + rand.nextInt(75) + rand.nextInt(50)) + "." + FilenameUtils.getExtension(message.getFile().getName())); 206 | if (source.exists()) { 207 | FileChannel src = new FileInputStream(source).getChannel(); 208 | FileChannel dst = new FileOutputStream(destination).getChannel(); 209 | dst.transferFrom(src, 0, src.size()); 210 | src.close(); 211 | dst.close(); 212 | } 213 | name = destination.getName(); 214 | break; 215 | } 216 | sendBigMessage(contact.getId(), message.getText(), name, message.getMime()); 217 | } 218 | 219 | @SuppressLint("StaticFieldLeak") 220 | public synchronized void getContacts(Context context, final GetContactsListener listener) throws WhatsappNotInstalledException { 221 | 222 | if (isWhatsappInstalled()) { 223 | new AsyncTask>() { 224 | @Override 225 | protected List doInBackground(Void... params) { 226 | Shell.SU.run("am force-stop com.whatsapp"); 227 | db = SQLiteDatabase.openOrCreateDatabase(new File("/data/data/com.whatsapp/databases/wa.db"), null); 228 | List contactList = new LinkedList<>(); 229 | String selectQuery = "SELECT jid, display_name FROM wa_contacts where phone_type is not null and is_whatsapp_user = 1"; 230 | Cursor cursor = db.rawQuery(selectQuery, null); 231 | if (cursor.moveToFirst()) { 232 | do { 233 | WContact contact = new WContact(cursor.getString(1), cursor.getString(0)); 234 | contactList.add(contact); 235 | } while (cursor.moveToNext()); 236 | } 237 | db.close(); 238 | return contactList; 239 | } 240 | 241 | @Override 242 | protected void onPostExecute(List contacts) { 243 | super.onPostExecute(contacts); 244 | if (listener != null) { 245 | listener.receiveWhatsappContacts(contacts); 246 | } 247 | } 248 | }.execute(); 249 | 250 | 251 | } else 252 | throw new WhatsappNotInstalledException(); 253 | 254 | 255 | } 256 | 257 | private void sendBigMessage(String jid, String msg, String file, String mimeType) { 258 | long l1; 259 | long l2; 260 | int k; 261 | String query2, query1; 262 | 263 | Random localRandom = new Random(20L); 264 | l1 = System.currentTimeMillis(); 265 | l2 = l1 / 1000L; 266 | k = localRandom.nextInt(); 267 | 268 | int mediaType = 0; 269 | 270 | if (mimeType == null || mimeType.length() < 2) 271 | mediaType = 0; 272 | else 273 | mediaType = (mimeType.contains("video")) ? 3 274 | : (mimeType.contains("image")) ? 1 275 | : (mimeType.contains("audio")) ? 2 276 | : 0; 277 | 278 | ContentValues initialValues = new ContentValues(); 279 | initialValues.put("key_remote_jid", jid); 280 | initialValues.put("key_from_me", 1); 281 | initialValues.put("key_id", l2 + "-" + k); 282 | initialValues.put("status", 1); 283 | initialValues.put("needs_push", 0); 284 | initialValues.put("timestamp", l1); 285 | initialValues.put("media_wa_type", mediaType); 286 | initialValues.put("media_name", file); 287 | initialValues.put("latitude", 0.0); 288 | initialValues.put("longitude", 0.0); 289 | initialValues.put("received_timestamp", l1); 290 | initialValues.put("send_timestamp", -1); 291 | initialValues.put("receipt_server_timestamp", -1); 292 | initialValues.put("receipt_device_timestamp", -1); 293 | initialValues.put("raw_data", -1); 294 | initialValues.put("recipient_count", 0); 295 | initialValues.put("media_duration", 0); 296 | 297 | if (!TextUtils.isEmpty(file) && !TextUtils.isEmpty(mimeType)) { 298 | Bitmap bMap = null; 299 | File spec; 300 | if (mediaType == 3) { 301 | spec = new File(vidFolder, file); 302 | bMap = ThumbnailUtils.createVideoThumbnail(spec.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND); 303 | } else if (mediaType == 2) { 304 | spec = new File(audFolder, file); 305 | } else { 306 | spec = new File(imgFolder, file); 307 | bMap = BitmapFactory.decodeFile(spec.getAbsolutePath()); 308 | } 309 | long mediaSize = (file.equals("")) ? 0 : spec.length(); 310 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 311 | if (mediaType == 1 || mediaType == 3) { 312 | bMap = Bitmap.createScaledBitmap(bMap, 100, 59, false); 313 | bMap.compress(Bitmap.CompressFormat.JPEG, 60, bos); 314 | } 315 | byte[] bArray = bos.toByteArray(); 316 | 317 | MediaData md = new MediaData(); 318 | md.fileSize = mediaSize; 319 | md.file = spec; 320 | md.autodownloadRetryEnabled = true; 321 | byte[] arr = SerializationUtils.serialize(md); 322 | 323 | initialValues.put("thumb_image", arr); 324 | initialValues.put("quoted_row_id", 0); 325 | initialValues.put("raw_data", bArray); 326 | initialValues.put("media_size", mediaSize); 327 | initialValues.put("origin", 0); 328 | initialValues.put("media_caption", msg); 329 | } else 330 | initialValues.put("data", msg); 331 | 332 | long idm = db.insert("messages", null, initialValues); 333 | 334 | query1 = " insert into chat_list (key_remote_jid) select '" + jid 335 | + "' where not exists (select 1 from chat_list where key_remote_jid='" + jid + "');"; 336 | 337 | query2 = " update chat_list set message_table_id = (select max(messages._id) from messages) where chat_list.key_remote_jid='" + jid + "';"; 338 | 339 | 340 | ContentValues values = new ContentValues(); 341 | values.put("docid", idm); 342 | values.put("c0content", "null "); 343 | db.insert("messages_fts_content", null, values); 344 | 345 | 346 | db.execSQL(query1 + query2); 347 | } 348 | 349 | public boolean isRootAvailable() { 350 | return isRootAvailable; 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/exception/RootNotAvailableException.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender.exception; 2 | 3 | 4 | public class RootNotAvailableException extends Exception { 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/exception/WhatsappNotInstalledException.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender.exception; 2 | 3 | public class WhatsappNotInstalledException extends Exception { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/liseteners/GetContactsListener.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender.liseteners; 2 | 3 | import java.util.List; 4 | 5 | import ga.nikhilkumar.whatsappsender.sender.model.WContact; 6 | 7 | public interface GetContactsListener { 8 | 9 | void receiveWhatsappContacts(List contacts); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/liseteners/SendMessageListener.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender.liseteners; 2 | 3 | import java.util.List; 4 | 5 | import ga.nikhilkumar.whatsappsender.sender.model.WContact; 6 | import ga.nikhilkumar.whatsappsender.sender.model.WMessage; 7 | 8 | public interface SendMessageListener { 9 | void finishSendWMessage(List contact, List messages); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/model/WContact.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender.model; 2 | 3 | public class WContact { 4 | 5 | String name; 6 | String id; 7 | 8 | public WContact(String name, String id) { 9 | this.name = name; 10 | this.id = id; 11 | 12 | 13 | if (!id.endsWith("@s.whatsapp.net")) 14 | throw new IllegalArgumentException("ID for Whatsapp contact should ends with : @s.whatsapp.net , for example 9999999999@s.whatsapp.net"); 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public void setName(String name) { 22 | this.name = name; 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | public void setId(String id) { 30 | this.id = id; 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (o == null || getClass() != o.getClass()) return false; 37 | 38 | WContact contact = (WContact) o; 39 | 40 | if (name != null ? !name.equals(contact.name) : contact.name != null) return false; 41 | return id != null ? id.equals(contact.id) : contact.id == null; 42 | 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | int result = name != null ? name.hashCode() : 0; 48 | result = 31 * result + (id != null ? id.hashCode() : 0); 49 | return result; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "WContact{" + 55 | "name='" + name + '\'' + 56 | ", id='" + id + '\'' + 57 | '}'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/sender/model/WMessage.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.sender.model; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | 7 | import ga.nikhilkumar.whatsappsender.sender.Utils; 8 | 9 | public class WMessage { 10 | String text; 11 | String mime; 12 | File file; 13 | MessageType type; 14 | double lat, lng; 15 | 16 | public WMessage(String text, File file, Context context) { 17 | this.text = text; 18 | this.file = file; 19 | if (file != null) { 20 | mime = Utils.getMimeType(context, file); 21 | if (mime == null) 22 | type = MessageType.TEXT; 23 | else if (mime.contains("video")) 24 | type = MessageType.VIDEO; 25 | else if (mime.contains("audio")) 26 | type = MessageType.AUDIO; 27 | else if (mime.contains("image")) 28 | type = MessageType.IMAGE; 29 | 30 | } else 31 | type = MessageType.TEXT; 32 | } 33 | 34 | public String getText() { 35 | return text; 36 | } 37 | 38 | public void setText(String text) { 39 | this.text = text; 40 | } 41 | 42 | public File getFile() { 43 | return file; 44 | } 45 | 46 | public void setFile(File file) { 47 | this.file = file; 48 | } 49 | 50 | public MessageType getType() { 51 | return type; 52 | } 53 | 54 | public void setType(MessageType type) { 55 | this.type = type; 56 | } 57 | 58 | public String getMime() { 59 | return mime; 60 | } 61 | 62 | public double getLat() { 63 | return lat; 64 | } 65 | 66 | public void setLat(double lat) { 67 | this.lat = lat; 68 | } 69 | 70 | public double getLng() { 71 | return lng; 72 | } 73 | 74 | public void setLng(double lng) { 75 | this.lng = lng; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (this == o) return true; 81 | if (o == null || getClass() != o.getClass()) return false; 82 | 83 | WMessage wMessage = (WMessage) o; 84 | 85 | if (text != null ? !text.equals(wMessage.text) : wMessage.text != null) return false; 86 | if (mime != null ? !mime.equals(wMessage.mime) : wMessage.mime != null) return false; 87 | if (file != null ? !file.equals(wMessage.file) : wMessage.file != null) return false; 88 | return type == wMessage.type; 89 | 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | int result = text != null ? text.hashCode() : 0; 95 | result = 31 * result + (mime != null ? mime.hashCode() : 0); 96 | result = 31 * result + (file != null ? file.hashCode() : 0); 97 | result = 31 * result + (type != null ? type.hashCode() : 0); 98 | return result; 99 | } 100 | 101 | @Override 102 | public String toString() { 103 | return "WMessage{" + 104 | "text='" + text + '\'' + 105 | ", mime='" + mime + '\'' + 106 | ", file=" + file + 107 | ", type=" + type + 108 | '}'; 109 | } 110 | 111 | public enum MessageType {TEXT, VIDEO, IMAGE, AUDIO} 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/ga/nikhilkumar/whatsappsender/whatsapp/MediaData.java: -------------------------------------------------------------------------------- 1 | package ga.nikhilkumar.whatsappsender.whatsapp; 2 | 3 | import java.io.File; 4 | import java.io.Serializable; 5 | 6 | public class MediaData implements Serializable { 7 | 8 | private static final long serialVersionUID = -3211751283609594L; 9 | 10 | public File file; 11 | 12 | public long fileSize, progress, trimFrom, trimTo; 13 | public int width, height, faceX, faceY, failErrorCode, suspiciousContent; 14 | public String uploadUrl; 15 | 16 | public boolean transcoded, transferred, autodownloadRetryEnabled; 17 | 18 | byte[] hmacKey, iv, mediaKey, refKey; 19 | 20 | public MediaData() { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sender.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |