├── .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 | [](https://www.android.com)
3 | 
4 | 
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 |
12 |
13 |
14 | 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.
15 | Go back to app activity.
16 | 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.
17 | Hit "Send" button.
18 | Sit back and relax.
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 | Open chrome browser and navigate to chrome://extensions/.
31 | Enable Developer mode from top right.
32 | Click "Load unpacked" and browse for chrome folder in this repo.
33 | Open WhatsApp Web at https://web.whatsapp.com and login.
34 | 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.
35 | Click on Submit to start sending messages.
36 | Wait for alert on completion of task.
37 | Get logs in the browser console.
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 |
16 |
17 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3fb551
4 | #309F3F
5 | #FF8140
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WhatsApp Sender
3 | Required to auto send messages
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/service_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/test/java/ga/nikhilkumar/whatsappsender/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package ga.nikhilkumar.whatsappsender;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.1'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url 'https://jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/chrome/background.js:
--------------------------------------------------------------------------------
1 | /*
2 | This variable is responsible for keeping track of the active WhatsApp tab.
3 | It is updated once the tab communicates with the background page (sends a type:start request)
4 | */
5 | let whatsapp_tab_id = -1;
6 |
7 | /*
8 | Injecting the content script into the WhatsApp webpage and resetting the connection settings to the host.
9 | */
10 | chrome.webNavigation.onCompleted.addListener(function(details) {
11 |
12 | if (~details.url.indexOf("https://web.whatsapp.com/")) {
13 | console.log("Injecting");
14 |
15 | whatsapp_tab_id = details.tabId;
16 | console.log(whatsapp_tab_id);
17 |
18 | // Injecting the content script into the page.
19 | chrome.tabs.executeScript(details.tabId, {file: "js/content.js"});
20 | }
21 | });
22 |
23 | /*
24 | Passes a message to the client (dict-obj)
25 | Full journey from here - to content script, then to the webpage through the DOM
26 | */
27 | function clientMessage(data) {
28 | chrome.tabs.sendMessage(whatsapp_tab_id, data);
29 | }
30 |
31 | /*
32 | Listening to messages from the content script (webpage -> content script -> *background page* -> host)
33 | */
34 | chrome.runtime.onMessage.addListener(function(request, sender) {
35 | if (sender.tab) {
36 | // From content script
37 | console.log("Background page received: ", request, sender);
38 |
39 | if (request.type === "ajax") {
40 | let m = request.ajax;
41 | m.success = function(e) {
42 | clientMessage({type: "ajax", ajax_id: request.ajax_id, status: "success", e: e});
43 | };
44 | m.error = function(a, b, c) {
45 | clientMessage({type: "ajax", ajax_id: request.ajax_id, status: "error", a: a, b: b, c: c});
46 | };
47 | $.ajax(m);
48 | }
49 | }
50 | });
51 |
52 |
--------------------------------------------------------------------------------
/chrome/html/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | WhatsApp Bulk Sender Version:
9 | This version is deprecated and may not be compatible with latest WhatsApp Web.
10 |
11 |
12 |
--------------------------------------------------------------------------------
/chrome/html/popup.js:
--------------------------------------------------------------------------------
1 | window.onload = function() {
2 | document.getElementById("verspan").innerHTML = chrome.runtime.getManifest().version;
3 | document.getElementById("sender").onclick = function () {
4 | chrome.tabs.executeScript({code: 'document.getElementById("sender").click();'});
5 | };
6 | document.getElementById("csv").onclick = function () {
7 | chrome.tabs.executeScript({code: 'document.getElementById("csv").click();'});
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/chrome/js/api.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | /*
4 | The core scripts of the API. Currently is public through `window` but will be hidden in production mode.
5 | */
6 | window.Core = {
7 |
8 | /*
9 | Returns a WhatsApp GroupMetadata object from a given group id.
10 | */
11 | group: function(_id) {
12 | let result = null;
13 | Store.GroupMetadata.models.forEach(x => {
14 | if (x.hasOwnProperty("__x_id") && x.__x_id == _id) {
15 | result = x;
16 | }
17 | });
18 | return result;
19 | },
20 |
21 | /*
22 | Returns a WhatsApp Contact object from a given contact id.
23 | */
24 | contact: function(_id) {
25 | let result = null;
26 | Store.Contact.models.forEach(x => {
27 | if (x.hasOwnProperty("__x_id") && x.__x_id == _id) {
28 | result = x;
29 | }
30 | });
31 | return result;
32 | },
33 |
34 | /*
35 | Returns a WhatsApp Chat object from a given chat id.
36 | */
37 | chat: function(_id) {
38 | let result = null;
39 | Store.Chat.models.forEach(x => {
40 | if (x.hasOwnProperty("__x_id") && x.__x_id == _id) {
41 | result = x;
42 | }
43 | });
44 | return result;
45 | },
46 |
47 | /*
48 | Returns a WhatsApp Msg object from a given serialized message id
49 | */
50 | msg: function(_id) {
51 | let result = null;
52 | Store.Msg.models.forEach(x => {
53 | if (x.hasOwnProperty("__x_id") && x.__x_id._serialized == _id) {
54 | result = x;
55 | }
56 | });
57 | return result;
58 | },
59 |
60 | /*
61 | Returns the element of a collection that satisfies a predicate condition.
62 | */
63 | find: function(collection, predicate) {
64 | let result = null;
65 | collection.forEach(x => {
66 | if (predicate(x)) {
67 | result = x;
68 | }
69 | });
70 | return result;
71 | },
72 |
73 | /*
74 | Calls a callback with an error object.
75 | */
76 | error: function(err, callback) {
77 | setTimeout(x => { (callback || Core.nop)({error: err}); }, 1);
78 | },
79 |
80 | /*
81 | Does nothing.
82 | */
83 | nop: function() {}
84 |
85 | };
86 |
87 | /*
88 | API Listener - listens for new events (via messages) and handles them.
89 | */
90 | const Listener = function () {
91 |
92 | this.ExternalHandlers = {
93 |
94 | /*
95 | Parameters:
96 | 1. The user that joined
97 | 2. The user that added them (undefined if they used a link? Should be checked)
98 | 3. The chat the user was added to
99 | */
100 | USER_JOIN_GROUP: [],
101 |
102 | /*
103 | Parameters:
104 | 1. The user that was removed
105 | 2. The user that removed them (undefined if they used a link? Should be checked)
106 | 3. The chat the user was removed from
107 | */
108 | USER_LEAVE_GROUP: [],
109 |
110 | /*
111 | Parameters:
112 | 1. The group ID
113 | 2. The user that changed the title
114 | 3. The new title
115 | 4. The subject type (should be 'subject')
116 | */
117 | GROUP_SUBJECT_CHANGE: [],
118 |
119 | /*
120 | Parameters:
121 | 1. Sender of the message
122 | 2. Chat the message was sent at
123 | 3. Parsed Msg object
124 | */
125 | MESSAGE_RECEIVED: [],
126 |
127 | /*
128 | Parameters:
129 | 1. The chat the message was sent to
130 | 2. Parsed Msg object
131 | */
132 | MESSAGE_SENT: []
133 | };
134 |
135 | /*
136 | Handlers for different message types
137 | */
138 | const handlers = [
139 | /*
140 | User join / leave group.
141 | */
142 | {
143 | predicate: msg => msg.__x_isNotification && msg.__x_eventType == "i" && msg.__x_type == "gp2",
144 | handler: function (msg) {
145 | const is_join = msg.__x_subtype == "add" || msg.__x_subtype == "invite";
146 | const is_leave = msg.__x_subtype == "leave" || msg.__x_subtype == "remove";
147 | const object = msg.__x_recipients[0];
148 | const subject = msg.__x_sender;
149 | const chat = msg.chat.__x_id;
150 |
151 | if (is_join) {
152 | API.listener.ExternalHandlers.USER_JOIN_GROUP.forEach(x => x(object, subject, chat));
153 | } else if (is_leave) {
154 | API.listener.ExternalHandlers.USER_LEAVE_GROUP.forEach(x => x(object, subject, chat));
155 | }
156 | }
157 | },
158 | /*
159 | Group subject change.
160 | */
161 | {
162 | predicate: msg => msg.__x_isNotification && msg.__x_eventType == "n",
163 | handler: function (msg) {
164 | const chat = msg.__x_to;
165 | const changer = msg.__x_sender;
166 | const new_title = msg.__x_body;
167 | const subtype = msg.__x_subtype;
168 | API.listener.ExternalHandlers.GROUP_SUBJECT_CHANGE.forEach(x => x(chat, changer, new_title, subtype));
169 | }
170 | },
171 | /*
172 | Message received
173 | */
174 | {
175 | predicate: msg => msg.__x_isUserCreatedType && !msg.__x_isNotification && !msg.__x_isSentByMe,
176 | handler: function (msg) {
177 | let sender = msg.__x_sender;
178 | let chat = msg.__x_from;
179 | let message = msg.__x_id._serialized;
180 | console.log(msg);
181 | API.listener.ExternalHandlers.MESSAGE_RECEIVED.forEach(x => x(sender, chat, API.parseMsgObject(msg)));
182 | }
183 | },
184 | /*
185 | Message sent
186 | */
187 | {
188 | predicate: msg => msg.__x_isUserCreatedType && !msg.__x_isNotification && msg.__x_isSentByMe,
189 | handler: function (msg) {
190 | let to = msg.__x_to;
191 | API.listener.ExternalHandlers.MESSAGE_SENT.forEach(x => x(to, API.parseMsgObject(msg), msg));
192 | }
193 | }
194 | ];
195 |
196 | /*
197 | Handles a new incoming message
198 | */
199 | let handle_msg = function (msg) {
200 | for (let i = 0; i < handlers.length; i++) {
201 | if (handlers[i].predicate(msg)) {
202 | handlers[i].handler(msg);
203 | console.log("Firing handler " + i);
204 | return;
205 | }
206 | }
207 | console.log("No suitable handlers were found for ", msg);
208 | };
209 |
210 | /*
211 | Goes through messages and filters new ones out. Then calls handle_msg on the newly created ones.
212 | */
213 | let check_update = function () {
214 |
215 | if (window.Store) {
216 | Store.Msg.models.forEach(model => {
217 | if (model.__x_isNewMsg) {
218 | model.__x_isNewMsg = false;
219 | handle_msg(model);
220 | }
221 | });
222 | } else {
223 | window.makeStore();
224 | }
225 | };
226 |
227 | /*
228 | Clears previously created listeners and starts a new one.
229 | */
230 | this.listen = function () {
231 | if (window.API_LISTENER_TOKEN) {
232 | clearInterval(window.API_LISTENER_TOKEN);
233 | }
234 |
235 | window.API_LISTENER_TOKEN = setInterval(check_update, 10);
236 | };
237 |
238 | };
239 |
240 | /*
241 | This is the API, which contains functions, literals, constants and utilities to integrate with WhatsApp Web version.
242 | */
243 | window.API = {
244 |
245 | /*
246 | Exception constants.
247 | */
248 | Error: {
249 | OK: true,
250 | USER_NOT_FOUND: "The specified user ID was not found",
251 | CHAT_NOT_FOUND: "The specified chat ID was not found",
252 | GROUP_NOT_FOUND: "The specified group metadata ID was not found",
253 | USER_NOT_IN_GROUP: "The specified user is not a member of the required group"
254 | },
255 |
256 | /*
257 | Returns the contact ID from a given phone number.
258 | Only digits in the phone number. Example: "972557267388" and not "(+972) 055-726-7388"
259 | */
260 | findContactId: function(phone_number) {
261 | let result = null;
262 | Store.Contact.models.forEach(x => {
263 | if (x.hasOwnProperty("__x_id") && (x.__x_id.match(/\d+/g) || []).join("") == phone_number) {
264 | result = x.__x_id;
265 | }
266 | });
267 | return result || null;
268 | },
269 |
270 | /*
271 | Returns an array of chat ID's that correspond to chats with the parameter in the title.
272 | For example, calling it with title='John' may return the ID's of the chats: 'John Smith', 'John from the cafeteria', and 'Johnna\'s birthday party 2016'
273 | */
274 | findChatIds: function(title) {
275 | const result = [];
276 | Store.Chat.models.forEach(x => {
277 | if (x.hasOwnProperty("__x_formattedTitle") && ~(x.__x_formattedTitle.indexOf(title))) {
278 | result.push(x.__x_id);
279 | }
280 | });
281 | return result;
282 | },
283 |
284 | listener: new Listener(),
285 |
286 | /*
287 | Adds a user to a group.
288 | Parameters:
289 | user_id - the ID of the user (NOT the phone number)
290 | group_id - the ID of the group
291 | callback - to be invoked after the operation finishes
292 | */
293 | addUserToGroup: function(user_id, group_id, callback) {
294 | let group = Core.group(group_id);
295 | let user = Core.contact(user_id);
296 |
297 | if (group == null) {
298 | Core.error(API.Error.GROUP_NOT_FOUND, callback);
299 | return;
300 | }
301 | if (user == null) {
302 | Core.error(API.Error.USER_NOT_FOUND, callback);
303 | return;
304 | }
305 |
306 | group.participants.addParticipant(user).then(callback);
307 | },
308 |
309 | /*
310 | Removes a user from a group.
311 | Parameters:
312 | user_id - the ID of the user (NOT the phone number)
313 | group_id - the ID of the group
314 | callback - to be invoked after the operation finishes
315 | */
316 | removeUserFromGroup: function(user_id, group_id, callback) {
317 | let group = Core.group(group_id);
318 | if (group == null) {
319 | Core.error(API.Error.GROUP_NOT_FOUND, callback);
320 | return;
321 | }
322 |
323 | let user = Core.find(group.participants, x => x.hasOwnProperty("__x_id") && x.__x_id == user_id);
324 | if (user == null) {
325 | Core.error(API.Error.USER_NOT_IN_GROUP, callback || Core.nop);
326 | return;
327 | }
328 |
329 | group.participants.removeParticipant(user).then(callback);
330 | },
331 |
332 | /*
333 | Sets a chat's archived status
334 | Parameters:
335 | chat_id - the ID of the conversation
336 | archive_status - true for archiving, false for unarchiving.
337 | callback - to be invoked after the operation finishes
338 | */
339 | setChatArchiveStatus: function(chat_id, archive_status, callback) {
340 | let chat = Core.chat(chat_id);
341 | if (chat == null) {
342 | Core.error(API.Error.CHAT_NOT_FOUND, callback);
343 | return;
344 | }
345 |
346 | chat.setArchive(!!archive_status).then(function() {
347 | (callback || Core.nop)({status: 200});
348 | });
349 | },
350 |
351 | /*
352 | Gets the archive status of a chat
353 | Parameters:
354 | chat_id - the ID of the conversation
355 | Return value:
356 | bool - true if archived, false if not archived.
357 | null - if chat was not found
358 | */
359 | getChatArchiveStatus: function(chat_id) {
360 | let chat = Core.chat(chat_id);
361 | if (chat == null) {
362 | return null;
363 | }
364 |
365 | return chat.archive;
366 | },
367 |
368 | /*
369 | Gets the invite link for a group.
370 | Parameters:
371 | group_id - the ID of the group
372 | Return value:
373 | string - the invite link
374 | null - if the group was not found
375 | */
376 | getGroupInviteLink: function(group_id) {
377 | let group = Core.group(group_id);
378 | if (group == null) {
379 | return null;
380 | }
381 |
382 | return group.groupInviteLink;
383 | },
384 |
385 | /*
386 | Revokes a group's invite link.
387 | Parameters:
388 | group_id - the ID of the group
389 | callback - to be invoked after the operation completes
390 | */
391 | revokeGroupInviteLink: function(group_id, callback) {
392 | let group = Core.group(group_id);
393 | if (group == null) {
394 | Core.error(Core.Error.GROUP_NOT_FOUND, callback);
395 | return;
396 | }
397 |
398 | group.revokeGroupInvite().then(function(e) {
399 | (callback || Core.nop)({status: e});
400 | });
401 | },
402 |
403 | /*
404 | Sets a user's blocked status
405 | Parameters:
406 | user_id - the ID of the user to block/unblock
407 | blocked_status - true - blocked, false - unblocked
408 | callback - to be invoked after the operation completes
409 | */
410 | setBlockedStatus: function(user_id, blocked_status, callback) {
411 | let user = Core.contact(user_id);
412 | if (user == null) {
413 | Core.error(API.Error.USER_NOT_FOUND, callback);
414 | return;
415 | }
416 |
417 | user.setBlock(blocked_status).then(function(e) {
418 | (callback || Core.nop)({status: e});
419 | });
420 | },
421 |
422 | /*
423 | Sends a text message in a given chat.
424 | Parameters:
425 | chat_id - the chat to send a message to.
426 | message_text - the plain text of the message.
427 | callback - to be invoked after the operation completes
428 | */
429 | sendTextMessage: function(chat_id, message_text, callback) {
430 | let chat = Core.chat(chat_id);
431 | if (chat == null) {
432 | Core.error(API.Error.CHAT_NOT_FOUND, callback)
433 | return;
434 | }
435 |
436 | chat.sendMessage(message_text).then(function(e) {
437 | (callback || Core.nop)({status: e});
438 | });
439 | },
440 |
441 | /*
442 | Creates a new group.
443 | Parameters:
444 | group_subject - the title of the newly created group
445 | participants - an array of ids of users to add to the group
446 | callback - to be invoked after the operation completes
447 |
448 | Needs further testing
449 | */
450 | createGroup: function(group_subject, participants, callback) {
451 | let p = [];
452 | for (let x = 0; x < participants.length; x++)
453 | {
454 | p.push(Core.contact(participants[x]));
455 | }
456 |
457 | Store.Chat.createGroup(group_subject, null, null, p, undefined).then(callback);
458 | },
459 |
460 | /*
461 | Retrieves contact info for a certain id
462 | Parameters:
463 | user_id - the id of the user to look for
464 | Return value:
465 | object - the details
466 | null - if the user was not found
467 | */
468 | getContactInfo: function(user_id) {
469 | const contact = Core.contact(user_id);
470 | if (contact == null || !contact["all"]) {
471 | return null;
472 | }
473 |
474 | return contact.all;
475 | },
476 |
477 | /*
478 | Retrieves message info for a certain id
479 | Parameters:
480 | message_id - the id of the message to look for
481 | Return value:
482 | object - the details
483 | null - if the message was not found in any chat
484 | */
485 | getMessageInfo: function(message_id) {
486 | return Core.find(Store.Msg.models, x => x.__x_id._serialized == message_id);
487 | },
488 |
489 | /*
490 | Returns a list of all contact IDs.
491 | Return Value:
492 | array - the array of strings containing the IDs of the clients.
493 | */
494 | getContactList: function() {
495 | const result = [];
496 | Store.Contact.models.forEach(x => { result.push(x.__x_id); });
497 | return result;
498 | },
499 |
500 | /*
501 | Sends contact(s) to a chat.
502 | Parameters:
503 | chat_id - the ID of the chat to write the contacts to
504 | contacts - the contact / array of contacts to send
505 | callback - to be incoked after the operation completes
506 |
507 | Needs further testing.
508 | */
509 | sendContactMessage: function(chat_id, contacts, callback) {
510 | if (contacts.constructor != Array) {
511 | contacts = [contacts];
512 | }
513 |
514 | const chat = Core.chat(chat_id);
515 | if (chat == null) {
516 | Core.error(API.Error.CHAT_NOT_FOUND, callback);
517 | return;
518 | }
519 |
520 | const toSend = [];
521 | contacts.forEach(x => {
522 | const c = Core.contact(x);
523 | if (c != null) {
524 | toSend.push(c);
525 | }
526 | });
527 |
528 | if (!toSend.length) {
529 | Core.error(API.Error.USER_NOT_FOUND, callback);
530 | return;
531 | }
532 |
533 | if (toSend.length == 1) {
534 | chat.sendContact(toSend[0]);
535 | }
536 | else {
537 | chat.sendContactList(toSend);
538 | }
539 | },
540 |
541 | /*
542 | Send the other side " is typing..."
543 | chat_id - the chat id
544 | */
545 | sendTyping: function(chat_id) {
546 | const chat = Core.chat(chat_id);
547 | if (chat == null) {
548 | return API.Error.CHAT_NOT_FOUND;
549 | }
550 |
551 | chat.markComposing();
552 | },
553 |
554 | /*
555 | Send the other side " is recording audio..."
556 | chat_id - the chat id
557 | */
558 | sendRecording: function(chat_id) {
559 | const chat = Core.chat(chat_id);
560 | if (chat == null) {
561 | return API.Error.CHAT_NOT_FOUND;
562 | }
563 |
564 | chat.markRecording();
565 | },
566 |
567 | /*
568 | Hides the " is recording audio..." to the other side
569 | chat_id - the chat id
570 | */
571 | sendStopRecording: function(chat_id) {
572 | const chat = Core.chat(chat_id);
573 | if (chat == null) {
574 | return API.Error.CHAT_NOT_FOUND;
575 | }
576 |
577 | chat.markPaused();
578 | },
579 |
580 | /*
581 | Initializes a group (required to call before interacting with a group).
582 | */
583 | initGroup(group_id, callback) {
584 | const group = Core.group(group_id);
585 | if (group == null) {
586 | return API.Error.GROUP_NOT_FOUND;
587 | }
588 |
589 | group.update().then(callback);
590 | },
591 |
592 | /*
593 | Minimizes a message object to a JSON convertable object for sending over network (smaller size than a huge Msg object)
594 | Parameters:
595 | msg_object - the message object to convert to JSON compatible type
596 | */
597 | parseMsgObject: function(msg_object) {
598 | const m = msg_object.all;
599 | if (msg_object["__x__quotedMsgObj"]) {
600 | m.quotedMsg = API.parseMsgObject(Core.msg(msg_object.__x__quotedMsgObj.__x_id._serialized));
601 | }
602 | m.chat = m.chat.all;
603 | delete m.msgChunk;
604 | return m;
605 | },
606 |
607 | /*
608 | Initializes the API and sets the window.Store object.
609 | */
610 | init: function() {
611 | window.makeStore();
612 | window.API.listener.listen();
613 | }
614 | };
615 | })();
616 |
617 | window.makeStore = function() {
618 | if (!window.Store) {
619 | (function() {
620 | function getStore(modules) {
621 | let foundCount = 0;
622 | let neededObjects = [
623 | { id: "Store", conditions: (module) => (module.Chat && module.Msg) ? module : null },
624 | { id: "Wap", conditions: (module) => (module.createGroup) ? module : null },
625 | { id: "MediaCollection", conditions: (module) => (module.default && module.default.prototype && module.default.prototype.processFiles !== undefined) ? module.default : null },
626 | { id: "WapDelete", conditions: (module) => (module.sendConversationDelete && module.sendConversationDelete.length == 2) ? module : null },
627 | { id: "Conn", conditions: (module) => (module.default && module.default.ref && module.default.refTTL) ? module.default : null },
628 | { id: "WapQuery", conditions: (module) => (module.queryExist) ? module : null },
629 | { id: "ProtoConstructor", conditions: (module) => (module.prototype && module.prototype.constructor.toString().indexOf('binaryProtocol deprecated version') >= 0) ? module : null },
630 | { id: "UserConstructor", conditions: (module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null }
631 | ];
632 |
633 | for (let idx in modules) {
634 | if ((typeof modules[idx] === "object") && (modules[idx] !== null)) {
635 | let first = Object.values(modules[idx])[0];
636 | if ((typeof first === "object") && (first.exports)) {
637 | for (let idx2 in modules[idx]) {
638 | let module = modules(idx2);
639 | if (!module) {
640 | continue;
641 | }
642 |
643 | neededObjects.forEach((needObj) => {
644 | if(!needObj.conditions || needObj.foundedModule) return;
645 | let neededModule = needObj.conditions(module);
646 | if(neededModule !== null) {
647 | foundCount++;
648 | needObj.foundedModule = neededModule;
649 | }
650 | });
651 |
652 | if(foundCount === neededObjects.length) {
653 | break;
654 | }
655 | }
656 |
657 | let neededStore = neededObjects.find((needObj) => needObj.id === "Store");
658 | window.Store = neededStore.foundedModule ? neededStore.foundedModule : {};
659 | neededObjects.splice(neededObjects.indexOf(neededStore), 1);
660 | neededObjects.forEach((needObj) => {
661 | if(needObj.foundedModule) {
662 | window.Store[needObj.id] = needObj.foundedModule;
663 | }
664 | });
665 |
666 | return window.Store;
667 | }
668 | }
669 | }
670 | }
671 |
672 | webpackJsonp([], {'parasite': (x, y, z) => getStore(z)}, 'parasite');
673 | })();
674 | }
675 | };
676 |
677 | API.init();
678 |
679 | let success = 0;
680 | let failure = 0;
681 |
682 | function sendNext(list, progress, size){
683 | let entry = list[progress];
684 | window.API.sendTyping(entry.msisdn+"@c.us");
685 | window.API.sendTextMessage(entry.msisdn+"@c.us",entry.message,function (event){
686 | if (event.status!=="success"){
687 | console.error("Failed sending message: "+entry.message+" to MSISDN: "+entry.msisdn+" Reason: "+event.error);
688 | failure++;
689 | }
690 | else {
691 | console.log("Successfully sent message: "+entry.message+" to MSISDN: "+entry.msisdn);
692 | success++;
693 | }
694 | progress++;
695 | if (progress!==size){
696 | sendNext(list, progress, size);
697 | }
698 | else {
699 | alert("Processed "+(success+failure)+" Entries. Success: "+success+" Failure: "+failure+". See browser console for logs.");
700 | success = 0;
701 | failure = 0;
702 | }
703 | });
704 | }
705 |
706 | let list = [];
707 | let ele = document.createElement("div");
708 | ele.hidden = true;
709 | let csv = document.createElement("input");
710 | csv.type = "file";
711 | csv.id = "csv";
712 | csv.onchange = function(event){
713 | let file = event.target.files[0], read = new FileReader();
714 |
715 | read.readAsBinaryString(file);
716 |
717 | read.onloadend = function(){
718 | list = JSON.parse(read.result);
719 | }
720 | };
721 | let btn = document.createElement("input");
722 | btn.type = "submit";
723 | btn.id = "sender";
724 | btn.innerHTML = "Send";
725 | btn.onclick = function(){
726 | let size = list.length;
727 | sendNext(list, 0, size)
728 | };
729 | ele.appendChild(btn);
730 | ele.appendChild(csv);
731 | document.getElementById("app").prepend(ele);
732 |
--------------------------------------------------------------------------------
/chrome/js/content.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | // For receiving messages from WhatsApp
3 | let messageFromClient = function (data) {
4 | chrome.runtime.sendMessage(data);
5 | };
6 |
7 | // Constructing a new messaging DOM element
8 | let messaging = document.createElement("div");
9 | messaging.id = "whatsapp_messaging";
10 |
11 | // Attaching a listener for messages from WhatsApp
12 | messaging.addEventListener("whatsapp_message", function(e) {
13 | messageFromClient(e.detail);
14 | });
15 |
16 | // Injecting the element
17 | (document.head || document.documentElement).appendChild(messaging);
18 |
19 | // For sending messages to WhatsApp
20 | let clientMessage = function (data) {
21 | document.getElementById("whatsapp_messaging").dispatchEvent(new CustomEvent("content_message", {"detail": data}));
22 | };
23 |
24 | // Listening for messages from background script
25 | chrome.runtime.onMessage.addListener(function(request, sender) {
26 | if (!sender.tab) {
27 | // Message from background script
28 | clientMessage(request);
29 | }
30 | });
31 |
32 | // Loading the API
33 | let el = document.createElement("script");
34 | el.src = chrome.extension.getURL("js/api.js");
35 | (document.head || document.documentElement).appendChild(el);
36 |
37 | // Loading the WhatsBot script
38 | el = document.createElement("script");
39 | el.src = chrome.extension.getURL("js/whatsapp.js");
40 | (document.head || document.documentElement).appendChild(el);
41 | })();
--------------------------------------------------------------------------------
/chrome/js/jquery.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=" ",y.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 background page [-> host])
5 | */
6 | window.serverMessage = function(data) {
7 | document.getElementById("whatsapp_messaging").dispatchEvent(new CustomEvent("whatsapp_message", { "detail": data }));
8 | };
9 |
10 | /*
11 | Receiving messages from the host/background page
12 | */
13 | window.messageFromServer = function(data) {
14 | console.log("Received message from server ", data);
15 |
16 | if (data.type === "ajax") {
17 | if (window.ajaxes[data.ajax_id]) {
18 | if (data.status === "success") {
19 | window.ajaxes[data.ajax_id].success(data.e);
20 | }
21 | else {
22 | window.ajaxes[data.ajax_id].error(data.a, data.b, data.c);
23 | }
24 | delete window.ajaxes[data.ajax_id];
25 | }
26 | }
27 | };
28 |
29 | /*
30 | For the DOM communication between the page and the content script
31 | */
32 | document.getElementById("whatsapp_messaging").addEventListener("content_message", function(e) {
33 | messageFromServer(e.detail);
34 | });
35 |
36 | window.$ajax = function(params) {
37 | let success = null, error = null;
38 | if (params.success) {
39 | success = params.success;
40 | params.success = null;
41 | }
42 | if (params.error) {
43 | error = params.error;
44 | params.error = null;
45 | }
46 | ajax_id = Math.random();
47 |
48 | window.ajaxes[ajax_id] = {success: success, error: error};
49 |
50 | serverMessage({
51 | type: "ajax",
52 | ajax_id: ajax_id,
53 | ajax: params
54 | });
55 | };
--------------------------------------------------------------------------------
/chrome/logo/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/chrome/logo/128.png
--------------------------------------------------------------------------------
/chrome/logo/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/chrome/logo/16.png
--------------------------------------------------------------------------------
/chrome/logo/48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/chrome/logo/48.png
--------------------------------------------------------------------------------
/chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "WhatsApp Bulk Sender",
4 | "version": "1.0.0",
5 | "homepage_url": "https://nikhilkumar.ga",
6 | "description": "A chrome Extension to send bulk messages through WhatsApp Web",
7 | "icons": {
8 | "16": "logo/16.png",
9 | "48": "logo/48.png",
10 | "128": "logo/128.png"
11 | },
12 | "browser_action": {
13 | "default_title": "WhatsApp Bulk Sender",
14 | "default_popup": "html/popup.html"
15 | },
16 |
17 | "background": {
18 | "persistent": true,
19 | "scripts": ["js/jquery.js", "background.js"]
20 | },
21 |
22 | "author": "Nikhil Kumar",
23 |
24 | "permissions": [
25 | "activeTab",
26 | "storage",
27 | "webNavigation",
28 | "webRequest",
29 | "tabs",
30 | "https://web.whatsapp.com/*"
31 | ],
32 |
33 | "web_accessible_resources": [
34 | "js/jquery.js",
35 | "js/api.js",
36 | "js/whatsapp.js"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Oct 20 04:19:17 IST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/samples/sample.csv:
--------------------------------------------------------------------------------
1 | 912266006022,"Enjoy"
2 | 912266006022,"Sending"
3 | 912266006022,"Messages"
4 |
--------------------------------------------------------------------------------
/samples/sample.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "msisdn": 912266006022,
4 | "message": "Enjoy"
5 | },
6 | {
7 | "msisdn": 912266006022,
8 | "message": "Sending"
9 | },
10 | {
11 | "msisdn": 912266006022,
12 | "message": "Messages"
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/samples/tutorial.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikhilmuz/WhatsApp-Bulk-Sender/e26e19b1f2e7122745d601c5bcb2ddf18228618f/samples/tutorial.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------